Skip to content

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

Merged
merged 6 commits into from
Jul 18, 2024

Conversation

cloutierMat
Copy link
Contributor

@cloutierMat cloutierMat commented Jul 18, 2024

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 of raw_headers and multi_value_headers. The value of header is what used to be known as raw_headers
  • IntegrationRequest type for headers is now a Werkzeug Headers
  • Various fixes through the handlers to fully support the new types

TODO

@cloutierMat cloutierMat self-assigned this Jul 18, 2024
@cloutierMat cloutierMat added the semver: patch Non-breaking changes which can be included in patch releases label Jul 18, 2024
@cloutierMat cloutierMat changed the base branch from master to apigw-ng-add-circle-ci-pipeline July 18, 2024 02:11
{
"host": host_header,
"test-header": "value1",
"test-header-multi": ["value2", "value3"],
"content-length": len(body),
Copy link
Contributor Author

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.

@cloutierMat cloutierMat marked this pull request as ready for review July 18, 2024 03:38
@cloutierMat cloutierMat requested a review from bentsku as a code owner July 18, 2024 03:38
@cloutierMat cloutierMat added this to the 3.6 milestone Jul 18, 2024
Copy link
Contributor

@bentsku bentsku left a 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"))
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big oversitght! Nice catch!

Comment on lines 525 to 528
multi_value_headers = defaultdict(list)
for key, value in headers:
multi_value_headers[key].append(value)

Copy link
Contributor

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:

Suggested change
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()}

Copy link
Contributor Author

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']})

Copy link
Contributor

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 🤔

Copy link
Contributor Author

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")
Copy link
Contributor

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

Copy link
Contributor Author

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.

Base automatically changed from apigw-ng-add-circle-ci-pipeline to master July 18, 2024 15:30
@cloutierMat cloutierMat force-pushed the apigw-ng-refactor-headers-to-werkzeug-headers branch from c5c9ad2 to 732be5a Compare July 18, 2024 15:34
Copy link

github-actions bot commented Jul 18, 2024

LocalStack Community integration with Pro

  2 files  ±    0    2 suites  ±0   24m 31s ⏱️ - 1h 9m 31s
682 tests  - 2 527  636 ✅  - 2 186  46 💤  - 341  0 ❌ ±0 
684 runs   - 2 527  636 ✅  - 2 186  48 💤  - 341  0 ❌ ±0 

Results for commit 76aa3ea. ± Comparison against base commit 91c4dff.

This pull request removes 2527 tests.
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_lambda_dynamodb
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_opensearch_crud
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_search_books
tests.aws.scenario.bookstore.test_bookstore.TestBookstoreApplication ‑ test_setup
tests.aws.scenario.kinesis_firehose.test_kinesis_firehose.TestKinesisFirehoseScenario ‑ test_kinesis_firehose_s3
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_destination_sns
tests.aws.scenario.lambda_destination.test_lambda_destination_scenario.TestLambdaDestinationScenario ‑ test_infra
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_prefill_dynamodb_table
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]
tests.aws.scenario.loan_broker.test_loan_broker.TestLoanBrokerScenario ‑ test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]
…

♻️ This comment has been updated with latest results.

Comment on lines +29 to +34
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
Copy link
Contributor Author

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()

@cloutierMat cloutierMat requested a review from bentsku July 18, 2024 16:52
Copy link
Contributor

@bentsku bentsku left a 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"),
Copy link
Contributor

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"]?

Copy link
Contributor Author

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())
Copy link
Contributor

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 😄

Copy link
Contributor Author

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? 🤔

Copy link
Contributor

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)
Copy link
Contributor

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!

@cloutierMat cloutierMat merged commit debe841 into master Jul 18, 2024
33 checks passed
@cloutierMat cloutierMat deleted the apigw-ng-refactor-headers-to-werkzeug-headers branch July 18, 2024 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver: patch Non-breaking changes which can be included in patch releases
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants