示例#1
0
    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
示例#2
0
 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')
示例#3
0
 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")
示例#4
0
    def test_merge_headers_no_multi_value(self):
        event = {'headers': {'a': 'b'}}

        merged = merge_headers(event)
        self.assertEqual(merged['a'], 'b')
示例#5
0
    def test_merge_headers_no_multi_value(self):
        event = {"headers": {"a": "b"}}

        merged = merge_headers(event)
        self.assertEqual(merged["a"], "b")