def _request_handler(self, **kwargs): """ We handle all requests to the host:port. The general flow of handling a request is as follows * Fetch request from the Flask Global state. This is where Flask places the request and is per thread so multiple requests are still handled correctly * Find the Lambda function to invoke by doing a look up based on the request.endpoint and method * If we don't find the function, we will throw a 502 (just like the 404 and 405 responses we get from Flask. * Since we found a Lambda function to invoke, we construct the Lambda Event from the request * Then Invoke the Lambda function (docker container) * We then transform the response or errors we get from the Invoke and return the data back to the caller :param kwargs dict: Keyword Args that are passed to the function from Flask. This happens when we have Path Parameters. :return: Response object """ route = self._get_current_route(request) try: event = self._construct_event(request, self.port, route.binary_types) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() stdout_stream = io.BytesIO() try: self.lambda_runner.invoke(route.function_name, event, stdout=stdout_stream, stderr=self.stderr) except FunctionNotFound: return ServiceErrorResponses.lambda_not_found_response() lambda_response, lambda_logs, _ = LambdaOutputParser.get_lambda_output( stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) try: (status_code, headers, body) = self._parse_lambda_output(lambda_response, route.binary_types, request) except (KeyError, TypeError, ValueError): LOG.error( "Function returned an invalid response (must include one of: body, headers or " "statusCode in the response object). Response received: %s", lambda_response) return ServiceErrorResponses.lambda_failure_response() return self.service_response(body, headers, status_code)
def test_get_lambda_output_extracts_response(self, test_case_name, stdout_data, expected_logs, expected_response): stdout = Mock() stdout.getvalue.return_value = stdout_data response, logs, is_customer_error = LambdaOutputParser.get_lambda_output( stdout) self.assertEqual(logs, expected_logs) self.assertEqual(response, expected_response) self.assertFalse(is_customer_error)
def _invoke_request_handler(self, function_name): """ Request Handler for the Local Lambda Invoke path. This method is responsible for understanding the incoming request and invoking the Local Lambda Function Parameters ---------- function_name str Name of the function to invoke Returns ------- A Flask Response response object as if it was returned from Lambda """ flask_request = request request_data = flask_request.get_data() if not request_data: request_data = b'{}' request_data = request_data.decode('utf-8') stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(function_name, request_data, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: LOG.debug('%s was not found to invoke.', function_name) return LambdaErrorResponses.resource_not_found(function_name) lambda_response, lambda_logs, is_lambda_user_error_response = \ LambdaOutputParser.get_lambda_output(stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) if is_lambda_user_error_response: return self.service_response( lambda_response, { 'Content-Type': 'application/json', 'x-amz-function-error': 'Unhandled' }, 200) return self.service_response(lambda_response, {'Content-Type': 'application/json'}, 200)
def _invoke_request_handler(self, function_name): """ Request Handler for the Local Lambda Invoke path. This method is responsible for understanding the incoming request and invoking the Local Lambda Function Parameters ---------- function_name str Name of the function to invoke Returns ------- A Flask Response response object as if it was returned from Lambda """ flask_request = request request_data = flask_request.get_data() if not request_data: request_data = b'{}' request_data = request_data.decode('utf-8') stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(function_name, request_data, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: LOG.debug('%s was not found to invoke.', function_name) return LambdaErrorResponses.resource_not_found(function_name) lambda_response, lambda_logs, is_lambda_user_error_response = \ LambdaOutputParser.get_lambda_output(stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) if is_lambda_user_error_response: return self.service_response(lambda_response, {'Content-Type': 'application/json', 'x-amz-function-error': 'Unhandled'}, 200) return self.service_response(lambda_response, {'Content-Type': 'application/json'}, 200)
def _request_handler(self, **kwargs): """ We handle all requests to the host:port. The general flow of handling a request is as follows * Fetch request from the Flask Global state. This is where Flask places the request and is per thread so multiple requests are still handled correctly * Find the Lambda function to invoke by doing a look up based on the request.endpoint and method * If we don't find the function, we will throw a 502 (just like the 404 and 405 responses we get from Flask. * Since we found a Lambda function to invoke, we construct the Lambda Event from the request * Then Invoke the Lambda function (docker container) * We then transform the response or errors we get from the Invoke and return the data back to the caller Parameters ---------- kwargs dict Keyword Args that are passed to the function from Flask. This happens when we have path parameters Returns ------- Response object """ route = self._get_current_route(request) cors_headers = Cors.cors_to_headers(self.api.cors) method, _ = self.get_request_methods_endpoints(request) if method == "OPTIONS" and self.api.cors: headers = Headers(cors_headers) return self.service_response("", headers, 200) try: event = self._construct_event(request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(route.function_name, event, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: return ServiceErrorResponses.lambda_not_found_response() lambda_response, lambda_logs, _ = LambdaOutputParser.get_lambda_output( stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) try: (status_code, headers, body) = self._parse_lambda_output(lambda_response, self.api.binary_media_types, request) except LambdaResponseParseException: LOG.error(str(LambdaResponseParseException)) return ServiceErrorResponses.lambda_failure_response() return self.service_response(body, headers, status_code)
def _request_handler(self, **kwargs): """ We handle all requests to the host:port. The general flow of handling a request is as follows * Fetch request from the Flask Global state. This is where Flask places the request and is per thread so multiple requests are still handled correctly * Find the Lambda function to invoke by doing a look up based on the request.endpoint and method * If we don't find the function, we will throw a 502 (just like the 404 and 405 responses we get from Flask. * Since we found a Lambda function to invoke, we construct the Lambda Event from the request * Then Invoke the Lambda function (docker container) * We then transform the response or errors we get from the Invoke and return the data back to the caller Parameters ---------- kwargs dict Keyword Args that are passed to the function from Flask. This happens when we have path parameters Returns ------- Response object """ route = self._get_current_route(request) cors_headers = Cors.cors_to_headers(self.api.cors) method, endpoint = self.get_request_methods_endpoints(request) if method == "OPTIONS" and self.api.cors: headers = Headers(cors_headers) return self.service_response("", headers, 200) try: # the Lambda Event 2.0 is only used for the HTTP API gateway with defined payload format version equal 2.0 # or none, as the default value to be used is 2.0 # https://docs.aws.amazon.com/apigatewayv2/latest/api-reference/apis-apiid-integrations.html#apis-apiid-integrations-prop-createintegrationinput-payloadformatversion if route.event_type == Route.HTTP and route.payload_format_version in [ None, "2.0" ]: route_key = self._v2_route_key(method, endpoint, route.is_default_route) event = self._construct_v_2_0_event_http( request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables, route_key, ) else: event = self._construct_v_1_0_event( request, self.port, self.api.binary_media_types, self.api.stage_name, self.api.stage_variables) except UnicodeDecodeError: return ServiceErrorResponses.lambda_failure_response() stdout_stream = io.BytesIO() stdout_stream_writer = StreamWriter(stdout_stream, self.is_debugging) try: self.lambda_runner.invoke(route.function_name, event, stdout=stdout_stream_writer, stderr=self.stderr) except FunctionNotFound: return ServiceErrorResponses.lambda_not_found_response() lambda_response, lambda_logs, _ = LambdaOutputParser.get_lambda_output( stdout_stream) if self.stderr and lambda_logs: # Write the logs to stderr if available. self.stderr.write(lambda_logs) try: if route.event_type == Route.HTTP and ( not route.payload_format_version or route.payload_format_version == "2.0"): (status_code, headers, body) = self._parse_v2_payload_format_lambda_output( lambda_response, self.api.binary_media_types, request) else: (status_code, headers, body) = self._parse_v1_payload_format_lambda_output( lambda_response, self.api.binary_media_types, request) except LambdaResponseParseException as ex: LOG.error("Invalid lambda response received: %s", ex) return ServiceErrorResponses.lambda_failure_response() return self.service_response(body, headers, status_code)