1111from samcli .local .services .base_local_service import BaseLocalService , LambdaOutputParser
1212from samcli .lib .utils .stream_writer import StreamWriter
1313from samcli .local .lambdafn .exceptions import FunctionNotFound
14- from samcli .local .events .api_event import ContextIdentity , RequestContext , ApiGatewayLambdaEvent
14+ from samcli .local .events .api_event import (
15+ ContextIdentity ,
16+ ContextHTTP ,
17+ RequestContext ,
18+ RequestContextV2 ,
19+ ApiGatewayLambdaEvent ,
20+ ApiGatewayV2LambdaEvent ,
21+ )
1522from .service_error_responses import ServiceErrorResponses
1623from .path_converter import PathConverter
1724
@@ -25,9 +32,11 @@ class LambdaResponseParseException(Exception):
2532
2633
2734class Route :
35+ API = "Api"
36+ HTTP = "HttpApi"
2837 ANY_HTTP_METHODS = ["GET" , "DELETE" , "PUT" , "POST" , "HEAD" , "OPTIONS" , "PATCH" ]
2938
30- def __init__ (self , function_name , path , methods ):
39+ def __init__ (self , function_name , path , methods , event_type = API ):
3140 """
3241 Creates an ApiGatewayRoute
3342
@@ -38,6 +47,7 @@ def __init__(self, function_name, path, methods):
3847 self .methods = self .normalize_method (methods )
3948 self .function_name = function_name
4049 self .path = path
50+ self .event_type = event_type
4151
4252 def __eq__ (self , other ):
4353 return (
@@ -181,15 +191,26 @@ def _request_handler(self, **kwargs):
181191 route = self ._get_current_route (request )
182192 cors_headers = Cors .cors_to_headers (self .api .cors )
183193
184- method , _ = self .get_request_methods_endpoints (request )
194+ method , endpoint = self .get_request_methods_endpoints (request )
195+ route_key = self ._route_key (method , endpoint )
185196 if method == "OPTIONS" and self .api .cors :
186197 headers = Headers (cors_headers )
187198 return self .service_response ("" , headers , 200 )
188199
189200 try :
190- event = self ._construct_event (
191- request , self .port , self .api .binary_media_types , self .api .stage_name , self .api .stage_variables
192- )
201+ if route .event_type == Route .HTTP :
202+ event = self ._construct_event_http (
203+ request ,
204+ self .port ,
205+ self .api .binary_media_types ,
206+ self .api .stage_name ,
207+ self .api .stage_variables ,
208+ route_key ,
209+ )
210+ else :
211+ event = self ._construct_event (
212+ request , self .port , self .api .binary_media_types , self .api .stage_name , self .api .stage_variables
213+ )
193214 except UnicodeDecodeError :
194215 return ServiceErrorResponses .lambda_failure_response ()
195216
@@ -411,14 +432,14 @@ def _construct_event(flask_request, port, binary_types, stage_name=None, stage_v
411432 # Flask does not parse/decode the request data. We should do it ourselves
412433 request_data = request_data .decode ("utf-8" )
413434
435+ query_string_dict , multi_value_query_string_dict = LocalApigwService ._query_string_params (flask_request )
436+
414437 context = RequestContext (
415438 resource_path = endpoint , http_method = method , stage = stage_name , identity = identity , path = endpoint
416439 )
417440
418441 headers_dict , multi_value_headers_dict = LocalApigwService ._event_headers (flask_request , port )
419442
420- query_string_dict , multi_value_query_string_dict = LocalApigwService ._query_string_params (flask_request )
421-
422443 event = ApiGatewayLambdaEvent (
423444 http_method = method ,
424445 body = request_data ,
@@ -438,6 +459,59 @@ def _construct_event(flask_request, port, binary_types, stage_name=None, stage_v
438459 LOG .debug ("Constructed String representation of Event to invoke Lambda. Event: %s" , event_str )
439460 return event_str
440461
462+ @staticmethod
463+ def _construct_event_http (flask_request , port , binary_types , stage_name = None , stage_variables = None , route_key = None ):
464+ """
465+ Helper method that constructs the Event 2.0 to be passed to Lambda
466+
467+ https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
468+
469+ :param request flask_request: Flask Request
470+ :return: String representing the event
471+ """
472+ # pylint: disable-msg=too-many-locals
473+
474+ endpoint = PathConverter .convert_path_to_api_gateway (flask_request .endpoint )
475+ method = flask_request .method
476+
477+ request_data = flask_request .get_data ()
478+
479+ request_mimetype = flask_request .mimetype
480+
481+ is_base_64 = LocalApigwService ._should_base64_encode (binary_types , request_mimetype )
482+
483+ if is_base_64 :
484+ LOG .debug ("Incoming Request seems to be binary. Base64 encoding the request data before sending to Lambda." )
485+ request_data = base64 .b64encode (request_data )
486+
487+ if request_data :
488+ # Flask does not parse/decode the request data. We should do it ourselves
489+ request_data = request_data .decode ("utf-8" )
490+
491+ query_string_dict , _ = LocalApigwService ._query_string_params (flask_request )
492+
493+ cookies = LocalApigwService ._event_http_cookies (flask_request )
494+ headers = LocalApigwService ._event_http_headers (flask_request , port )
495+ context_http = ContextHTTP (method = method , path = flask_request .path , source_ip = flask_request .remote_addr )
496+ context = RequestContextV2 (http = context_http , route_key = route_key , stage = stage_name )
497+ event = ApiGatewayV2LambdaEvent (
498+ route_key = route_key ,
499+ raw_path = flask_request .path ,
500+ raw_query_string = flask_request .query_string .decode ("utf-8" ),
501+ cookies = cookies ,
502+ headers = headers ,
503+ query_string_params = query_string_dict ,
504+ request_context = context ,
505+ body = request_data ,
506+ path_parameters = flask_request .view_args ,
507+ is_base_64_encoded = is_base_64 ,
508+ stage_variables = stage_variables ,
509+ )
510+
511+ event_str = json .dumps (event .to_dict ())
512+ LOG .debug ("Constructed String representation of Event Version 2.0 to invoke Lambda. Event: %s" , event_str )
513+ return event_str
514+
441515 @staticmethod
442516 def _query_string_params (flask_request ):
443517 """
@@ -506,6 +580,55 @@ def _event_headers(flask_request, port):
506580 multi_value_headers_dict ["X-Forwarded-Port" ] = [str (port )]
507581 return headers_dict , multi_value_headers_dict
508582
583+ @staticmethod
584+ def _event_http_cookies (flask_request ):
585+ """
586+ All cookie headers in the request are combined with commas.
587+
588+ https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
589+
590+ Parameters
591+ ----------
592+ flask_request request
593+ Request from Flask
594+
595+ Returns list
596+ -------
597+ Returns a list of cookies
598+
599+ """
600+ cookies = []
601+ for cookie_key in flask_request .cookies .keys ():
602+ cookies .append ("{}={}" .format (cookie_key , flask_request .cookies .get (cookie_key )))
603+ return cookies
604+
605+ @staticmethod
606+ def _event_http_headers (flask_request , port ):
607+ """
608+ Duplicate headers are combined with commas.
609+
610+ https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
611+
612+ Parameters
613+ ----------
614+ flask_request request
615+ Request from Flask
616+
617+ Returns list
618+ -------
619+ Returns a list of cookies
620+
621+ """
622+ headers = {}
623+ # Multi-value request headers is not really supported by Flask.
624+ # See https://github.com/pallets/flask/issues/850
625+ for header_key in flask_request .headers .keys ():
626+ headers [header_key ] = flask_request .headers .get (header_key )
627+
628+ headers ["X-Forwarded-Proto" ] = flask_request .scheme
629+ headers ["X-Forwarded-Port" ] = str (port )
630+ return headers
631+
509632 @staticmethod
510633 def _should_base64_encode (binary_types , request_mimetype ):
511634 """
0 commit comments