-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce URL rewriting policy [THREESCALE-296] #529
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
--- URL rewriting policy | ||
-- This policy allows to modify the path of a request. | ||
|
||
local ipairs = ipairs | ||
local sub = ngx.re.sub | ||
local gsub = ngx.re.gsub | ||
|
||
local policy = require('apicast.policy') | ||
local _M = policy.new('URL rewriting policy') | ||
|
||
local new = _M.new | ||
|
||
local substitute_functions = { sub = sub, gsub = gsub } | ||
|
||
-- func needs to be ngx.re.sub or ngx.re.gsub. | ||
-- This method simply calls one of those 2. They have the same interface. | ||
local function substitute(func, subject, regex, replace, options) | ||
local new_uri, num_changes, err = func(subject, regex, replace, options) | ||
|
||
if not new_uri then | ||
ngx.log(ngx.WARN, 'There was an error applying the regex: ', err) | ||
end | ||
|
||
return new_uri, num_changes > 0 | ||
end | ||
|
||
-- Returns true when the URL was rewritten and false otherwise | ||
local function apply_rewrite_command(command) | ||
local func = substitute_functions[command.op] | ||
|
||
if not func then | ||
ngx.log(ngx.WARN, "Unknown URL rewrite operation: ", command.op) | ||
end | ||
|
||
local new_uri, changed = substitute( | ||
func, ngx.var.uri, command.regex, command.replace, command.options) | ||
|
||
if changed then | ||
ngx.req.set_uri(new_uri) | ||
end | ||
|
||
return changed | ||
end | ||
|
||
--- Initialize a URL rewriting policy | ||
-- @tparam[opt] table config Contains the rewrite commands. | ||
-- The rewrite commands are based on the 'ngx.re.sub' and 'ngx.re.gsub' | ||
-- functions provided by OpenResty. Please check | ||
-- https://github.com/openresty/lua-nginx-module for more details. | ||
-- Each rewrite command is a table with the following fields: | ||
-- | ||
-- - op: can be 'sub' or 'gsub'. | ||
-- - regex: regular expression to be matched. | ||
-- - replace: string that will replace whatever is matched by the regex. | ||
-- - options[opt]: options to control how the regex match will be done. | ||
-- Accepted options are the ones in 'ngx.re.sub' and 'ngx.re.gsub'. | ||
-- - break[opt]: defaults to false. When set to true, if the command rewrote | ||
-- the URL, it will be the last command applied. | ||
function _M.new(config) | ||
local self = new() | ||
self.config = config or {} | ||
return self | ||
end | ||
|
||
function _M:rewrite() | ||
for _, command in ipairs(self.config) do | ||
local rewritten = apply_rewrite_command(command) | ||
|
||
if rewritten and command['break'] then | ||
break | ||
end | ||
end | ||
end | ||
|
||
return _M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"title": "URL rewriting policy configuration", | ||
"type": "object", | ||
"properties": { | ||
"commands": { | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"op": { | ||
"type": "string", | ||
"enum": ["sub", "gsub"] | ||
}, | ||
"regex": { | ||
"type": "string" | ||
}, | ||
"replace": { | ||
"type": "string" | ||
}, | ||
"options": { | ||
"type": "string" | ||
}, | ||
"break": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are considering also other mutually exclusive values like: IMO might be nicer to model it in a way they are truly mutually exclusive by being a value of one key: But since we are not releasing this yet, It could wait until we have some more options. |
||
"type": "boolean" | ||
} | ||
}, | ||
"required": ["op", "regex", "replace"] | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
use lib 't'; | ||
use TestAPIcastBlackbox 'no_plan'; | ||
|
||
repeat_each(2); | ||
run_tests(); | ||
|
||
__DATA__ | ||
|
||
=== TEST 1: sub operation | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
local args = ngx.var.args | ||
if args == expected then | ||
ngx.exit(200) | ||
else | ||
ngx.log(ngx.ERR, expected, ' did not match: ', args) | ||
ngx.exit(403) | ||
end | ||
} | ||
} | ||
--- configuration | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.url_rewriting", | ||
"configuration": | ||
[ | ||
{ "op": "sub", "regex": "original", "replace": "new" } | ||
] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
--- upstream | ||
location ~ /xxx_new_yyy$ { | ||
content_by_lua_block { | ||
ngx.say('yay, api backend'); | ||
} | ||
} | ||
--- request | ||
GET /xxx_original_yyy?user_key=value | ||
--- response_body | ||
yay, api backend | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] | ||
|
||
=== TEST 2: gsub operation | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
local args = ngx.var.args | ||
if args == expected then | ||
ngx.exit(200) | ||
else | ||
ngx.log(ngx.ERR, expected, ' did not match: ', args) | ||
ngx.exit(403) | ||
end | ||
} | ||
} | ||
--- configuration | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.url_rewriting", | ||
"configuration": | ||
[ | ||
{ "op": "gsub", "regex": "original", "replace": "new" } | ||
] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
--- upstream | ||
location ~ /aaa_new_bbb_new_ccc$ { | ||
content_by_lua_block { | ||
ngx.say('yay, api backend'); | ||
} | ||
} | ||
--- request | ||
GET /aaa_original_bbb_original_ccc?user_key=value | ||
--- response_body | ||
yay, api backend | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] | ||
|
||
=== TEST 3: ordered commands | ||
Substitutions are applied in the order specified. | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
local args = ngx.var.args | ||
if args == expected then | ||
ngx.exit(200) | ||
else | ||
ngx.log(ngx.ERR, expected, ' did not match: ', args) | ||
ngx.exit(403) | ||
end | ||
} | ||
} | ||
--- configuration | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.url_rewriting", | ||
"configuration": | ||
[ | ||
{ "op": "gsub", "regex": "aaa", "replace": "bbb", "options": "i" }, | ||
{ "op": "sub", "regex": "bbb", "replace": "ccc" }, | ||
{ "op": "sub", "regex": "ccc", "replace": "ddd" } | ||
] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
--- upstream | ||
location ~ /ddd_bbb$ { | ||
content_by_lua_block { | ||
ngx.say('yay, api backend'); | ||
} | ||
} | ||
--- request | ||
GET /aaa_aaa?user_key=value | ||
--- response_body | ||
yay, api backend | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] | ||
|
||
=== TEST 4: break | ||
We need to test 2 things: | ||
1) A break is only applied when the URL is rewritten. | ||
2) When break is specified in a command, it will be the last one applied. | ||
--- backend | ||
location /transactions/authrep.xml { | ||
content_by_lua_block { | ||
local expected = "service_token=token-value&service_id=42&usage%5Bhits%5D=2&user_key=value" | ||
local args = ngx.var.args | ||
if args == expected then | ||
ngx.exit(200) | ||
else | ||
ngx.log(ngx.ERR, expected, ' did not match: ', args) | ||
ngx.exit(403) | ||
end | ||
} | ||
} | ||
--- configuration | ||
{ | ||
"services": [ | ||
{ | ||
"id": 42, | ||
"backend_version": 1, | ||
"backend_authentication_type": "service_token", | ||
"backend_authentication_value": "token-value", | ||
"proxy": { | ||
"api_backend": "http://test:$TEST_NGINX_SERVER_PORT/", | ||
"proxy_rules": [ | ||
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, | ||
{ | ||
"name": "apicast.policy.url_rewriting", | ||
"configuration": | ||
[ | ||
{ "op": "sub", "regex": "does_not_match", "replace": "a", "break": true }, | ||
{ "op": "sub", "regex": "aaa", "replace": "bbb" }, | ||
{ "op": "sub", "regex": "bbb", "replace": "ccc", "break": true }, | ||
{ "op": "sub", "regex": "ccc", "replace": "ddd" } | ||
] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
--- upstream | ||
location ~ /ccc$ { | ||
content_by_lua_block { | ||
ngx.say('yay, api backend'); | ||
} | ||
} | ||
--- request | ||
GET /aaa?user_key=value | ||
--- response_body | ||
yay, api backend | ||
--- error_code: 200 | ||
--- no_error_log | ||
[error] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not recognized by the ldoc and looks kind of ugly in the generated doc. Not sure how better describe it though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed :)