-
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
"Rewrite URL captures" policy #827
Conversation
{ "pattern": "/", "http_method": "GET", "metric_system_name": "hits", "delta": 2 } | ||
], | ||
"policy_chain": [ | ||
{ "name": "apicast.policy.apicast" }, |
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.
I'll change these tests so they use the Echo policy instead. I think that will simplify the code.
I'll keep just one with the APIcast policy just to make sure that there are not any problems when combining it with the new policy.
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.
I'd love that 👍
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.
I just realized that we can't do that because the Echo policy uses ngx.say(ngx.var.request)
, so it just works with the original request.
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.
So the upstream policy pointing to the echo endpoint? That is still simpler than APIcast + dependencies.
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.
Using the echo policy requires more work because we are only interested in checking the path and the query args, but the echo returns more data.
In the end, I opted for deleting the APIcast policy from the chain and adding an upstream policy that redirects all the request to the upstream defining in the config. I think this is the simplest solution.
I kept one of the tests I had with the APIcast policy just to make sure that both policies can be combined without any problems.
local replaced_template = replace_in_template( | ||
self.named_args, matches, self.template) | ||
|
||
local uri, raw_params = unpack(re_split(replaced_template, '\\?')) |
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.
unpack
can't be JITed so this should not be in the hot path.
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.
And instead of '\\?'
you can use [[\?]]
, but that might be just my preference.
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 the unpack
.
I don't have a strong preference on \\?
vs [[\?]]
so I changed it.
local res = template | ||
|
||
for i, arg in ipairs(args) do | ||
res = re_gsub(res, "{" .. arg .. "}", vals[i], 'oj') |
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 allocating 3 strings in a loop. Better to use string.format
and even better to do this during policy initialization.
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.
Right. It turns out that we always use the result of this wrapped between "{" and "}" so I concatenate them in extract_named_args
which is only called from .new()
.
local params_split = re_split(string_params, '&') | ||
|
||
for _, param in ipairs(params_split) do | ||
local name, val = unpack(re_split(param, '=')) |
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.
Another unpack in a loop. This will prevent JIT.
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 👍
local function string_params_to_table(string_params) | ||
local res = {} | ||
|
||
local params_split = re_split(string_params, '&') |
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.
Maybe good to extract re_split
into own function split
that will always pass oj
flags.
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.
Right, that way we won't forget to add oj
. Fixed 👍
So it can be used from other policies, and not just from the URL rewriting one.
abf0448
to
b47a619
Compare
local params_split = split(string_params, '&') | ||
|
||
for _, param in ipairs(params_split) do | ||
local parts = split(param, '=') -- avoid unpack, not jitted. |
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.
You might want to limit the number of parts you want by setting the max results parameter.
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.
Done 👍
local parts = split(param, '=') -- avoid unpack, not jitted. | ||
local name = parts[1] | ||
local val = parts[2] | ||
res[name] = val |
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.
LuaJIT performance guide says it is better not to create short lived temporary variables like these and just use the tuple:
res[parts[1]] = parts[2]
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.
I think that defining those vars helps readability. I doubt this will have a noticeable impact.
local function set_query_params(new_params) | ||
local query_params = QueryParams.new() | ||
|
||
for arg, value in pairs(new_params) do |
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.
pairs
is not JITed. Does it need to be key valued table? Integer indexed could be JITed.
end | ||
|
||
local function uri_and_params_from_template(template) | ||
local parts = split(template, [[\?]]) -- avoid unpack, not jitted. |
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.
Here it might need to limit the results too.
@mikz Regarding the name of the policy, do you have any suggestions? |
1d797ad
to
d9c7b97
Compare
I addressed all your comments @mikz |
d9c7b97
to
70f81b8
Compare
…ing to URL captures
70f81b8
to
306619a
Compare
end | ||
|
||
-- Returns a list of named args extracted from a match_rule. | ||
-- For example, for the rule /{abc}/{def}?{ghi}=1, it returns this list: |
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.
So, do you think it makes sense to use just {}
instead of {{ }}
to match with the liquid syntax?
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.
I think {}
is better in this case because it's the same format that we are using for the mapping rules.
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.
Ok, thats fair point. And for the replacement ? Shouldn't we just pass it through liquid?
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.
Not sure. That would only replace the code we have in replace_in_template
which is not problematic, in my opinion.
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.
Yep. I'm thinking about consistency. If push people everywhere to use liquid, then it would make sense to stick to it. I have no hard feelings one way or the other, just want to avoid confused people that can't use what they do everywhere else.
But it will not be hard to migrate. Just adding the template_type
. So if you have no feelings one way or the other then lets merge this 👍
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.
I'd rather leave this one as it is, at least for now 👍
function _M.new(config) | ||
local self = new(config) | ||
|
||
self.matchers = {} |
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.
Just for the future. Using tab_new(#config.transformations, 0)
would save table expansion because it would preallocate slots in the table for each matcher.
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.
I think this is great! 👍
My only opened question is if we should make this look like liquid.
And regarding the name, I personally think this is fine, but @3scale/product should have say in this.
Closes #711 (first use case)
After the discussion in #711, I think that implementing the first use case of that issue with a new policy seems reasonable. We need something for the next release, although in the future, we might be able to do something better as we add more features to the conditional policy.
Regarding the name of the policy, I'm open to suggestions. I can't think of a good name for it 🤔