def handler(self, event, context): """ An AWS Lambda function which parses specific API Gateway input into a WSGI request, feeds it to our WSGI app, procceses the response, and returns that back to the API Gateway. """ settings = self.settings # If in DEBUG mode, log all raw incoming events. if settings.DEBUG: logger.debug('Zappa Event: {}'.format(event)) # Set any API Gateway defined Stage Variables # as env vars if event.get('stageVariables'): for key in event['stageVariables'].keys(): os.environ[str(key)] = event['stageVariables'][key] # This is the result of a keep alive, recertify # or scheduled event. if event.get('detail-type') == 'Scheduled Event': whole_function = event['resources'][0].split('/')[-1].split('-')[-1] # This is a scheduled function. if '.' in whole_function: app_function = self.import_module_and_get_function(whole_function) # Execute the function! return self.run_function(app_function, event, context) # Else, let this execute as it were. # This is a direct command invocation. elif event.get('command', None): whole_function = event['command'] app_function = self.import_module_and_get_function(whole_function) result = self.run_function(app_function, event, context) print("Result of %s:" % whole_function) print(result) return result # This is a direct, raw python invocation. # It's _extremely_ important we don't allow this event source # to be overridden by unsanitized, non-admin user input. elif event.get('raw_command', None): raw_command = event['raw_command'] exec(raw_command) return # This is a Django management command invocation. elif event.get('manage', None): from django.core import management try: # Support both for tests from zappa.ext.django_zappa import get_django_wsgi except ImportError as e: # pragma: no cover from django_zappa_app import get_django_wsgi # Get the Django WSGI app from our extension # We don't actually need the function, # but we do need to do all of the required setup for it. app_function = get_django_wsgi(self.settings.DJANGO_SETTINGS) # Couldn't figure out how to get the value into stdout with StringIO.. # Read the log for now. :[] management.call_command(*event['manage'].split(' ')) return {} # This is an AWS-event triggered invocation. elif event.get('Records', None): records = event.get('Records') result = None whole_function = self.get_function_for_aws_event(records[0]) if whole_function: app_function = self.import_module_and_get_function(whole_function) result = self.run_function(app_function, event, context) logger.debug(result) else: logger.error("Cannot find a function to process the triggered event.") return result # this is an AWS-event triggered from Lex bot's intent elif event.get('bot'): result = None whole_function = self.get_function_from_bot_intent_trigger(event) if whole_function: app_function = self.import_module_and_get_function(whole_function) result = self.run_function(app_function, event, context) logger.debug(result) else: logger.error("Cannot find a function to process the triggered event.") return result # This is an API Gateway authorizer event elif event.get('type') == 'TOKEN': whole_function = self.settings.AUTHORIZER_FUNCTION if whole_function: app_function = self.import_module_and_get_function(whole_function) policy = self.run_function(app_function, event, context) return policy else: logger.error("Cannot find a function to process the authorization request.") raise Exception('Unauthorized') # This is an AWS Cognito Trigger Event elif event.get('triggerSource', None): triggerSource = event.get('triggerSource') whole_function = self.get_function_for_cognito_trigger(triggerSource) result = event if whole_function: app_function = self.import_module_and_get_function(whole_function) result = self.run_function(app_function, event, context) logger.debug(result) else: logger.error("Cannot find a function to handle cognito trigger {}".format(triggerSource)) return result # Normal web app flow try: # Timing time_start = datetime.datetime.now() # This is a normal HTTP request if event.get('httpMethod', None): script_name = '' is_elb_context = False headers = merge_headers(event) if event.get('requestContext', None) and event['requestContext'].get('elb', None): # Related: https://github.com/Miserlou/Zappa/issues/1715 # inputs/outputs for lambda loadbalancer # https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html is_elb_context = True # host is lower-case when forwarded from ELB host = headers.get('host') # TODO: pathParameters is a first-class citizen in apigateway but not available without # some parsing work for ELB (is this parameter used for anything?) event['pathParameters'] = '' else: if headers: host = headers.get('Host') else: host = None logger.debug('host found: [{}]'.format(host)) if host: if 'amazonaws.com' in host: logger.debug('amazonaws found in host') # The path provided in th event doesn't include the # stage, so we must tell Flask to include the API # stage in the url it calculates. See https://github.com/Miserlou/Zappa/issues/1014 script_name = '/' + settings.API_STAGE else: # This is a test request sent from the AWS console if settings.DOMAIN: # Assume the requests received will be on the specified # domain. No special handling is required pass else: # Assume the requests received will be to the # amazonaws.com endpoint, so tell Flask to include the # API stage script_name = '/' + settings.API_STAGE base_path = getattr(settings, 'BASE_PATH', None) # Create the environment for WSGI and handle the request environ = create_wsgi_request( event, script_name=script_name, base_path=base_path, trailing_slash=self.trailing_slash, binary_support=settings.BINARY_SUPPORT, context_header_mappings=settings.CONTEXT_HEADER_MAPPINGS ) # We are always on https on Lambda, so tell our wsgi app that. environ['HTTPS'] = 'on' environ['wsgi.url_scheme'] = 'https' environ['lambda.context'] = context environ['lambda.event'] = event # Execute the application with Response.from_app(self.wsgi_app, environ) as response: # This is the object we're going to return. # Pack the WSGI response into our special dictionary. zappa_returndict = dict() # Issue #1715: ALB support. ALB responses must always include # base64 encoding and status description if is_elb_context: zappa_returndict.setdefault('isBase64Encoded', False) zappa_returndict.setdefault('statusDescription', response.status) if response.data: if settings.BINARY_SUPPORT and \ not response.mimetype.startswith("text/") \ and response.mimetype != "application/json": zappa_returndict['body'] = base64.b64encode(response.data).decode('utf-8') zappa_returndict["isBase64Encoded"] = True else: zappa_returndict['body'] = response.get_data(as_text=True) zappa_returndict['statusCode'] = response.status_code if 'headers' in event: zappa_returndict['headers'] = {} for key, value in response.headers: zappa_returndict['headers'][key] = value if 'multiValueHeaders' in event: zappa_returndict['multiValueHeaders'] = {} for key, value in response.headers: zappa_returndict['multiValueHeaders'][key] = response.headers.getlist(key) # Calculate the total response time, # and log it in the Common Log format. time_end = datetime.datetime.now() delta = time_end - time_start response_time_ms = delta.total_seconds() * 1000 response.content = response.data common_log(environ, response, response_time=response_time_ms) return zappa_returndict except Exception as e: # pragma: no cover # Print statements are visible in the logs either way print(e) exc_info = sys.exc_info() message = ('An uncaught exception happened while servicing this request. ' 'You can investigate this with the `zappa tail` command.') # If we didn't even build an app_module, just raise. if not settings.DJANGO_SETTINGS: try: self.app_module except NameError as ne: message = 'Failed to import module: {}'.format(ne.message) # Call exception handler for unhandled exceptions exception_handler = self.settings.EXCEPTION_HANDLER self._process_exception(exception_handler=exception_handler, event=event, context=context, exception=e) # Return this unspecified exception as a 500, using template that API Gateway expects. content = collections.OrderedDict() content['statusCode'] = 500 body = {'message': message} if settings.DEBUG: # only include traceback if debug is on. body['traceback'] = traceback.format_exception(*exc_info) # traceback as a list for readability. content['body'] = json.dumps(str(body), sort_keys=True, indent=4) return content
def test_merge_headers_no_single_value(self): event = {'multiValueHeaders': {'a': ['c', 'd'], 'x': ['y', 'z', 'f']}} merged = merge_headers(event) self.assertEqual(merged['a'], 'c, d') self.assertEqual(merged['x'], 'y, z, f')
def test_merge_headers_no_single_value(self): event = {"multiValueHeaders": {"a": ["c", "d"], "x": ["y", "z", "f"]}} merged = merge_headers(event) self.assertEqual(merged["a"], "c, d") self.assertEqual(merged["x"], "y, z, f")
def test_merge_headers_no_multi_value(self): event = {'headers': {'a': 'b'}} merged = merge_headers(event) self.assertEqual(merged['a'], 'b')
def test_merge_headers_no_multi_value(self): event = {"headers": {"a": "b"}} merged = merge_headers(event) self.assertEqual(merged["a"], "b")