Skip to content

Commit

Permalink
Request transformation plugin and minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
subnetmarco committed Apr 22, 2015
1 parent 2e3814d commit 1388516
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 10 deletions.
7 changes: 6 additions & 1 deletion kong-0.2.0-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ dependencies = {
"luasocket ~> 2.0.2-5",
"ansicolors ~> 1.0.2-3",
"lrexlib-pcre ~> 2.7.2-1",
"lua-llthreads2 ~> 0.1.3-1"
"lua-llthreads2 ~> 0.1.3-1",
"multipart ~> 0.1-2"
}
build = {
type = "builtin",
Expand Down Expand Up @@ -102,6 +103,10 @@ build = {
["kong.plugins.ratelimiting.access"] = "kong/plugins/ratelimiting/access.lua",
["kong.plugins.ratelimiting.schema"] = "kong/plugins/ratelimiting/schema.lua",

["kong.plugins.request_transfomer.handler"] = "kong/plugins/request_transformer/handler.lua",
["kong.plugins.request_transfomer.access"] = "kong/plugins/request_transformer/access.lua",
["kong.plugins.request_transfomer.schema"] = "kong/plugins/request_transformer/schema.lua",

["kong.web.app"] = "kong/web/app.lua",
["kong.web.routes.apis"] = "kong/web/routes/apis.lua",
["kong.web.routes.consumers"] = "kong/web/routes/consumers.lua",
Expand Down
1 change: 1 addition & 0 deletions kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins_available:
- tcplog
- udplog
- filelog
- request_transformer

# Nginx prefix path directory
nginx_working_dir: /usr/local/kong/
Expand Down
9 changes: 9 additions & 0 deletions kong/kong.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ local IO = require "kong.tools.io"
local cache = require "kong.tools.cache"
local constants = require "kong.constants"
local timestamp = require "kong.tools.timestamp"
local stringy = require "stringy"

-- Define the plugins to load here, in the appropriate order
local plugins = {}
Expand Down Expand Up @@ -176,6 +177,14 @@ function _M.exec_plugins_access()
end
end

-- Append any modified querystring parameters
local parts = stringy.split(ngx.var.backend_url, "?")
local final_url = parts[1]
if utils.table_size(ngx.req.get_uri_args()) > 0 then
final_url = final_url.."?"..ngx.encode_args(ngx.req.get_uri_args())
end
ngx.var.backend_url = final_url

ngx.ctx.proxy_started_at = timestamp.get_utc() -- Setting a property that will be available for every plugin
end

Expand Down
130 changes: 130 additions & 0 deletions kong/plugins/request_transformer/access.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
local stringy = require "stringy"
local Multipart = require "multipart"

local _M = {}

local CONTENT_LENGTH = "content-length"
local FORM_URLENCODED = "application/x-www-form-urlencoded"
local MULTIPART_DATA = "multipart/form-data"
local CONTENT_TYPE = "content-type"

local function iterate_and_exec(val, cb)
if utils.table_size(val) > 0 then
for _, entry in ipairs(val) do
local parts = stringy.split(entry, ":")
cb(parts[1], utils.table_size(parts) == 2 and parts[2] or nil)
end
end
end

local function get_content_type(request)
local header_value = ngx.req.get_headers()[CONTENT_TYPE]
if header_value then
return stringy.strip(header_value)
end
return nil
end

function _M.execute(conf)
if not conf then return end

if conf.add then

-- Add headers
if conf.add.headers then
iterate_and_exec(conf.add.headers, function(name, value)
ngx.req.set_header(name, value)
end)
end

-- Add Querystring
if conf.add.querystring then
local querystring = ngx.req.get_uri_args()
iterate_and_exec(conf.add.querystring, function(name, value)
querystring[name] = value
end)
ngx.req.set_uri_args(querystring)
end

if conf.add.form then
local content_type = get_content_type(ngx.req)
if content_type and stringy.startswith(content_type, FORM_URLENCODED) then
-- Call ngx.req.read_body to read the request body first
-- or turn on the lua_need_request_body directive to avoid errors.
ngx.req.read_body()

local parameters = ngx.req.get_post_args()
iterate_and_exec(conf.add.form, function(name, value)
parameters[name] = value
end)
local encoded_args = ngx.encode_args(parameters)
ngx.req.set_header(CONTENT_LENGTH, string.len(encoded_args))
ngx.req.set_body_data(encoded_args)
elseif content_type and stringy.startswith(content_type, MULTIPART_DATA) then
-- Call ngx.req.read_body to read the request body first
-- or turn on the lua_need_request_body directive to avoid errors.
ngx.req.read_body()

local body = ngx.req.get_body_data()
local parameters = Multipart(body and body or "", content_type)
iterate_and_exec(conf.add.form, function(name, value)
parameters:set_simple(name, value)
end)
local new_data = parameters:tostring()
ngx.req.set_header(CONTENT_LENGTH, string.len(new_data))
ngx.req.set_body_data(new_data)
end
end

end

if conf.remove then

-- Add headers
if conf.remove.headers then
iterate_and_exec(conf.remove.headers, function(name, value)
ngx.req.clear_header(name)
end)
end

if conf.remove.querystring then
local querystring = ngx.req.get_uri_args()
iterate_and_exec(conf.remove.querystring, function(name)
querystring[name] = nil
end)
ngx.req.set_uri_args(querystring)
end

if conf.remove.form then
local content_type = get_content_type(ngx.req)
if content_type and stringy.startswith(content_type, FORM_URLENCODED) then
local parameters = ngx.req.get_post_args()

iterate_and_exec(conf.remove.form, function(name)
parameters[name] = nil
end)

local encoded_args = ngx.encode_args(parameters)
ngx.req.set_header(CONTENT_LENGTH, string.len(encoded_args))
ngx.req.set_body_data(encoded_args)
elseif content_type and stringy.startswith(content_type, MULTIPART_DATA) then
-- Call ngx.req.read_body to read the request body first
-- or turn on the lua_need_request_body directive to avoid errors.
ngx.req.read_body()

local body = ngx.req.get_body_data()
local parameters = Multipart(body and body or "", content_type)
iterate_and_exec(conf.remove.form, function(name)
parameters:delete(name)
end)
local new_data = parameters:tostring()
ngx.req.set_header(CONTENT_LENGTH, string.len(new_data))
ngx.req.set_body_data(new_data)
end
end

end

end

return _M
19 changes: 19 additions & 0 deletions kong/plugins/request_transformer/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Copyright (C) Mashape, Inc.

local BasePlugin = require "kong.plugins.base_plugin"
local access = require "kong.plugins.request_transformer.access"

local RequestTransformerHandler = BasePlugin:extend()

function RequestTransformerHandler:new()
RequestTransformerHandler.super.new(self, "request_transformer")
end

function RequestTransformerHandler:access(conf)
RequestTransformerHandler.super.access(self)
access.execute(conf)
end

RequestTransformerHandler.PRIORITY = 800

return RequestTransformerHandler
16 changes: 16 additions & 0 deletions kong/plugins/request_transformer/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local constants = require "kong.constants"

return {
add = { require = "false", type = "table", schema = {
form = { required = false, type = "table" },
headers = { required = false, type = "table" },
querystring = { required = false, type = "table" }
}
},
remove = { require = "false", type = "table", schema = {
form = { required = false, type = "table" },
headers = { required = false, type = "table" },
querystring = { required = false, type = "table" }
}
}
}
13 changes: 11 additions & 2 deletions kong/tools/faker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Faker.FIXTURES = {
{ name = "API TESTS 2", public_dns = "test2.com", target_url = "http://mockbin.com" },
{ name = "API TESTS 3", public_dns = "test3.com", target_url = "http://mockbin.com" },
{ name = "API TESTS 4", public_dns = "test4.com", target_url = "http://mockbin.com" },
{ name = "API TESTS 5", public_dns = "test5.com", target_url = "http://mockbin.com" }

-- DEVELOPMENT APIs. Please do not use those in tests
},
Expand All @@ -48,15 +49,23 @@ Faker.FIXTURES = {
{ name = "keyauth", value = { key_names = { "apikey" }}, __api = 1 },
{ name = "tcplog", value = { host = "127.0.0.1", port = 7777 }, __api = 1 },
{ name = "udplog", value = { host = "127.0.0.1", port = 8888 }, __api = 1 },
{ name = "filelog", value = {}, __api = 1 },
{ name = "filelog", value = { }, __api = 1 },
-- API 2
{ name = "basicauth", value = {}, __api = 2 },
-- API 3
{ name = "keyauth", value = {key_names = {"apikey"}, hide_credentials = true}, __api = 3 },
{ name = "ratelimiting", value = {period = "minute", limit = 6}, __api = 3 },
{ name = "ratelimiting", value = {period = "minute", limit = 8}, __api = 3, __consumer = 1 },
-- API 4
{ name = "ratelimiting", value = {period = "minute", limit = 6}, __api = 4 }
{ name = "ratelimiting", value = {period = "minute", limit = 6}, __api = 4 },
--API 5
{ name = "request_transformer", value = {
add = { headers = {"x-added:true", "x-added2:true" },
querystring = {"newparam:value"},
form = {"newformparam:newvalue"} },
remove = { headers = { "x-to-remove" },
querystring = { "toremovequery" },
form = { "toremoveform" } } }, __api = 5 }
}
}

Expand Down
4 changes: 2 additions & 2 deletions kong/web/routes/base_controller.lua
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function BaseController.parse_params(schema, params)

-- Process subschemas
for k, v in pairs(subschemas) do
local subschema_value = BaseController.parse_params(v.schema(result), v.params)
local subschema_value = BaseController.parse_params(type(v.schema) == "table" and v.schema or v.schema(result), v.params)
if utils.table_size(subschema_value) > 0 then -- Set subschemas to nil if nothing exists
result[k] = subschema_value
else
Expand Down Expand Up @@ -188,4 +188,4 @@ function BaseController:new(dao_collection, collection)

end

return BaseController
return BaseController
4 changes: 2 additions & 2 deletions spec/integration/cli/start_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("CLI", function()
end)

it("should not work when a plugin is being used in the DB but it's not in the configuration", function()
replace_conf_property("plugins_available", {"keyauth", "basicauth", "tcplog", "udplog", "filelog"})
replace_conf_property("plugins_available", {"keyauth", "basicauth", "tcplog", "udplog", "filelog", "request_transformer"})

spec_helper.prepare_db(SERVER_CONF)

Expand All @@ -88,7 +88,7 @@ describe("CLI", function()
end)

it("should work the used plugins are enabled", function()
replace_conf_property("plugins_available", {"ratelimiting", "keyauth", "basicauth", "tcplog", "udplog", "filelog"})
replace_conf_property("plugins_available", {"ratelimiting", "keyauth", "basicauth", "tcplog", "udplog", "filelog", "request_transformer"})

spec_helper.prepare_db(SERVER_CONF)

Expand Down
104 changes: 104 additions & 0 deletions spec/integration/proxy/request_transformer_plugin_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
local spec_helper = require "spec.spec_helpers"
local http_client = require "kong.tools.http_client"
local cjson = require "cjson"

STUB_GET_URL = spec_helper.STUB_GET_URL
STUB_POST_URL = spec_helper.STUB_POST_URL

describe("Request Transformer Plugin #proxy", function()

setup(function()
spec_helper.prepare_db()
spec_helper.start_kong()
end)

teardown(function()
spec_helper.stop_kong()
spec_helper.reset_db()
end)

describe("Test adding parameters", function()

it("should add new headers", function()
local response, status, headers = http_client.get(STUB_GET_URL, {}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("true", body.headers["x-added"])
assert.are.equal("true", body.headers["x-added2"])
end)

it("should add new parameters on POST", function()
local response, status, headers = http_client.post(STUB_POST_URL, {}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("newvalue", body.postData.params["newformparam"])
end)

it("should add new parameters on POST when existing params exist", function()
local response, status, headers = http_client.post(STUB_POST_URL, { hello = "world" }, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("world", body.postData.params["hello"])
assert.are.equal("newvalue", body.postData.params["newformparam"])
end)

it("should add new parameters on multipart POST", function()
local response, status, headers = http_client.post_multipart(STUB_POST_URL, {}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("newvalue", body.postData.params["newformparam"])
end)

it("should add new parameters on multipart POST when existing params exist", function()
local response, status, headers = http_client.post_multipart(STUB_POST_URL, { hello = "world" }, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("world", body.postData.params["hello"])
assert.are.equal("newvalue", body.postData.params["newformparam"])
end)

it("should add new parameters on GET", function()
local response, status, headers = http_client.get(STUB_GET_URL, {}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.are.equal("value", body.queryString["newparam"])
end)

end)

describe("Test removing parameters", function()

it("should remove a header", function()
local response, status, headers = http_client.get(STUB_GET_URL, {}, {host = "test5.com", ["x-to-remove"] = "true"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.falsy(body.headers["x-to-remove"])
end)

it("should remove parameters on POST", function()
local response, status, headers = http_client.post(STUB_POST_URL, {["toremoveform"] = "yes", ["nottoremove"] = "yes"}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.falsy(body.postData.params["toremoveform"])
assert.are.same("yes", body.postData.params["nottoremove"])
end)

it("should remove parameters on multipart POST", function()
local response, status, headers = http_client.post_multipart(STUB_POST_URL, {["toremoveform"] = "yes", ["nottoremove"] = "yes"}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.falsy(body.postData.params["toremoveform"])
assert.are.same("yes", body.postData.params["nottoremove"])
end)

it("should remove parameters on GET", function()
local response, status, headers = http_client.get(STUB_GET_URL, {["toremovequery"] = "yes", ["nottoremove"] = "yes"}, {host = "test5.com"})
local body = cjson.decode(response)
assert.are.equal(200, status)
assert.falsy(body.queryString["toremovequery"])
assert.are.equal("yes", body.queryString["nottoremove"])
end)

end)

end)
Loading

0 comments on commit 1388516

Please sign in to comment.