-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
add context variables and stage variables to API NG context #11111
Changes from 6 commits
01e00e3
0fb41de
1216a74
7e15dac
f105057
f96581c
6a1308d
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 |
---|---|---|
@@ -1,14 +1,21 @@ | ||
import datetime | ||
import logging | ||
from collections import defaultdict | ||
from typing import Optional | ||
from urllib.parse import urlparse | ||
|
||
from rolo.request import Request, restore_payload | ||
from werkzeug.datastructures import Headers, MultiDict | ||
|
||
from localstack.http import Response | ||
from localstack.services.apigateway.helpers import REQUEST_TIME_DATE_FORMAT | ||
from localstack.utils.strings import short_uid | ||
from localstack.utils.time import timestamp | ||
|
||
from ..api import RestApiGatewayHandler, RestApiGatewayHandlerChain | ||
from ..context import InvocationRequest, RestApiInvocationContext | ||
from ..moto_helpers import get_stage_variables | ||
from ..variables import ContextVariables | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
@@ -27,6 +34,11 @@ def __call__( | |
def parse_and_enrich(self, context: RestApiInvocationContext): | ||
# first, create the InvocationRequest with the incoming request | ||
context.invocation_request = self.create_invocation_request(context.request) | ||
# then we can create the ContextVariables, used throughout the invocation as payload and to render authorizer | ||
# payload, mapping templates and such. | ||
context.context_variables = self.create_context_variables(context) | ||
# then populate the stage variables | ||
context.stage_variables = self.fetch_stage_variables(context) | ||
|
||
def create_invocation_request(self, request: Request) -> InvocationRequest: | ||
params, multi_value_params = self._get_single_and_multi_values_from_multidict(request.args) | ||
|
@@ -99,3 +111,44 @@ def _get_single_and_multi_values_from_headers( | |
multi_values[key] = headers.getlist(key) | ||
|
||
return single_values, multi_values | ||
|
||
@staticmethod | ||
def create_context_variables(context: RestApiInvocationContext) -> ContextVariables: | ||
invocation_request: InvocationRequest = context.invocation_request | ||
domain_name = invocation_request["raw_headers"].get("Host", "") | ||
domain_prefix = domain_name.split(".")[0] | ||
now = datetime.datetime.now() | ||
|
||
# TODO: verify which values needs to explicitly have None set | ||
context_variables = ContextVariables( | ||
accountId=context.account_id, | ||
apiId=context.api_id, | ||
deploymentId=context.deployment_id, | ||
domainName=domain_name, | ||
domainPrefix=domain_prefix, | ||
extendedRequestId=short_uid(), # TODO: use snapshot tests to verify format | ||
httpMethod=invocation_request["http_method"], | ||
path=invocation_request[ | ||
"path" | ||
], # TODO: check if we need the raw path? with forward slashes | ||
protocol="HTTP/1.1", | ||
requestId=short_uid(), # TODO: use snapshot tests to verify format | ||
requestTime=timestamp(time=now, format=REQUEST_TIME_DATE_FORMAT), | ||
requestTimeEpoch=int(now.timestamp() * 1000), | ||
stage=context.stage, | ||
) | ||
return context_variables | ||
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. Should we log this dict immediately? 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. Done! Good catch! We can revisit the overall logging later on when we have a clear picture, to make it a consistent experience, but for now simply logging it is really good. I believe we will also realize later on some values needs default values, maybe |
||
|
||
@staticmethod | ||
def fetch_stage_variables(context: RestApiInvocationContext) -> Optional[dict[str, str]]: | ||
stage_variables = get_stage_variables( | ||
account_id=context.account_id, | ||
region=context.region, | ||
api_id=context.api_id, | ||
stage_name=context.stage, | ||
) | ||
if not stage_variables: | ||
# we need to set the stage variables to None in the context if we don't have at least one | ||
return None | ||
|
||
return stage_variables |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
|
||
from ..api import RestApiGatewayHandler, RestApiGatewayHandlerChain | ||
from ..context import RestApiInvocationContext | ||
from ..variables import ContextVariables | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
@@ -84,6 +85,7 @@ def route_and_enrich(self, context: RestApiInvocationContext): | |
router = self.get_router_for_deployment(context.deployment) | ||
|
||
resource, path_parameters = router.match(context) | ||
resource: Resource | ||
|
||
context.invocation_request["path_parameters"] = path_parameters | ||
context.resource = resource | ||
|
@@ -94,6 +96,16 @@ def route_and_enrich(self, context: RestApiInvocationContext): | |
) | ||
context.resource_method = method | ||
|
||
self.update_context_variables_with_resource(context.context_variables, resource) | ||
|
||
@staticmethod | ||
def update_context_variables_with_resource( | ||
context_variables: ContextVariables, resource: Resource | ||
): | ||
# TODO: log updating the context_variables? | ||
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. It is fine to leave as a TODO, but I think it would really useful to log them in debug mode. 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. Done 👍 |
||
context_variables["resourcePath"] = resource["path"] | ||
context_variables["resourceId"] = resource["id"] | ||
|
||
@staticmethod | ||
@cache | ||
def get_router_for_deployment(deployment: RestApiDeployment) -> RestAPIResourceRouter: | ||
|
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.
Having that clear separation from the invocation request will be key in maintaining transparency over the requests.