-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Apigw ng refactor headers to werkzeug headers #11227
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
Apigw ng refactor headers to werkzeug headers #11227
Conversation
{ | ||
"host": host_header, | ||
"test-header": "value1", | ||
"test-header-multi": ["value2", "value3"], | ||
"content-length": len(body), |
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.
It is now removed and no longer accessible in the invocation requests. This is on parity with aws.
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.
LGTM! Nice refactor! Just minor comments, nothing blocking, minor optimizations I'd say 😄
This is great, this is gonna help simplify the code along the way, the multiple headers made it quite complicated... this is great! Great job, I hope this will help for the remapping then!
@@ -120,7 +120,7 @@ def _get_missing_required_parameters(method: Method, request: InvocationRequest) | |||
param_type, param_value = request_parameter.removeprefix("method.request.").split(".") | |||
match param_type: | |||
case "header": | |||
is_missing = param_value not in request.get("headers", []) | |||
is_missing = param_value not in dict(request.get("headers")) |
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.
nit: should we call this once outside the loop, something like case_sensitive_headers
in order call this multiple times?
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.
Big oversitght! Nice catch!
localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py
Outdated
Show resolved
Hide resolved
multi_value_headers = defaultdict(list) | ||
for key, value in headers: | ||
multi_value_headers[key].append(value) | ||
|
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 we could simplify it like you've done in apigateway/next_gen/execute_api/integrations/http.py
:
multi_value_headers = defaultdict(list) | |
for key, value in headers: | |
multi_value_headers[key].append(value) | |
return {k: headers.getlist(k) for k in headers.keys()} | |
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.
Actually, we might want to update both to this implementation.
headers = Headers({"foo":"bar","FOO":"BAR"})
dict_comp_headers ={k: headers.getlist(k) for k in headers.keys()}
# {'foo': ['bar', 'BAR'], 'FOO': ['bar', 'BAR']}
multi_value_headers = defaultdict(list)
for key, value in headers:
multi_value_headers[key].append(value)
# defaultdict(<class 'list'>, {'foo': ['bar'], 'FOO': ['BAR']})
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 does look better 🤔
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 am creating a util that we can reuse for the parameter mapping as well, that will simplify work with casing and multi value everywhere, I believe.
@@ -203,7 +203,7 @@ def get_response_template( | |||
return "" | |||
|
|||
# The invocation request header is used to find the right response templated | |||
accepts = request["raw_headers"].getlist("accept") | |||
accepts = request["headers"].getlist("accept") |
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.
it seems for the render_request_template_mapping
(btw, it seems the name was copy pasted from the integration request) we didn't call dict(context.invocation_request.get("headers", {})
for the MappingTemplateParams
on line 233
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.
It seems like we might not need to, since the VTL template is calling .get(), it appears we are returning the proper values.
We can probably update the render_request_template_mapping as well, and safe a bit on efficiency by not converting the headers to dict for the templates.
c5c9ad2
to
732be5a
Compare
LocalStack Community integration with Pro 2 files ± 0 2 suites ±0 24m 31s ⏱️ - 1h 9m 31s Results for commit 76aa3ea. ± Comparison against base commit 91c4dff. This pull request removes 2527 tests.
♻️ This comment has been updated with latest results. |
def build_multi_value_headers(headers: Headers) -> dict[str, list[str]]: | ||
multi_value_headers = defaultdict(list) | ||
for key, value in headers: | ||
multi_value_headers[key].append(value) | ||
|
||
return multi_value_headers |
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.
@bentsku This util was added as a way to always get the proper multi value headers with none of the duplication created by Headers.getlist()
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.
LGTM! Thanks for addressing the comments and looking more into the multi values headers, that's a great find! 🚀
@@ -128,7 +130,7 @@ def render_request_template_mapping( | |||
params=MappingTemplateParams( | |||
path=request.get("path_parameters"), | |||
querystring=request.get("query_string_parameters", {}), | |||
header=request.get("headers", {}), | |||
header=request.get("headers"), |
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.
as we remove the default value, and we know the headers
are always set, should we use request["headers"]
?
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, I will fix in the follow up PR for the remapped headers.
@@ -113,14 +113,16 @@ def _get_missing_required_parameters(method: Method, request: InvocationRequest) | |||
if not (request_parameters := method.get("requestParameters")): | |||
return missing_params | |||
|
|||
case_sensitive_headers = list(request.get("headers").keys()) |
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.
nit: we could even use set()
here as we use as a lookup, but really not important 😄
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 know they say set()
is meant to be faster, but every time I tried to timeit
, it turned out to be slightly slower. Maybe because I am working with smaller sets? It might have a bigger impact on greater sets? 🤔
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 gave it a try with a list of parameters of 7, and 8 headers, and it's about 15% faster. I guess it can depend heavily 😄
invocation_headers = Headers() | ||
for key, value in headers: | ||
if should_drop_header_from_invocation(key): | ||
LOG.debug("Dropping header from invocation request: '%s'", key) |
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 might be quite verbose, we could also build a list and log it maybe, we'll see once we try it more!
Motivation
As discussed with @bentsku, to simplify the usage of headers through the invocation, we will only keep the Werzeug headers as part of the context. We were mostly using the
raw_headers
through the handlers anyway, so it makes sense to only keep them in the context.This is also in preparation to a refactor that will move some of the duplicate code in the integration handlers to the integration request handler.
Changes
InvocationContext
gets rid ofraw_headers
andmulti_value_headers
. The value of header is what used to be known asraw_headers
IntegrationRequest
type forheaders
is now a WerkzeugHeaders
TODO