Esempio n. 1
0
    def test_wsgi_path_info(self):
        # Test no parameters (site.com/)
        event = {
            "body": {},
            "headers": {},
            "params": {},
            "method": "GET",
            "query": {}
        }

        request = create_wsgi_request(event, trailing_slash=True)
        self.assertEqual("/", request['PATH_INFO'])

        request = create_wsgi_request(event, trailing_slash=False)
        self.assertEqual("/", request['PATH_INFO'])

        # Test parameters (site.com/asdf1/asdf2 or site.com/asdf1/asdf2/)
        event = {
            "body": {},
            "headers": {},
            "params": {
                "parameter_1": "asdf1",
                "parameter_2": "asdf2",
            },
            "method": "GET",
            "query": {}
        }

        request = create_wsgi_request(event, trailing_slash=True)
        self.assertEqual("/asdf1/asdf2/", request['PATH_INFO'])

        request = create_wsgi_request(event, trailing_slash=False)
        self.assertEqual("/asdf1/asdf2", request['PATH_INFO'])
Esempio n. 2
0
 def test_should_allow_empty_query_params(self):
     event = {
         u'httpMethod': u'GET',
         u'queryStringParameters': {},
         u'multiValueQueryStringParameters': {},
         u'path': u'/v1/runs',
         u'params': {},
         u'body': {},
         u'headers': {
             u'Content-Type': u'application/json'
         },
         u'pathParameters': {
             u'proxy': 'v1/runs'
         },
         u'requestContext': {
             u"resourceId": u"123456",
             u"apiId": u"1234567890",
             u"resourcePath": u"/{proxy+}",
             u"httpMethod": u"POST",
             u"requestId": u"c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
             u"accountId": u"123456789012",
             u"identity": {
                 u"userAgent": u"Custom User Agent String",
                 u"cognitoIdentityPoolId": u"userpoolID",
                 u"cognitoIdentityId": u"myCognitoID",
                 u"sourceIp": u"127.0.0.1",
             },
             "stage": "prod"
         },
         u'query': {}
     }
     environ = create_wsgi_request(event,
                                   script_name='http://zappa.com/',
                                   trailing_slash=False)
     self.assertEqual(environ['QUERY_STRING'], u'')
Esempio n. 3
0
    def test_wsgi_event(self):

        event = {
            "body": "",
            "headers": {
                "Via": "1.1 e604e934e9195aaf3e36195adbcb3e18.cloudfront.net (CloudFront)",
                "Accept-Language": "en-US,en;q=0.5",
                "Accept-Encoding": "gzip",
                "CloudFront-Is-SmartTV-Viewer": "false",
                "CloudFront-Forwarded-Proto": "https",
                "X-Forwarded-For": "109.81.209.118, 216.137.58.43",
                "CloudFront-Viewer-Country": "CZ",
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                "X-Forwarded-Proto": "https",
                "X-Amz-Cf-Id": "LZeP_TZxBgkDt56slNUr_H9CHu1Us5cqhmRSswOh1_3dEGpks5uW-g==",
                "CloudFront-Is-Tablet-Viewer": "false",
                "X-Forwarded-Port": "443",
                "CloudFront-Is-Mobile-Viewer": "false",
                "CloudFront-Is-Desktop-Viewer": "true",
                "Content-Type": "application/json"
            },
            "params": {
                "parameter_1": "asdf1",
                "parameter_2": "asdf2",
            },
            "method": "POST",
            "query": {
                "dead": "beef"
            }
        }
        request = create_wsgi_request(event)
Esempio n. 4
0
    def test_wsgi_event(self):

        event = {
            "body": "",
            "headers": {
                "Via":
                "1.1 e604e934e9195aaf3e36195adbcb3e18.cloudfront.net (CloudFront)",
                "Accept-Language": "en-US,en;q=0.5",
                "Accept-Encoding": "gzip",
                "CloudFront-Is-SmartTV-Viewer": "false",
                "CloudFront-Forwarded-Proto": "https",
                "X-Forwarded-For": "109.81.209.118, 216.137.58.43",
                "CloudFront-Viewer-Country": "CZ",
                "Accept":
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                "X-Forwarded-Proto": "https",
                "X-Amz-Cf-Id":
                "LZeP_TZxBgkDt56slNUr_H9CHu1Us5cqhmRSswOh1_3dEGpks5uW-g==",
                "CloudFront-Is-Tablet-Viewer": "false",
                "X-Forwarded-Port": "443",
                "CloudFront-Is-Mobile-Viewer": "false",
                "CloudFront-Is-Desktop-Viewer": "true",
                "Content-Type": "application/json"
            },
            "params": {
                "parameter_1": "asdf1",
                "parameter_2": "asdf2",
            },
            "method": "POST",
            "query": {
                "dead": "beef"
            }
        }
        request = create_wsgi_request(event)
Esempio n. 5
0
 def test_should_allow_empty_query_params(self):
     event = {
         "httpMethod": "GET",
         "queryStringParameters": {},
         "multiValueQueryStringParameters": {},
         "path": "/v1/runs",
         "params": {},
         "body": {},
         "headers": {
             "Content-Type": "application/json"
         },
         "pathParameters": {
             "proxy": "v1/runs"
         },
         "requestContext": {
             "resourceId": "123456",
             "apiId": "1234567890",
             "resourcePath": "/{proxy+}",
             "httpMethod": "POST",
             "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
             "accountId": "123456789012",
             "identity": {
                 "userAgent": "Custom User Agent String",
                 "cognitoIdentityPoolId": "userpoolID",
                 "cognitoIdentityId": "myCognitoID",
                 "sourceIp": "127.0.0.1",
             },
             "stage": "prod",
         },
         "query": {},
     }
     environ = create_wsgi_request(event,
                                   script_name="http://zappa.com/",
                                   trailing_slash=False)
     self.assertEqual(environ["QUERY_STRING"], "")
Esempio n. 6
0
 def test_wsgi_path_info_unquoted(self):
     event = {
             "body": {},
             "headers": {},
             "pathParameters": {},
             "path": '/path%3A1', # encoded /path:1
             "httpMethod": "GET",
             "queryStringParameters": {},
             "requestContext": {}
         }
     request = create_wsgi_request(event, trailing_slash=True)
     self.assertEqual("/path:1", request['PATH_INFO'])
Esempio n. 7
0
    def test_wsgi_path_info(self):
        # Test no parameters (site.com/)
        event = {
            "body": {},
            "headers": {},
            "params": {},
            "method": "GET",
            "query": {}
        }

        request = create_wsgi_request(event, trailing_slash=True)
        self.assertEqual("/", request['PATH_INFO'])

        request = create_wsgi_request(event, trailing_slash=False)
        self.assertEqual("/", request['PATH_INFO'])

        # Test parameters (site.com/asdf1/asdf2 or site.com/asdf1/asdf2/)
        event = {
            "body": {},
            "headers": {},
            "params": {
                "parameter_1": "asdf1",
                "parameter_2": "asdf2",
            },
            "method": "GET",
            "query": {}
        }

        request = create_wsgi_request(event, trailing_slash=True)
        self.assertEqual("/asdf1/asdf2/", request['PATH_INFO'])

        request = create_wsgi_request(event, trailing_slash=False)
        self.assertEqual("/asdf1/asdf2", request['PATH_INFO'])

        request = create_wsgi_request(event,
                                      trailing_slash=False,
                                      script_name='asdf1')
        self.assertEqual("/asdf1/asdf2", request['PATH_INFO'])
Esempio n. 8
0
def lambda_handler(event, context):    

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zappa_settings")
    import django
    django.setup()

    # This is a normal HTTP request
    if event.get('method', None):
        handler = LambdaHandler()
        environ = create_wsgi_request(event)
        response = handler(environ)
        returnme = {'Content': response.content}
        for item in response.items():
            returnme[item[0]] = item[1]
        returnme['Status'] = response.status_code

        if response.status_code != 200:

            # So that we can always match on the first few characters
            # ex '{"AAA": "404'
            # returnme['AAA'] = str(response.status_code)
            # returnme['errorMessage'] = str(response.status_code)
            # returnme['errorType'] = str(response.status_code)
            # returnme['stackTrace'] = str(response.status_code)

            # error_json = json.dumps(returnme, sort_keys=True)
            # print "Error JSON:"
            # print error_json

            content = response.content
            content = "<!DOCTYPE html>" + str(response.status_code) + response.content

            b64_content = base64.b64encode(content)
            print b64_content

            raise Exception(b64_content)

        print returnme

        return returnme
    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}
Esempio n. 9
0
 def test_wsgi_logging(self):
     event = {
         "body": {},
         "headers": {},
         "params": {
             "parameter_1": "asdf1",
             "parameter_2": "asdf2",
         },
         "method": "GET",
         "query": {}
     }
     environ = create_wsgi_request(event, trailing_slash=False)
     response_tuple = collections.namedtuple('Response', ['status_code', 'content'])
     response = response_tuple(200, 'hello')
     le = common_log(environ, response, response_time=True)
     le = common_log(environ, response, response_time=False)
Esempio n. 10
0
 def test_wsgi_logging(self):
     event = {
         "body": {},
         "headers": {},
         "params": {
             "parameter_1": "asdf1",
             "parameter_2": "asdf2",
         },
         "method": "GET",
         "query": {}
     }
     environ = create_wsgi_request(event, trailing_slash=False)
     response_tuple = collections.namedtuple('Response', ['status_code', 'content'])
     response = response_tuple(200, 'hello')
     le = common_log(environ, response, response_time=True)
     le = common_log(environ, response, response_time=False)
Esempio n. 11
0
    def test_wsgi_logging(self):
        # event = {
        #     "body": {},
        #     "headers": {},
        #     "params": {
        #         "parameter_1": "asdf1",
        #         "parameter_2": "asdf2",
        #     },
        #     "httpMethod": "GET",
        #     "query": {}
        # }

        event = {u'body': None, u'resource': u'/{proxy+}', u'requestContext': {u'resourceId': u'dg451y', u'apiId': u'79gqbxq31c', u'resourcePath': u'/{proxy+}', u'httpMethod': u'GET', u'requestId': u'766df67f-8991-11e6-b2c4-d120fedb94e5', u'accountId': u'724336686645', u'identity': {u'apiKey': None, u'userArn': None, u'cognitoAuthenticationType': None, u'caller': None, u'userAgent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0', u'user': None, u'cognitoIdentityPoolId': None, u'cognitoIdentityId': None, u'cognitoAuthenticationProvider': None, u'sourceIp': u'96.90.37.59', u'accountId': None}, u'stage': u'devorr'}, u'queryStringParameters': None, u'httpMethod': u'GET', u'pathParameters': {u'proxy': u'asdf1/asdf2'}, u'headers': {u'Via': u'1.1 b2aeb492548a8a2d4036401355f928dd.cloudfront.net (CloudFront)', u'Accept-Language': u'en-US,en;q=0.5', u'Accept-Encoding': u'gzip, deflate, br', u'X-Forwarded-Port': u'443', u'X-Forwarded-For': u'96.90.37.59, 54.240.144.50', u'CloudFront-Viewer-Country': u'US', u'Accept': u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', u'Upgrade-Insecure-Requests': u'1', u'Host': u'79gqbxq31c.execute-api.us-east-1.amazonaws.com', u'X-Forwarded-Proto': u'https', u'X-Amz-Cf-Id': u'BBFP-RhGDrQGOzoCqjnfB2I_YzWt_dac9S5vBcSAEaoM4NfYhAQy7Q==', u'User-Agent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0', u'CloudFront-Forwarded-Proto': u'https'}, u'stageVariables': None, u'path': u'/asdf1/asdf2'}

        environ = create_wsgi_request(event, trailing_slash=False)
        response_tuple = collections.namedtuple('Response', ['status_code', 'content'])
        response = response_tuple(200, 'hello')
        le = common_log(environ, response, response_time=True)
        le = common_log(environ, response, response_time=False)
Esempio n. 12
0
 def test_wsgi_multipart(self):
     event = {
         u'body':
         u'LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS03Njk1MjI4NDg0Njc4MTc2NTgwNjMwOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9Im15c3RyaW5nIg0KDQpkZGQNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tNzY5NTIyODQ4NDY3ODE3NjU4MDYzMDk2MS0tDQo=',
         u'headers': {
             u'Content-Type':
             u'multipart/form-data; boundary=---------------------------7695228484678176580630961',
             u'Via':
             u'1.1 38205a04d96d60185e88658d3185ccee.cloudfront.net (CloudFront)',
             u'Accept-Language': u'en-US,en;q=0.5',
             u'Accept-Encoding': u'gzip, deflate, br',
             u'CloudFront-Is-SmartTV-Viewer': u'false',
             u'CloudFront-Forwarded-Proto': u'https',
             u'X-Forwarded-For': u'71.231.27.57, 104.246.180.51',
             u'CloudFront-Viewer-Country': u'US',
             u'Accept':
             u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
             u'User-Agent':
             u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0',
             u'Host': u'xo2z7zafjh.execute-api.us-east-1.amazonaws.com',
             u'X-Forwarded-Proto': u'https',
             u'Cookie': u'zappa=AQ4',
             u'CloudFront-Is-Tablet-Viewer': u'false',
             u'X-Forwarded-Port': u'443',
             u'Referer':
             u'https://xo8z7zafjh.execute-api.us-east-1.amazonaws.com/former/post',
             u'CloudFront-Is-Mobile-Viewer': u'false',
             u'X-Amz-Cf-Id':
             u'31zxcUcVyUxBOMk320yh5NOhihn5knqrlYQYpGGyOngKKwJb0J0BAQ==',
             u'CloudFront-Is-Desktop-Viewer': u'true'
         },
         u'params': {
             u'parameter_1': u'post'
         },
         u'method': u'POST',
         u'query': {}
     }
     environ = create_wsgi_request(event, trailing_slash=False)
     response_tuple = collections.namedtuple('Response',
                                             ['status_code', 'content'])
     response = response_tuple(200, 'hello')
Esempio n. 13
0
    def test_wsgi_without_body(self):
        event = {
            u'body': None,
            u'resource': u'/',
            u'requestContext': {
                u'resourceId': u'6cqjw9qu0b',
                u'apiId': u'9itr2lba55',
                u'resourcePath': u'/',
                u'httpMethod': u'POST',
                u'requestId': u'c17cb1bf-867c-11e6-b938-ed697406e3b5',
                u'accountId': u'724336686645',
                u'identity': {
                    u'apiKey': None,
                    u'userArn': None,
                    u'cognitoAuthenticationType': None,
                    u'caller': None,
                    u'userAgent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:48.0) Gecko/20100101 Firefox/48.0',
                    u'user': None,
                    u'cognitoIdentityPoolId': None,
                    u'cognitoIdentityId': None,
                    u'cognitoAuthenticationProvider': None,
                    u'sourceIp': u'50.191.225.98',
                    u'accountId': None,
                    },
                u'stage': u'devorr',
                },
            u'queryStringParameters': None,
            u'httpMethod': u'POST',
            u'pathParameters': None,
            u'headers': {u'Via': u'1.1 38205a04d96d60185e88658d3185ccee.cloudfront.net (CloudFront)', u'Accept-Language': u'en-US,en;q=0.5', u'Accept-Encoding': u'gzip, deflate, br', u'CloudFront-Is-SmartTV-Viewer': u'false', u'CloudFront-Forwarded-Proto': u'https', u'X-Forwarded-For': u'71.231.27.57, 104.246.180.51', u'CloudFront-Viewer-Country': u'US', u'Accept': u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', u'User-Agent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0', u'Host': u'xo2z7zafjh.execute-api.us-east-1.amazonaws.com', u'X-Forwarded-Proto': u'https', u'Cookie': u'zappa=AQ4', u'CloudFront-Is-Tablet-Viewer': u'false', u'X-Forwarded-Port': u'443', u'Referer': u'https://xo8z7zafjh.execute-api.us-east-1.amazonaws.com/former/post', u'CloudFront-Is-Mobile-Viewer': u'false', u'X-Amz-Cf-Id': u'31zxcUcVyUxBOMk320yh5NOhihn5knqrlYQYpGGyOngKKwJb0J0BAQ==', u'CloudFront-Is-Desktop-Viewer': u'true'},
            u'stageVariables': None,
            u'path': u'/',
            }

        environ = create_wsgi_request(event, trailing_slash=False)
        response_tuple = collections.namedtuple('Response', ['status_code', 'content'])
        response = response_tuple(200, 'hello')
Esempio n. 14
0
 def test_should_handle_multi_value_query_string_params(self):
     event = {
         'httpMethod': 'GET',
         'queryStringParameters': {},
         'multiValueQueryStringParameters': {
             'foo': [1, 2]
         },
         'path': '/v1/runs',
         'params': {},
         'body': {},
         'headers': {
             'Content-Type': 'application/json'
         },
         'pathParameters': {
             'proxy': 'v1/runs'
         },
         'requestContext': {
             "resourceId": "123456",
             "apiId": "1234567890",
             "resourcePath": "/{proxy+}",
             "httpMethod": "POST",
             "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
             "accountId": "123456789012",
             "identity": {
                 "userAgent": "Custom User Agent String",
                 "cognitoIdentityPoolId": "userpoolID",
                 "cognitoIdentityId": "myCognitoID",
                 "sourceIp": "127.0.0.1",
             },
             "stage": "prod"
         },
         'query': {}
     }
     environ = create_wsgi_request(event,
                                   script_name='http://zappa.com/',
                                   trailing_slash=False)
     self.assertEqual(environ['QUERY_STRING'], 'foo=1&foo=2')
Esempio n. 15
0
def lambda_handler(event, context=None, settings_name="zappa_settings"):  # NoQA
    """
    An AWS Lambda function which parses specific API Gateway input into a WSGI request.

    The request get fed it to Django, processes the Django response, and returns that
    back to the API Gateway.
    """
    time_start = datetime.datetime.now()

    # If in DEBUG mode, log all raw incoming events.
    if settings.DEBUG:
        logger.info('Zappa Event: {}'.format(event))

    # This is a normal HTTP request
    if event.get('method', None):

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME)

        # We are always on https on Lambda, so tell our wsgi app that.
        environ['HTTPS'] = 'on'
        environ['wsgi.url_scheme'] = 'https'

        wrap_me = get_wsgi_application()
        app = ZappaWSGIMiddleware(wrap_me)

        # Execute the application
        response = Response.from_app(app, environ)
        response.content = response.data

        # Prepare the special dictionary which will be returned to the API GW.
        returnme = {'Content': response.data}

        # Pack the WSGI response into our special dictionary.
        for (header_name, header_value) in response.headers:
            returnme[header_name] = header_value
        returnme['Status'] = response.status_code

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        exception = None
        if response.status_code in ERROR_CODES:
            content = u"<!DOCTYPE html>" + unicode(response.status_code) + unicode('<meta charset="utf-8" />') + response.data.encode('utf-8')
            b64_content = base64.b64encode(content)
            exception = (b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif 300 <= response.status_code < 400 and response.has_header('Location'):
            location = returnme['Location']
            location = '/' + location.replace("http://zappa/", "")
            exception = location

        # 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
        common_log(environ, response, response_time=response_time_ms)

        # Finally, return the response to API Gateway.
        if exception:
            raise Exception(exception)
        else:
            return returnme

    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}
    elif event.get('detail'):
        module, function = event['detail'].rsplit('.', 1)

        app_module = importlib.import_module(module)
        app_function = getattr(app_module, function)

        # Execute the function!
        app_function()
        return
    else:
        logger.error('Unhandled event: {}'.format(json.dumps(event)))
Esempio n. 16
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))

        # This is the result of a keep alive, recertify
        # or scheduled event.
        if event.get('detail-type') == u'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 overriden 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 invokation.
        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 API Gateway authorizer event
        elif event.get('type') == u'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')

        # Normal web app flow
        try:
            # Timing
            time_start = datetime.datetime.now()

            # This is a normal HTTP request
            if event.get('httpMethod', None):
                # If we just want to inspect this,
                # return this event instead of processing the request
                # https://your_api.aws-api.com/?event_echo=true
                # event_echo = getattr(settings, "EVENT_ECHO", True)
                # if event_echo and 'event_echo' in event['params'].values():
                #     return {'Content': str(event) + '\n' + str(context), 'Status': 200}

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(
                    event,
                    script_name=script_name,
                    trailing_slash=self.trailing_slash)

                # 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

                # Execute the application
                response = Response.from_app(self.wsgi_app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict()

                if response.data:
                    zappa_returndict['body'] = response.data

                zappa_returndict['statusCode'] = response.status_code
                zappa_returndict['headers'] = {}
                for key, value in response.headers:
                    zappa_returndict['headers'][key] = value

                # To ensure correct status codes, we need to
                # pack the response as a deterministic B64 string and raise it
                # as an error to match our APIGW regex.
                # The DOCTYPE ensures that the page still renders in the browser.
                exception = None  # this variable never used
                # if response.status_code in ERROR_CODES:
                #     content = collections.OrderedDict()
                #     content['http_status'] = response.status_code
                #     content['content'] = base64.b64encode(response.data.encode('utf-8'))
                #     exception = json.dumps(content)
                # # Internal are changed to become relative redirects
                # # so they still work for apps on raw APIGW and on a domain.
                # elif 300 <= response.status_code < 400 and hasattr(response, 'Location'):
                #     # Location is by default relative on Flask. Location is by default
                #     # absolute on Werkzeug. We can set autocorrect_location_header on
                #     # the response to False, but it doesn't work. We have to manually
                #     # remove the host part.
                #     location = response.location
                #     hostname = 'https://' + environ['HTTP_HOST']
                #     if location.startswith(hostname):
                #         exception = location[len(hostname):]
                #     else:
                #         exception = location

                # 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)

            # 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(body, sort_keys=True,
                                         indent=4).encode('utf-8')
            return content
Esempio n. 17
0
    def test_wsgi_map_context_headers_handling(self):

        # Validate a single context value mapping is translated into a HTTP header
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u'user1'
                },

            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False,
                                      context_header_mappings={'PrincipalId': 'authorizer.principalId'})
        self.assertEqual(environ['HTTP_PRINCIPALID'], u'user1')

        # Validate multiple mappings with an invalid mapping
        # Invalid mapping should be ignored
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u"resourceId": u"123456",
                u"apiId": u"1234567890",
                u"resourcePath": u"/{proxy+}",
                u"httpMethod": u"POST",
                u"requestId": u"c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
                u"accountId": u"123456789012",
                u"identity": {
                    u"userAgent": u"Custom User Agent String",
                    u"cognitoIdentityPoolId": u"userpoolID",
                    u"cognitoIdentityId": u"myCognitoID",
                    u"sourceIp": u"127.0.0.1",
                },
                "stage": "prod"
            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False,
                                      context_header_mappings={'CognitoIdentityID': 'identity.cognitoIdentityId',
                                                               'APIStage': 'stage',
                                                               'InvalidValue': 'identity.cognitoAuthenticationType',
                                                               'OtherInvalid': 'nothinghere'})
        self.assertEqual(environ['HTTP_COGNITOIDENTITYID'], u'myCognitoID')
        self.assertEqual(environ['HTTP_APISTAGE'], u'prod')
        self.assertNotIn('HTTP_INVALIDVALUE', environ)
        self.assertNotIn('HTTP_OTHERINVALID', environ)
Esempio n. 18
0
def lambda_handler(event, context):
    """
    An AWS Lambda function which parses specific API Gateway input into a WSGI request,
    feeds it to Django, procceses the Django response, and returns that 
    back to the API Gateway.

    """

    # Django requires settings and an explicit call to setup()
    # if being used from inside a python context.
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zappa_settings")
    import django
    django.setup()
    from django.conf import settings

    # This is a normal HTTP request
    if event.get('method', None):

        # If we just want to inspect this,
        # return this event instead of processing the request
        # https://your_api.aws-api.com/?event_echo=true
        event_echo = getattr(settings, "EVENT_ECHO", True)
        if event_echo:
            if 'event_echo' in event['params'].values():
                return {
                    'Content': str(event) + '\n' + str(context),
                    'Status': 200
                }

        # This doesn't matter, but Django's handler requires it.
        def start(a, b):
            return

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME)
        handler = WSGIHandler()
        response = handler(environ, start)

        # Prepare the special dictionary which will be returned to the API GW.
        returnme = {'Content': response.content}

        # Pack the WSGI response into our special dictionary.
        for item in response.items():
            returnme[item[0]] = item[1]
        returnme['Status'] = response.status_code

        # Parse the WSGI Cookie and pack it.
        cookie = response.cookies.output()
        if ': ' in cookie:
            returnme['Set-Cookie'] = response.cookies.output().split(': ')[1]

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        if response.status_code in [400, 401, 403, 500]:
            content = response.content
            content = "<!DOCTYPE html>" + str(
                response.status_code) + response.content
            b64_content = base64.b64encode(content)
            raise Exception(b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif response.status_code in [301, 302]:
            location = returnme['Location']
            location = '/' + location.replace("http://zappa/", "")
            raise Exception(location)
        else:
            return returnme

    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}
Esempio n. 19
0
    def test_wsgi_map_context_headers_handling(self):

        # Validate a single context value mapping is translated into a HTTP header
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {
                "authorizer": {
                    "principalId": "user1"
                },
            },
            "query": {},
        }

        environ = create_wsgi_request(
            event,
            script_name="http://zappa.com/",
            trailing_slash=False,
            context_header_mappings={"PrincipalId": "authorizer.principalId"},
        )
        self.assertEqual(environ["HTTP_PRINCIPALID"], "user1")

        # Validate multiple mappings with an invalid mapping
        # Invalid mapping should be ignored
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {
                "resourceId": "123456",
                "apiId": "1234567890",
                "resourcePath": "/{proxy+}",
                "httpMethod": "POST",
                "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
                "accountId": "123456789012",
                "identity": {
                    "userAgent": "Custom User Agent String",
                    "cognitoIdentityPoolId": "userpoolID",
                    "cognitoIdentityId": "myCognitoID",
                    "sourceIp": "127.0.0.1",
                },
                "stage": "prod",
            },
            "query": {},
        }

        environ = create_wsgi_request(
            event,
            script_name="http://zappa.com/",
            trailing_slash=False,
            context_header_mappings={
                "CognitoIdentityID": "identity.cognitoIdentityId",
                "APIStage": "stage",
                "InvalidValue": "identity.cognitoAuthenticationType",
                "OtherInvalid": "nothinghere",
            },
        )
        self.assertEqual(environ["HTTP_COGNITOIDENTITYID"], "myCognitoID")
        self.assertEqual(environ["HTTP_APISTAGE"], "prod")
        self.assertNotIn("HTTP_INVALIDVALUE", environ)
        self.assertNotIn("HTTP_OTHERINVALID", environ)
Esempio n. 20
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.

        """

        # This is the result of a keep alive
        # or scheduled event.
        if event.get('detail-type') == u'Scheduled Event':

            whole_function = event['resources'][0].split('/')[-1]
            module, function = whole_function.rsplit('.', 1)

            app_module = importlib.import_module(module)
            app_function = getattr(app_module, function)

            # Execute the function!
            app_function()
            return

        # This is a direct command invocation.
        elif event.get('command', None):

            whole_function = event['command']
            module, function = whole_function.rsplit('.', 1)
            app_module = importlib.import_module(module)
            app_function = getattr(app_module, function)
            result = app_function()
            print("Result of %s:" % whole_function)

            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 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 {}

        try:
            # Timing
            time_start = datetime.datetime.now()

            settings = self.settings

            # If in DEBUG mode, log all raw incoming events.
            if settings.DEBUG:
                logger.debug('Zappa Event: {}'.format(event))

            # Custom log level
            if settings.LOG_LEVEL:
                level = logging.getLevelName(settings.LOG_LEVEL)
                logger.setLevel(level)

            # Django gets special treatment.
            if not settings.DJANGO_SETTINGS:
                # The app module
                app_module = importlib.import_module(settings.APP_MODULE)

                # The application
                app_function = getattr(app_module, settings.APP_FUNCTION)
                trailing_slash = False
            else:

                try: # Support both for tests
                    from zappa.ext.django 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
                app_function = get_django_wsgi(settings.DJANGO_SETTINGS)
                trailing_slash = True

            app = ZappaWSGIMiddleware(app_function)

            # This is a normal HTTP request
            if event.get('method', None):
                # If we just want to inspect this,
                # return this event instead of processing the request
                # https://your_api.aws-api.com/?event_echo=true
                event_echo = getattr(settings, "EVENT_ECHO", True)
                if event_echo and 'event_echo' in event['params'].values():
                    return {'Content': str(event) + '\n' + str(context), 'Status': 200}

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(event,
                                                script_name=script_name,
                                                trailing_slash=trailing_slash
                                            )

                # We are always on https on Lambda, so tell our wsgi app that.
                environ['HTTPS'] = 'on'
                environ['wsgi.url_scheme'] = 'https'

                # Execute the application
                response = Response.from_app(app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict(response.headers)

                if 'Content' not in zappa_returndict and response.data:
                    zappa_returndict['Content'] = response.data

                zappa_returndict['Status'] = response.status_code

                # To ensure correct status codes, we need to
                # pack the response as a deterministic B64 string and raise it
                # as an error to match our APIGW regex.
                # The DOCTYPE ensures that the page still renders in the browser.
                exception = None
                if response.status_code in ERROR_CODES:
                    content = u"<!DOCTYPE html>" + unicode(response.status_code) + unicode('<meta charset="utf-8" />') + response.data.encode('utf-8')
                    exception = base64.b64encode(content)
                # Internal are changed to become relative redirects
                # so they still work for apps on raw APIGW and on a domain.
                elif 300 <= response.status_code < 400 and hasattr(response, 'Location'):
                    # Location is by default relative on Flask. Location is by default
                    # absolute on Werkzeug. We can set autocorrect_location_header on
                    # the response to False, but it doesn't work. We have to manually
                    # remove the host part.
                    location = response.location
                    hostname = 'https://' + environ['HTTP_HOST']
                    if location.startswith(hostname):
                        exception = location[len(hostname):]

                # 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)

                # Finally, return the response to API Gateway.
                if exception: # pragma: no cover
                    raise LambdaException(exception)
                else:
                    return zappa_returndict
        except LambdaException as e: # pragma: no cover
            raise e
        except Exception as e: # pragma: no cover

            # Print statements are visible in the logs either way
            print(e)

            # If we didn't even build an app_module, just raise.
            if not settings.DJANGO_SETTINGS:
                try:
                    app_module
                except NameError:
                    raise e

            # Print the error to the browser upon failure?
            if settings.DEBUG:
                # Return this unspecified exception as a 500.
                content = "<!DOCTYPE html>500. From Zappa: <pre>" + str(e) + "</pre><br /><pre>" + traceback.format_exc().replace('\n', '<br />') + "</pre>"
                exception = base64.b64encode(content)
                raise Exception(exception)
            else:
                raise e
Esempio n. 21
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))

        # This is the result of a keep alive, recertify
        # or scheduled event.
        if event.get('detail-type') == u'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 overriden 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 invokation.
        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 API Gateway authorizer event
        elif event.get('type') == u'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')

        # Normal web app flow
        try:
            # Timing
            time_start = datetime.datetime.now()

            # This is a normal HTTP request
            if event.get('httpMethod', None):

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(
                    event,
                    script_name=script_name,
                    trailing_slash=self.trailing_slash,
                    binary_support=settings.BINARY_SUPPORT
                )

                # 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

                # Execute the application
                response = Response.from_app(self.wsgi_app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict()

                if response.data:
                    if settings.BINARY_SUPPORT:
                        if not response.mimetype.startswith("text/") \
                            or response.mimetype != "application/json":
                                zappa_returndict['body'] = base64.b64encode(response.data)
                                zappa_returndict["isBase64Encoded"] = "true"
                        else:
                            zappa_returndict['body'] = response.data
                    else:
                        zappa_returndict['body'] = response.data

                zappa_returndict['statusCode'] = response.status_code
                zappa_returndict['headers'] = {}
                for key, value in response.headers:
                    zappa_returndict['headers'][key] = value

                # 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)

            # 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(body, sort_keys=True, indent=4).encode('utf-8')
            return content
Esempio n. 22
0
def lambda_handler(event, context):
    """
    An AWS Lambda function which parses specific API Gateway input into a WSGI request,
    feeds it to Django, procceses the Django response, and returns that 
    back to the API Gateway.

    """    

    # Django requires settings and an explicit call to setup()
    # if being used from inside a python context.
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zappa_settings")
    import django
    django.setup()
    from django.conf import settings

    # This is a normal HTTP request
    if event.get('method', None):

        # If we just want to inspect this,
        # return this event instead of processing the request
        # https://your_api.aws-api.com/?event_echo=true
        event_echo = getattr(settings, "EVENT_ECHO", True)
        if event_echo:
            if 'event_echo' in event['params'].values():
                return {'Content': str(event) + '\n' + str(context), 'Status': 200}

        # This doesn't matter, but Django's handler requires it.
        def start(a, b):
            return

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME)
        handler = WSGIHandler()
        response = handler(environ, start)

        # Prepare the special dictionary which will be returned to the API GW.
        returnme = {'Content': response.content}

        # Pack the WSGI response into our special dictionary.
        for item in response.items():
            returnme[item[0]] = item[1]
        returnme['Status'] = response.status_code

        # Parse the WSGI Cookie and pack it.
        cookie = response.cookies.output()
        if ': ' in cookie:
            returnme['Set-Cookie'] = response.cookies.output().split(': ')[1]

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        if response.status_code in [400, 401, 403, 500]:
            content = response.content
            content = "<!DOCTYPE html>" + str(response.status_code) + response.content
            b64_content = base64.b64encode(content)
            raise Exception(b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif response.status_code in [301, 302]:
            location = returnme['Location']
            location = '/' + location.replace("http://zappa/", "")
            raise Exception(location)
        else:
            return returnme

    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}
Esempio n. 23
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:
            print('Zappa Event: {}'.format(event))
            logger.debug('Zappa Event: {}'.format(event))

        # Custom log level
        if settings.LOG_LEVEL:
            level = logging.getLevelName(settings.LOG_LEVEL)
            logger.setLevel(level)

        # This is the result of a keep alive
        # or scheduled event.
        if event.get('detail-type') == u'Scheduled Event':

            whole_function = event['resources'][0].split('/')[-1].split(
                '-')[-1]

            # This is a scheduled, non-keep-alive function.
            # This is not the best way to do this but it'll do.
            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 Django management command invocation.
        elif event.get('manage', None):

            from django.core import management

            try:  # Support both for tests
                from zappa.ext.django 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 invokation.
        elif event.get('Records', None):

            records = event.get('Records')
            event_types = ['dynamodb', 'kinesis', 's3', 'sns', 'events']

            for record in records:
                for event_type in event_types:
                    if record.has_key(event_type):

                        whole_function = record[event_type]['configurationId']
                        app_function = self.import_module_and_get_function(
                            whole_function)
                        result = self.run_function(app_function, event,
                                                   context)
                        print(result)

            return result

        try:
            # Timing
            time_start = datetime.datetime.now()

            # Django gets special treatment.
            if not settings.DJANGO_SETTINGS:
                # The app module
                app_module = importlib.import_module(settings.APP_MODULE)

                # The application
                app_function = getattr(app_module, settings.APP_FUNCTION)
                trailing_slash = False
            else:

                try:  # Support both for tests
                    from zappa.ext.django 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
                app_function = get_django_wsgi(settings.DJANGO_SETTINGS)
                trailing_slash = True

            app = ZappaWSGIMiddleware(app_function)

            # This is a normal HTTP request
            if event.get('method', None):
                # If we just want to inspect this,
                # return this event instead of processing the request
                # https://your_api.aws-api.com/?event_echo=true
                event_echo = getattr(settings, "EVENT_ECHO", True)
                if event_echo and 'event_echo' in event['params'].values():
                    return {
                        'Content': str(event) + '\n' + str(context),
                        'Status': 200
                    }

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(event,
                                              script_name=script_name,
                                              trailing_slash=trailing_slash)

                # 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

                # Execute the application
                response = Response.from_app(app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict(response.headers)

                if 'Content' not in zappa_returndict and response.data:
                    zappa_returndict['Content'] = response.data

                zappa_returndict['Status'] = response.status_code

                # To ensure correct status codes, we need to
                # pack the response as a deterministic B64 string and raise it
                # as an error to match our APIGW regex.
                # The DOCTYPE ensures that the page still renders in the browser.
                exception = None
                if response.status_code in ERROR_CODES:
                    content = u"<!DOCTYPE html>" + unicode(
                        response.status_code) + unicode(
                            '<meta charset="utf-8" />') + response.data.encode(
                                'utf-8')
                    exception = base64.b64encode(content)
                # Internal are changed to become relative redirects
                # so they still work for apps on raw APIGW and on a domain.
                elif 300 <= response.status_code < 400 and hasattr(
                        response, 'Location'):
                    # Location is by default relative on Flask. Location is by default
                    # absolute on Werkzeug. We can set autocorrect_location_header on
                    # the response to False, but it doesn't work. We have to manually
                    # remove the host part.
                    location = response.location
                    hostname = 'https://' + environ['HTTP_HOST']
                    if location.startswith(hostname):
                        exception = location[len(hostname):]

                # 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)

                # Finally, return the response to API Gateway.
                if exception:  # pragma: no cover
                    raise LambdaException(exception)
                else:
                    return zappa_returndict
        except LambdaException as e:  # pragma: no cover
            raise e
        except Exception as e:  # pragma: no cover

            # Print statements are visible in the logs either way
            print(e)

            # If we didn't even build an app_module, just raise.
            if not settings.DJANGO_SETTINGS:
                try:
                    app_module
                except NameError:
                    raise e

            # Print the error to the browser upon failure?
            if settings.DEBUG:
                # Return this unspecified exception as a 500.
                content = "<!DOCTYPE html>500. From Zappa: <pre>" + str(
                    e) + "</pre><br /><pre>" + traceback.format_exc().replace(
                        '\n', '<br />') + "</pre>"
                exception = base64.b64encode(content)
                raise Exception(exception)
            else:
                raise e
Esempio n. 24
0
    def test_wsgi_map_context_headers_handling(self):

        # Validate a single context value mapping is translated into a HTTP header
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u'user1'
                },
            },
            u'query': {}
        }

        environ = create_wsgi_request(
            event,
            script_name='http://zappa.com/',
            trailing_slash=False,
            context_header_mappings={'PrincipalId': 'authorizer.principalId'})
        self.assertEqual(environ['HTTP_PRINCIPALID'], u'user1')

        # Validate multiple mappings with an invalid mapping
        # Invalid mapping should be ignored
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u"resourceId": u"123456",
                u"apiId": u"1234567890",
                u"resourcePath": u"/{proxy+}",
                u"httpMethod": u"POST",
                u"requestId": u"c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
                u"accountId": u"123456789012",
                u"identity": {
                    u"userAgent": u"Custom User Agent String",
                    u"cognitoIdentityPoolId": u"userpoolID",
                    u"cognitoIdentityId": u"myCognitoID",
                    u"sourceIp": u"127.0.0.1",
                },
                "stage": "prod"
            },
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False,
                                      context_header_mappings={
                                          'CognitoIdentityID':
                                          'identity.cognitoIdentityId',
                                          'APIStage': 'stage',
                                          'InvalidValue':
                                          'identity.cognitoAuthenticationType',
                                          'OtherInvalid': 'nothinghere'
                                      })
        self.assertEqual(environ['HTTP_COGNITOIDENTITYID'], u'myCognitoID')
        self.assertEqual(environ['HTTP_APISTAGE'], u'prod')
        self.assertNotIn('HTTP_INVALIDVALUE', environ)
        self.assertNotIn('HTTP_OTHERINVALID', environ)
Esempio n. 25
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, processes 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

        # This is a CloudWatch event
        # Related: https://github.com/Miserlou/Zappa/issues/1924
        elif event.get("awslogs", None):
            result = None
            whole_function = "{}.{}".format(settings.APP_MODULE, settings.APP_FUNCTION)
            app_function = self.import_module_and_get_function(whole_function)
            if app_function:
                result = self.run_function(app_function, event, context)
                logger.debug("Result of %s:" % whole_function)
                logger.debug(result)
            else:
                logger.error("Cannot find a function to process the triggered event.")
            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
Esempio n. 26
0
    def test_wsgi_event(self):

        ## This is a pre-proxy+ event
        # event = {
        #     "body": "",
        #     "headers": {
        #         "Via": "1.1 e604e934e9195aaf3e36195adbcb3e18.cloudfront.net (CloudFront)",
        #         "Accept-Language": "en-US,en;q=0.5",
        #         "Accept-Encoding": "gzip",
        #         "CloudFront-Is-SmartTV-Viewer": "false",
        #         "CloudFront-Forwarded-Proto": "https",
        #         "X-Forwarded-For": "109.81.209.118, 216.137.58.43",
        #         "CloudFront-Viewer-Country": "CZ",
        #         "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        #         "X-Forwarded-Proto": "https",
        #         "X-Amz-Cf-Id": "LZeP_TZxBgkDt56slNUr_H9CHu1Us5cqhmRSswOh1_3dEGpks5uW-g==",
        #         "CloudFront-Is-Tablet-Viewer": "false",
        #         "X-Forwarded-Port": "443",
        #         "CloudFront-Is-Mobile-Viewer": "false",
        #         "CloudFront-Is-Desktop-Viewer": "true",
        #         "Content-Type": "application/json"
        #     },
        #     "params": {
        #         "parameter_1": "asdf1",
        #         "parameter_2": "asdf2",
        #     },
        #     "method": "POST",
        #     "query": {
        #         "dead": "beef"
        #     }
        # }

        event = {
            u'body': None,
            u'resource': u'/',
            u'requestContext': {
                u'resourceId': u'6cqjw9qu0b',
                u'apiId': u'9itr2lba55',
                u'resourcePath': u'/',
                u'httpMethod': u'GET',
                u'requestId': u'c17cb1bf-867c-11e6-b938-ed697406e3b5',
                u'accountId': u'724336686645',
                u'identity': {
                    u'apiKey': None,
                    u'userArn': None,
                    u'cognitoAuthenticationType': None,
                    u'caller': None,
                    u'userAgent':
                    u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:48.0) Gecko/20100101 Firefox/48.0',
                    u'user': None,
                    u'cognitoIdentityPoolId': None,
                    u'cognitoIdentityId': None,
                    u'cognitoAuthenticationProvider': None,
                    u'sourceIp': u'50.191.225.98',
                    u'accountId': None,
                },
                u'stage': u'devorr',
            },
            u'queryStringParameters': None,
            u'httpMethod': u'GET',
            u'pathParameters': None,
            u'headers': {
                u'Via':
                u'1.1 6801928d54163af944bf854db8d5520e.cloudfront.net (CloudFront)',
                u'Accept-Language': u'en-US,en;q=0.5',
                u'Accept-Encoding': u'gzip, deflate, br',
                u'CloudFront-Is-SmartTV-Viewer': u'false',
                u'CloudFront-Forwarded-Proto': u'https',
                u'X-Forwarded-For': u'50.191.225.98, 204.246.168.101',
                u'CloudFront-Viewer-Country': u'US',
                u'Accept':
                u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                u'Upgrade-Insecure-Requests': u'1',
                u'Host': u'9itr2lba55.execute-api.us-east-1.amazonaws.com',
                u'X-Forwarded-Proto': u'https',
                u'X-Amz-Cf-Id':
                u'qgNdqKT0_3RMttu5KjUdnvHI3OKm1BWF8mGD2lX8_rVrJQhhp-MLDw==',
                u'CloudFront-Is-Tablet-Viewer': u'false',
                u'X-Forwarded-Port': u'443',
                u'User-Agent':
                u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:48.0) Gecko/20100101 Firefox/48.0',
                u'CloudFront-Is-Mobile-Viewer': u'false',
                u'CloudFront-Is-Desktop-Viewer': u'true',
            },
            u'stageVariables': None,
            u'path': u'/',
        }

        request = create_wsgi_request(event)
Esempio n. 27
0
    def test_wsgi_authorizer_handling(self):
        # With user
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {
                "authorizer": {
                    "principalId": "user1"
                }
            },
            "query": {},
        }

        environ = create_wsgi_request(event,
                                      script_name="http://zappa.com/",
                                      trailing_slash=False)
        self.assertEqual(environ["REMOTE_USER"], "user1")

        # With empty authorizer, should not include REMOTE_USER
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {
                "authorizer": {
                    "principalId": ""
                }
            },
            "query": {},
        }

        environ = create_wsgi_request(event,
                                      script_name="http://zappa.com/",
                                      trailing_slash=False)
        user = environ.get("REMOTE_USER", "no_user")
        self.assertEqual(user, "no_user")

        # With missing authorizer, should not include REMOTE_USER
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {},
            "query": {},
        }

        environ = create_wsgi_request(event,
                                      script_name="http://zappa.com/",
                                      trailing_slash=False)
        user = environ.get("REMOTE_USER", "no_user")
        self.assertEqual(user, "no_user")

        # With empty authorizer, should not include REMOTE_USER
        event = {
            "httpMethod": "GET",
            "queryStringParameters": None,
            "path": "/v1/runs",
            "params": {},
            "body": {},
            "headers": {
                "Content-Type": "application/json"
            },
            "pathParameters": {
                "proxy": "v1/runs"
            },
            "requestContext": {
                "authorizer": {}
            },
            "query": {},
        }

        environ = create_wsgi_request(event,
                                      script_name="http://zappa.com/",
                                      trailing_slash=False)
        user = environ.get("REMOTE_USER", "no_user")
        self.assertEqual(user, "no_user")
Esempio n. 28
0
def lambda_handler(event, context, settings_name="zappa_settings"):
    """ An AWS Lambda function which parses specific API Gateway input into a
    WSGI request, feeds it to Flask, procceses the Flask response, and returns
    that back to the API Gateway.
    """
    # Loading settings from a python module
    settings = importlib.import_module(settings_name)

    # The flask-app module
    app_module = importlib.import_module(settings.APP_MODULE)

    # The flask-app
    app = getattr(app_module, settings.APP_OBJECT)
    app.config.from_object('zappa_settings')

    app.wsgi_app = ZappaWSGIMiddleware(app.wsgi_app)

    # This is a normal HTTP request
    if event.get('method', None):
        # If we just want to inspect this,
        # return this event instead of processing the request
        # https://your_api.aws-api.com/?event_echo=true
        event_echo = getattr(settings, "EVENT_ECHO", True)
        if event_echo:
            if 'event_echo' in list(event['params'].values()):
                return {'Content': str(event) + '\n' + str(context), 'Status': 200}

        # TODO: Enable Let's Encrypt
        # # If Let's Encrypt is defined in the settings,
        # # and the path is your.domain.com/.well-known/acme-challenge/{{lets_encrypt_challenge_content}},
        # # return a 200 of lets_encrypt_challenge_content.
        # lets_encrypt_challenge_path = getattr(settings, "LETS_ENCRYPT_CHALLENGE_PATH", None)
        # lets_encrypt_challenge_content = getattr(settings, "LETS_ENCRYPT_CHALLENGE_CONTENT", None)
        # if lets_encrypt_challenge_path:
        #     if len(event['params']) == 3:
        #         if event['params']['parameter_1'] == '.well-known' and \
        #             event['params']['parameter_2'] == 'acme-challenge' and \
        #             event['params']['parameter_3'] == lets_encrypt_challenge_path:
        #                 return {'Content': lets_encrypt_challenge_content, 'Status': 200}

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME,
                                      trailing_slash=False)

        # We are always on https on Lambda, so tell our wsgi app that.
        environ['wsgi.url_scheme'] = 'https'

        response = Response.from_app(app, environ)

        # This doesn't work. It should probably be set right after creation, not
        # at such a late stage.
        # response.autocorrect_location_header = False

        zappa_returndict = dict()

        if response.data:
            zappa_returndict['Content'] = response.data

        # Pack the WSGI response into our special dictionary.
        for (header_name, header_value) in response.headers:
            zappa_returndict[header_name] = header_value
        zappa_returndict['Status'] = response.status_code

        # TODO: No clue how to handle the flask-equivalent of this. Or is this
        # something entirely specified by the middleware?
        # # Parse the WSGI Cookie and pack it.
        # cookie = response.cookies.output()
        # if ': ' in cookie:
        #     zappa_returndict['Set-Cookie'] = response.cookies.output().split(': ')[1]

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        if response.status_code in [400, 401, 403, 404, 500]:
            content = "<!DOCTYPE html>" + str(response.status_code) + response.data
            b64_content = base64.b64encode(content)
            raise Exception(b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif response.status_code in [301, 302]:
            # Location is by default relative on Flask. Location is by default
            # absolute on Werkzeug. We can set autocorrect_location_header on
            # the response to False, but it doesn't work. We have to manually
            # remove the host part.
            location = response.location
            hostname = 'https://' + environ['HTTP_HOST']
            if location.startswith(hostname):
                location = location[len(hostname):]
            raise Exception(location)
        else:
            return zappa_returndict
Esempio n. 29
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:
            print('Zappa Event: {}'.format(event))
            logger.debug('Zappa Event: {}'.format(event))

        # Custom log level
        if settings.LOG_LEVEL:
            level = logging.getLevelName(settings.LOG_LEVEL)
            logger.setLevel(level)

        # This is the result of a keep alive, recertify
        # or scheduled event.
        if event.get('detail-type') == u'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 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 invokation.
        elif event.get('Records', None):

            records = event.get('Records')
            result = None
            for record in records:
                whole_function = self.get_function_for_aws_event(record)
                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

        try:
            # Timing
            time_start = datetime.datetime.now()

            # Django gets special treatment.
            if not settings.DJANGO_SETTINGS:
                # The app module
                app_module = importlib.import_module(settings.APP_MODULE)

                # The application
                app_function = getattr(app_module, settings.APP_FUNCTION)
                trailing_slash = False
            else:

                try:  # Support both for tests
                    from zappa.ext.django 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
                app_function = get_django_wsgi(settings.DJANGO_SETTINGS)
                trailing_slash = True

            app = ZappaWSGIMiddleware(app_function)

            # This is a normal HTTP request
            if event.get('method', None):
                # If we just want to inspect this,
                # return this event instead of processing the request
                # https://your_api.aws-api.com/?event_echo=true
                event_echo = getattr(settings, "EVENT_ECHO", True)
                if event_echo and 'event_echo' in event['params'].values():
                    return {'Content': str(event) + '\n' + str(context), 'Status': 200}

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(
                    event,
                    script_name=script_name,
                    trailing_slash=trailing_slash
                )

                # 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

                # Execute the application
                response = Response.from_app(app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict(response.headers)

                if 'Content' not in zappa_returndict and response.data:
                    zappa_returndict['Content'] = response.data

                zappa_returndict['Status'] = response.status_code

                # To ensure correct status codes, we need to
                # pack the response as a deterministic B64 string and raise it
                # as an error to match our APIGW regex.
                # The DOCTYPE ensures that the page still renders in the browser.
                exception = None
                if response.status_code in ERROR_CODES:
                    content = collections.OrderedDict()
                    content['http_status'] = response.status_code
                    content['content'] = base64.b64encode(response.data.encode('utf-8'))
                    exception = json.dumps(content)
                # Internal are changed to become relative redirects
                # so they still work for apps on raw APIGW and on a domain.
                elif 300 <= response.status_code < 400 and hasattr(response, 'Location'):
                    # Location is by default relative on Flask. Location is by default
                    # absolute on Werkzeug. We can set autocorrect_location_header on
                    # the response to False, but it doesn't work. We have to manually
                    # remove the host part.
                    location = response.location
                    hostname = 'https://' + environ['HTTP_HOST']
                    if location.startswith(hostname):
                        exception = location[len(hostname):]

                # 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)

                # Finally, return the response to API Gateway.
                if exception:  # pragma: no cover
                    raise WSGIException(exception)
                else:
                    return zappa_returndict
        except WSGIException as e:  # pragma: no cover
            raise e
        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.'

            # If we didn't even build an app_module, just raise.
            if not settings.DJANGO_SETTINGS:
                try:
                    app_module
                except NameError as ne:
                    message = 'Failed to import module: {}'.format(ne.message)

            # Return this unspecified exception as a 500, using template that API Gateway expects.
            content = collections.OrderedDict()
            content['http_status'] = 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['content'] = base64.b64encode(json.dumps(body, sort_keys=True, indent=4).encode('utf-8'))
            exception = json.dumps(content)
            raise UncaughtWSGIException(exception, original=e), None, exc_info[2]  # Keep original traceback.
Esempio n. 30
0
    def test_wsgi_middleware_realcall(self):
        print("1: Setting the cookies.")
        event = {
            u'method': u'POST',
            u'params': {
                u'parameter_1': u'set_cookie'
            },
            u'body': u'foo=xxx&bar=yyy',
            u'headers': {},
            u'query': {}
        }

        def set_cookies(environ, start_response):
            status = '200 OK'
            print environ
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456'),
                                ('Set-Cookie', 'baz=789')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(set_cookies)

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie0 = zappa_cookie[0]
        self.assertTrue(zappa_cookie0.startswith('zappa='))

        print("2: Changing 1 cookie")
        event = {
            u'method': u'POST',
            u'params': {
                u'parameter_1': u'set_cookie'
            },
            u'body': u'foo=qwe',
            u'headers': {
                u'Cookie': zappa_cookie0
            },
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        def change_cookie(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = [('Set-Cookie', 'foo=new_value')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(change_cookie)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        zdict2 = json.loads(base58.b58decode(zdict['zappa']))
        print 'zdict2', zdict2
        self.assertEqual(len(zdict2), 3)
        self.assertEqual(zdict2['foo'], 'new_value')
        self.assertEqual(zdict2['bar'], '456')
        self.assertEqual(zdict2['baz'], '789')

        # We have changed foo, so they should be different
        self.assertNotEqual(zappa_cookie0, zappa_cookie1)

        print("3: Reading the cookies")
        event['headers']['Cookie'] = zappa_cookie1

        def read_cookies(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        app = ZappaWSGIMiddleware(read_cookies)

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)
        print "response", response
        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        cookies = json.loads(base58.b58decode(zdict['zappa']))
        self.assertEqual(cookies['foo'], 'new_value')
        self.assertEqual(cookies['bar'], '456')
        self.assertEqual(cookies['baz'], '789')
Esempio n. 31
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') == u'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 invokation.
        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') == u'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 = ''
                headers = event.get('headers')
                if headers:
                    host = headers.get('Host')
                else:
                    host = None

                if host:
                    if 'amazonaws.com' 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

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(
                    event,
                    script_name=script_name,
                    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
                response = Response.from_app(self.wsgi_app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict()

                if response.data:
                    if settings.BINARY_SUPPORT:
                        if not response.mimetype.startswith("text/") \
                            or response.mimetype != "application/json":
                                zappa_returndict['body'] = base64.b64encode(response.data).decode('utf-8')
                                zappa_returndict["isBase64Encoded"] = True
                        else:
                            zappa_returndict['body'] = response.data
                    else:
                        zappa_returndict['body'] = response.get_data(as_text=True)

                zappa_returndict['statusCode'] = response.status_code
                zappa_returndict['headers'] = {}
                for key, value in response.headers:
                    zappa_returndict['headers'][key] = value

                # 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
Esempio n. 32
0
    def test_wsgi_middleware_realcall(self):
        print("1: Setting the cookies.")
        event = {u'body': None, u'resource': u'/{proxy+}', u'requestContext': {u'resourceId': u'dg451y', u'apiId': u'79gqbxq31c', u'resourcePath': u'/{proxy+}', u'httpMethod': u'GET', u'requestId': u'766df67f-8991-11e6-b2c4-d120fedb94e5', u'accountId': u'724336686645', u'identity': {u'apiKey': None, u'userArn': None, u'cognitoAuthenticationType': None, u'caller': None, u'userAgent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0', u'user': None, u'cognitoIdentityPoolId': None, u'cognitoIdentityId': None, u'cognitoAuthenticationProvider': None, u'sourceIp': u'96.90.37.59', u'accountId': None}, u'stage': u'devorr'}, u'queryStringParameters': None, u'httpMethod': u'GET', u'pathParameters': {u'proxy': u'asdf1/asdf2'}, u'headers': {u'Via': u'1.1 b2aeb492548a8a2d4036401355f928dd.cloudfront.net (CloudFront)', u'Accept-Language': u'en-US,en;q=0.5', u'Accept-Encoding': u'gzip, deflate, br', u'X-Forwarded-Port': u'443', u'X-Forwarded-For': u'96.90.37.59, 54.240.144.50', u'CloudFront-Viewer-Country': u'US', u'Accept': u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', u'Upgrade-Insecure-Requests': u'1', u'Host': u'79gqbxq31c.execute-api.us-east-1.amazonaws.com', u'X-Forwarded-Proto': u'https', u'X-Amz-Cf-Id': u'BBFP-RhGDrQGOzoCqjnfB2I_YzWt_dac9S5vBcSAEaoM4NfYhAQy7Q==', u'User-Agent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0', u'CloudFront-Forwarded-Proto': u'https'}, u'stageVariables': None, u'path': u'/asdf1/asdf2'}

        def set_cookies(environ, start_response):
            status = '200 OK'
            print environ
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456'),
                                ('Set-Cookie', 'baz=789')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(set_cookies)

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie0 = zappa_cookie[0]
        self.assertTrue(zappa_cookie0.startswith('zappa='))

        print("2: Changing 1 cookie")
        # event = {
        #     u'httpMethod': u'POST',
        #     u'params': {u'parameter_1': u'set_cookie'},
        #     u'body': u'foo=qwe',
        #     u'headers': {
        #         u'Cookie': zappa_cookie0
        #     },
        #     u'query': {}
        # }

        event = {
            u'body': u'LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS03Njk1MjI4NDg0Njc4MTc2NTgwNjMwOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9Im15c3RyaW5nIg0KDQpkZGQNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tNzY5NTIyODQ4NDY3ODE3NjU4MDYzMDk2MS0tDQo=',
            u'resource': u'/',
            u'requestContext': {
                u'resourceId': u'6cqjw9qu0b',
                u'apiId': u'9itr2lba55',
                u'resourcePath': u'/',
                u'httpMethod': u'POST',
                u'requestId': u'c17cb1bf-867c-11e6-b938-ed697406e3b5',
                u'accountId': u'724336686645',
                u'identity': {
                    u'apiKey': None,
                    u'userArn': None,
                    u'cognitoAuthenticationType': None,
                    u'caller': None,
                    u'userAgent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:48.0) Gecko/20100101 Firefox/48.0',
                    u'user': None,
                    u'cognitoIdentityPoolId': None,
                    u'cognitoIdentityId': None,
                    u'cognitoAuthenticationProvider': None,
                    u'sourceIp': u'50.191.225.98',
                    u'accountId': None,
                    },
                u'stage': u'devorr',
                },
            u'queryStringParameters': None,
            u'httpMethod': u'POST',
            u'pathParameters': None,
            u'headers': {u'Content-Type': u'multipart/form-data; boundary=---------------------------7695228484678176580630961', u'Via': u'1.1 38205a04d96d60185e88658d3185ccee.cloudfront.net (CloudFront)', u'Accept-Language': u'en-US,en;q=0.5', u'Accept-Encoding': u'gzip, deflate, br', u'CloudFront-Is-SmartTV-Viewer': u'false', u'CloudFront-Forwarded-Proto': u'https', u'X-Forwarded-For': u'71.231.27.57, 104.246.180.51', u'CloudFront-Viewer-Country': u'US', u'Accept': u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', u'User-Agent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0', u'Host': u'xo2z7zafjh.execute-api.us-east-1.amazonaws.com', u'X-Forwarded-Proto': u'https', u'Cookie': zappa_cookie0, u'CloudFront-Is-Tablet-Viewer': u'false', u'X-Forwarded-Port': u'443', u'Referer': u'https://xo8z7zafjh.execute-api.us-east-1.amazonaws.com/former/post', u'CloudFront-Is-Mobile-Viewer': u'false', u'X-Amz-Cf-Id': u'31zxcUcVyUxBOMk320yh5NOhihn5knqrlYQYpGGyOngKKwJb0J0BAQ==', u'CloudFront-Is-Desktop-Viewer': u'true'},
            u'stageVariables': None,
            u'path': u'/',
            }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        def change_cookie(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = [('Set-Cookie', 'foo=new_value')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(change_cookie)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        zdict2 = json.loads(base58.b58decode(zdict['zappa']))
        print 'zdict2', zdict2
        self.assertEqual(len(zdict2), 3)
        self.assertEqual(zdict2['foo'], 'new_value')
        self.assertEqual(zdict2['bar'], '456')
        self.assertEqual(zdict2['baz'], '789')

        # We have changed foo, so they should be different
        self.assertNotEqual(zappa_cookie0, zappa_cookie1)

        print("3: Reading the cookies")
        event['headers']['Cookie'] = zappa_cookie1

        def read_cookies(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        app = ZappaWSGIMiddleware(read_cookies)

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)
        print "response", response
        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        cookies = json.loads(base58.b58decode(zdict['zappa']))
        self.assertEqual(cookies['foo'], 'new_value')
        self.assertEqual(cookies['bar'], '456')
        self.assertEqual(cookies['baz'], '789')
Esempio n. 33
0
    def test_wsgi_middleware_realcall(self):
        print("1: Setting the cookies.")
        event = {
            u'body': None,
            u'resource': u'/{proxy+}',
            u'requestContext': {
                u'resourceId': u'dg451y',
                u'apiId': u'79gqbxq31c',
                u'resourcePath': u'/{proxy+}',
                u'httpMethod': u'GET',
                u'requestId': u'766df67f-8991-11e6-b2c4-d120fedb94e5',
                u'accountId': u'724336686645',
                u'identity': {
                    u'apiKey': None,
                    u'userArn': None,
                    u'cognitoAuthenticationType': None,
                    u'caller': None,
                    u'userAgent':
                    u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0',
                    u'user': None,
                    u'cognitoIdentityPoolId': None,
                    u'cognitoIdentityId': None,
                    u'cognitoAuthenticationProvider': None,
                    u'sourceIp': u'96.90.37.59',
                    u'accountId': None
                },
                u'stage': u'devorr'
            },
            u'queryStringParameters': None,
            u'httpMethod': u'GET',
            u'pathParameters': {
                u'proxy': u'asdf1/asdf2'
            },
            u'headers': {
                u'Via':
                u'1.1 b2aeb492548a8a2d4036401355f928dd.cloudfront.net (CloudFront)',
                u'Accept-Language': u'en-US,en;q=0.5',
                u'Accept-Encoding': u'gzip, deflate, br',
                u'X-Forwarded-Port': u'443',
                u'X-Forwarded-For': u'96.90.37.59, 54.240.144.50',
                u'CloudFront-Viewer-Country': u'US',
                u'Accept':
                u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                u'Upgrade-Insecure-Requests': u'1',
                u'Host': u'79gqbxq31c.execute-api.us-east-1.amazonaws.com',
                u'X-Forwarded-Proto': u'https',
                u'X-Amz-Cf-Id':
                u'BBFP-RhGDrQGOzoCqjnfB2I_YzWt_dac9S5vBcSAEaoM4NfYhAQy7Q==',
                u'User-Agent':
                u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:49.0) Gecko/20100101 Firefox/49.0',
                u'CloudFront-Forwarded-Proto': u'https'
            },
            u'stageVariables': None,
            u'path': u'/asdf1/asdf2'
        }

        def set_cookies(environ, start_response):
            status = '200 OK'
            print environ
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456'),
                                ('Set-Cookie', 'baz=789')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(set_cookies)

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie0 = zappa_cookie[0]
        self.assertTrue(zappa_cookie0.startswith('zappa='))

        print("2: Changing 1 cookie")
        # event = {
        #     u'httpMethod': u'POST',
        #     u'params': {u'parameter_1': u'set_cookie'},
        #     u'body': u'foo=qwe',
        #     u'headers': {
        #         u'Cookie': zappa_cookie0
        #     },
        #     u'query': {}
        # }

        event = {
            u'body':
            u'LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS03Njk1MjI4NDg0Njc4MTc2NTgwNjMwOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9Im15c3RyaW5nIg0KDQpkZGQNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tNzY5NTIyODQ4NDY3ODE3NjU4MDYzMDk2MS0tDQo=',
            u'resource': u'/',
            u'requestContext': {
                u'resourceId': u'6cqjw9qu0b',
                u'apiId': u'9itr2lba55',
                u'resourcePath': u'/',
                u'httpMethod': u'POST',
                u'requestId': u'c17cb1bf-867c-11e6-b938-ed697406e3b5',
                u'accountId': u'724336686645',
                u'identity': {
                    u'apiKey': None,
                    u'userArn': None,
                    u'cognitoAuthenticationType': None,
                    u'caller': None,
                    u'userAgent':
                    u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:48.0) Gecko/20100101 Firefox/48.0',
                    u'user': None,
                    u'cognitoIdentityPoolId': None,
                    u'cognitoIdentityId': None,
                    u'cognitoAuthenticationProvider': None,
                    u'sourceIp': u'50.191.225.98',
                    u'accountId': None,
                },
                u'stage': u'devorr',
            },
            u'queryStringParameters': None,
            u'httpMethod': u'POST',
            u'pathParameters': None,
            u'headers': {
                u'Content-Type':
                u'multipart/form-data; boundary=---------------------------7695228484678176580630961',
                u'Via':
                u'1.1 38205a04d96d60185e88658d3185ccee.cloudfront.net (CloudFront)',
                u'Accept-Language': u'en-US,en;q=0.5',
                u'Accept-Encoding': u'gzip, deflate, br',
                u'CloudFront-Is-SmartTV-Viewer': u'false',
                u'CloudFront-Forwarded-Proto': u'https',
                u'X-Forwarded-For': u'71.231.27.57, 104.246.180.51',
                u'CloudFront-Viewer-Country': u'US',
                u'Accept':
                u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                u'User-Agent':
                u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0',
                u'Host': u'xo2z7zafjh.execute-api.us-east-1.amazonaws.com',
                u'X-Forwarded-Proto': u'https',
                u'Cookie': zappa_cookie0,
                u'CloudFront-Is-Tablet-Viewer': u'false',
                u'X-Forwarded-Port': u'443',
                u'Referer':
                u'https://xo8z7zafjh.execute-api.us-east-1.amazonaws.com/former/post',
                u'CloudFront-Is-Mobile-Viewer': u'false',
                u'X-Amz-Cf-Id':
                u'31zxcUcVyUxBOMk320yh5NOhihn5knqrlYQYpGGyOngKKwJb0J0BAQ==',
                u'CloudFront-Is-Desktop-Viewer': u'true'
            },
            u'stageVariables': None,
            u'path': u'/',
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        def change_cookie(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = [('Set-Cookie', 'foo=new_value')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(change_cookie)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        zdict2 = json.loads(base58.b58decode(zdict['zappa']))
        print 'zdict2', zdict2
        self.assertEqual(len(zdict2), 3)
        self.assertEqual(zdict2['foo'], 'new_value')
        self.assertEqual(zdict2['bar'], '456')
        self.assertEqual(zdict2['baz'], '789')

        # We have changed foo, so they should be different
        self.assertNotEqual(zappa_cookie0, zappa_cookie1)

        print("3: Reading the cookies")
        event['headers']['Cookie'] = zappa_cookie1

        def read_cookies(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        app = ZappaWSGIMiddleware(read_cookies)

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)
        print "response", response
        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        cookies = json.loads(base58.b58decode(zdict['zappa']))
        self.assertEqual(cookies['foo'], 'new_value')
        self.assertEqual(cookies['bar'], '456')
        self.assertEqual(cookies['baz'], '789')
Esempio n. 34
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.
        
        """

        time_start = datetime.datetime.now()

        settings = self.settings

        # The app module
        app_module = importlib.import_module(settings.APP_MODULE)

        # The application
        app_function = getattr(app_module, settings.APP_FUNCTION)

        app = ZappaWSGIMiddleware(app_function)

        # This is a normal HTTP request
        if event.get('method', None):
            # If we just want to inspect this,
            # return this event instead of processing the request
            # https://your_api.aws-api.com/?event_echo=true
            event_echo = getattr(settings, "EVENT_ECHO", True)
            if event_echo:
                if 'event_echo' in list(event['params'].values()):
                    return {'Content': str(event) + '\n' + str(context), 'Status': 200}

            # Create the environment for WSGI and handle the request
            environ = create_wsgi_request(event, script_name='',
                                          trailing_slash=False)

            # We are always on https on Lambda, so tell our wsgi app that.
            environ['wsgi.url_scheme'] = 'https'

            response = Response.from_app(app, environ)

            zappa_returndict = dict()

            if response.data:
                zappa_returndict['Content'] = response.data

            # Pack the WSGI response into our special dictionary.
            for (header_name, header_value) in response.headers:
                zappa_returndict[header_name] = header_value
            zappa_returndict['Status'] = response.status_code

            # To ensure correct status codes, we need to
            # pack the response as a deterministic B64 string and raise it
            # as an error to match our APIGW regex.
            # The DOCTYPE ensures that the page still renders in the browser.
            exception = None
            if response.status_code in [400, 401, 403, 404, 500]:
                content = "<!DOCTYPE html>" + str(response.status_code) + response.data
                exception = base64.b64encode(content)
            # Internal are changed to become relative redirects
            # so they still work for apps on raw APIGW and on a domain.
            elif response.status_code in [301, 302]:
                # Location is by default relative on Flask. Location is by default
                # absolute on Werkzeug. We can set autocorrect_location_header on
                # the response to False, but it doesn't work. We have to manually
                # remove the host part.
                location = response.location
                hostname = 'https://' + environ['HTTP_HOST']
                if location.startswith(hostname):
                    exception = location[len(hostname):]

            # 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)

            # Finally, return the response to API Gateway.
            if exception: # pragma: no cover
                raise Exception(exception)
            else:
                return zappa_returndict
Esempio n. 35
0
    def test_wsgi_authorizer_handling(self):
        # With user
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u'user1'
                }
            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)
        self.assertEqual(environ['REMOTE_USER'], u'user1')

        # With empty authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u''
                }
            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')

        # With missing authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {},
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')

        # With empty authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {}
            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')
Esempio n. 36
0
def lambda_handler(event,
                   context=None,
                   settings_name="zappa_settings"):  # NoQA
    """
    An AWS Lambda function which parses specific API Gateway input into a WSGI request.

    The request get fed it to Django, processes the Django response, and returns that
    back to the API Gateway.
    """
    time_start = datetime.datetime.now()

    # If in DEBUG mode, log all raw incoming events.
    if settings.DEBUG:
        logger.info('Zappa Event: {}'.format(event))

    # This is a normal HTTP request
    if event.get('method', None):

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME)

        # We are always on https on Lambda, so tell our wsgi app that.
        environ['HTTPS'] = 'on'
        environ['wsgi.url_scheme'] = 'https'

        wrap_me = get_wsgi_application()
        app = ZappaWSGIMiddleware(wrap_me)

        # Execute the application
        response = Response.from_app(app, environ)
        response.content = response.data

        # Prepare the special dictionary which will be returned to the API GW.
        returnme = {'Content': response.data}

        # Pack the WSGI response into our special dictionary.
        for (header_name, header_value) in response.headers:
            returnme[header_name] = header_value
        returnme['Status'] = response.status_code

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        exception = None
        if response.status_code in ERROR_CODES:
            content = u"<!DOCTYPE html>" + unicode(
                response.status_code) + unicode(
                    '<meta charset="utf-8" />') + response.data.encode('utf-8')
            b64_content = base64.b64encode(content)
            exception = (b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif 300 <= response.status_code < 400 and response.has_header(
                'Location'):
            location = returnme['Location']
            location = '/' + location.replace("http://zappa/", "")
            exception = location

        # 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
        common_log(environ, response, response_time=response_time_ms)

        # Finally, return the response to API Gateway.
        if exception:
            raise Exception(exception)
        else:
            return returnme

    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}
    elif event.get('detail'):
        module, function = event['detail'].rsplit('.', 1)

        app_module = importlib.import_module(module)
        app_function = getattr(app_module, function)

        # Execute the function!
        app_function()
        return
    else:
        logger.error('Unhandled event: {}'.format(json.dumps(event)))
Esempio n. 37
0
    def test_wsgi_middleware_realcall(self):
        print("1: Setting the cookies.")
        event = {
            u'method': u'POST',
            u'params': {u'parameter_1': u'set_cookie'},
            u'body': u'foo=xxx&bar=yyy',
            u'headers': {},
            u'query': {}}

        def set_cookies(environ, start_response):
            status = '200 OK'
            print environ
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456'),
                                ('Set-Cookie', 'baz=789')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(set_cookies)

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie0 = zappa_cookie[0]
        self.assertTrue(zappa_cookie0.startswith('zappa='))

        print("2: Changing 1 cookie")
        event = {
            u'method': u'POST',
            u'params': {u'parameter_1': u'set_cookie'},
            u'body': u'foo=qwe',
            u'headers': {
                u'Cookie': zappa_cookie0
            },
            u'query': {}
        }

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        def change_cookie(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = [('Set-Cookie', 'foo=new_value')]
            start_response(status, response_headers)
            return ['Set cookies!']

        app = ZappaWSGIMiddleware(change_cookie)

        response = Response.from_app(app, environ)

        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        zdict2 = json.loads(base58.b58decode(zdict['zappa']))
        print 'zdict2', zdict2
        self.assertEqual(len(zdict2), 3)
        self.assertEqual(zdict2['foo'], 'new_value')
        self.assertEqual(zdict2['bar'], '456')
        self.assertEqual(zdict2['baz'], '789')

        # We have changed foo, so they should be different
        self.assertNotEqual(zappa_cookie0, zappa_cookie1)

        print("3: Reading the cookies")
        event['headers']['Cookie'] = zappa_cookie1

        def read_cookies(environ, start_response):
            status = '200 OK'
            print 'environ', environ
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        app = ZappaWSGIMiddleware(read_cookies)

        environ = create_wsgi_request(event, script_name='http://zappa.com/',
                                      trailing_slash=False)

        response = Response.from_app(app, environ)
        print "response", response
        # Filter the headers for Set-Cookie header
        zappa_cookie = [x[1] for x in response.headers if x[0] == 'Set-Cookie']
        self.assertEqual(len(zappa_cookie), 1)
        zappa_cookie1 = zappa_cookie[0]
        self.assertTrue(zappa_cookie1.startswith('zappa='))
        zdict = parse_cookie(zappa_cookie1)
        print 'zdict', zdict
        cookies = json.loads(base58.b58decode(zdict['zappa']))
        self.assertEqual(cookies['foo'], 'new_value')
        self.assertEqual(cookies['bar'], '456')
        self.assertEqual(cookies['baz'], '789')
Esempio n. 38
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.

        """

        if os.environ.get('AWS_EXECUTION_ENV', '').startswith('AWS_Lambda_'):
            xray_recorder.begin_subsegment("pre_handle")

        settings = self.settings

        if 'multiValueHeaders' in event:
            event['headers'] = dict([(key, value[-1]) for (key, value) in (event.get('multiValueHeaders') or {}).items()])

        if 'multiValueQueryStringParameters' in event:
            event['queryStringParameters'] = dict([(key, value[-1]) for (key, value) in (event.get('multiValueQueryStringParameters') or {}).items()])

        # 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') == u'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)
            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 invokation.
        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') == u'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 = event.get('headers')
                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,
                    should_transform_to_iso_8859_1=not settings.DJANGO_SETTINGS
                )

                # 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

                if os.environ.get('AWS_EXECUTION_ENV', '').startswith('AWS_Lambda_'):
                    xray_recorder.end_subsegment()

                # Execute the application
                response = Response.from_app(self.wsgi_app, environ)

                if os.environ.get('AWS_EXECUTION_ENV', '').startswith('AWS_Lambda_'):
                    xray_recorder.begin_subsegment("post_handle")

                # 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:
                        if not response.mimetype.startswith("text/") \
                            or response.mimetype != "application/json":
                                zappa_returndict['body'] = base64.b64encode(response.data).decode('utf-8')
                                zappa_returndict["isBase64Encoded"] = True
                        else:
                            zappa_returndict['body'] = response.data
                    else:
                        zappa_returndict['body'] = response.get_data(as_text=True)

                zappa_returndict['statusCode'] = response.status_code
                zappa_returndict['headers'] = {}
                for key, value in response.headers:
                    zappa_returndict['headers'][key] = value

                zappa_returndict['multiValueHeaders'] = {}
                for key, value in response.headers:
                    zappa_returndict['multiValueHeaders'][key] = [value]

                # 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)

                if os.environ.get('AWS_EXECUTION_ENV', '').startswith('AWS_Lambda_'):
                    xray_recorder.end_subsegment()

                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
Esempio n. 39
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.
        
        """

        # TODO If this is invoked from a non-APIGW/Scheduled event source,
        # extract the method and process separately.

        if event.get('detail-type', None) == u'Scheduled Event':
            
            whole_function = event['resources'][0].split('/')[-1]
            module, function = whole_function.rsplit('.', 1)

            app_module = importlib.import_module(module)
            app_function = getattr(app_module, function)

            # Execute the function!
            app_function()
            return

        try:
            # Timing
            time_start = datetime.datetime.now()

            settings = self.settings

            # Custom log level
            if settings.LOG_LEVEL:
                level = logging.getLevelName(settings.LOG_LEVEL)
                logger.setLevel(level)

            # The app module
            app_module = importlib.import_module(settings.APP_MODULE)

            # The application
            app_function = getattr(app_module, settings.APP_FUNCTION)
            app = ZappaWSGIMiddleware(app_function)

            # This is a normal HTTP request
            if event.get('method', None):
                # If we just want to inspect this,
                # return this event instead of processing the request
                # https://your_api.aws-api.com/?event_echo=true
                event_echo = getattr(settings, "EVENT_ECHO", True)
                if event_echo and 'event_echo' in event['params'].values():
                    return {'Content': str(event) + '\n' + str(context), 'Status': 200}

                if settings.DOMAIN:
                    # If we're on a domain, we operate normally
                    script_name = ''
                else:
                    # But if we're not, then our base URL
                    # will be something like
                    # https://blahblahblah.execute-api.us-east-1.amazonaws.com/dev
                    # So, we need to make sure the WSGI app knows this.
                    script_name = '/' + settings.API_STAGE

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(event, 
                                                script_name=script_name,
                                                trailing_slash=False
                                            )

                # We are always on https on Lambda, so tell our wsgi app that.
                environ['wsgi.url_scheme'] = 'https'

                # Execute the application
                response = Response.from_app(app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict(response.headers)

                if 'Content' not in zappa_returndict and response.data:
                    zappa_returndict['Content'] = response.data

                zappa_returndict['Status'] = response.status_code

                # To ensure correct status codes, we need to
                # pack the response as a deterministic B64 string and raise it
                # as an error to match our APIGW regex.
                # The DOCTYPE ensures that the page still renders in the browser.
                exception = None
                if response.status_code in [400, 401, 403, 404, 500]:
                    content = "<!DOCTYPE html>" + str(response.status_code) + response.data
                    exception = base64.b64encode(content)
                # Internal are changed to become relative redirects
                # so they still work for apps on raw APIGW and on a domain.
                elif response.status_code in [301, 302]:
                    # Location is by default relative on Flask. Location is by default
                    # absolute on Werkzeug. We can set autocorrect_location_header on
                    # the response to False, but it doesn't work. We have to manually
                    # remove the host part.
                    location = response.location
                    hostname = 'https://' + environ['HTTP_HOST']
                    if location.startswith(hostname):
                        exception = location[len(hostname):]

                # 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)

                # Finally, return the response to API Gateway.
                if exception: # pragma: no cover
                    raise LambdaException(exception)
                else:
                    return zappa_returndict
        except LambdaException as e: # pragma: nocover
            raise e
        except Exception as e: # pragma: nocover

            # Print statements are visible in the logs either way
            print(e)

            # Print the error to the browser upon failure?
            debug = bool(getattr(app_module, settings.DEBUG, True))
            if debug:
                # Return this unspecified exception as a 500.
                content = "<!DOCTYPE html>500. From Zappa: <pre>" + str(e) + "</pre><br /><pre>" + traceback.format_exc().replace('\n', '<br />') + "</pre>"
                exception = base64.b64encode(content)
                raise Exception(exception)
            else:
                raise e
Esempio n. 40
0
def lambda_handler(event, context, settings_name="zappa_settings"):
    """ An AWS Lambda function which parses specific API Gateway input into a
    WSGI request, feeds it to Flask, procceses the Flask response, and returns
    that back to the API Gateway.
    """
    # Loading settings from a python module
    settings = importlib.import_module(settings_name)

    # The flask-app module
    app_module = importlib.import_module(settings.APP_MODULE)

    # The flask-app
    app = getattr(app_module, settings.APP_OBJECT)
    app.config.from_object('zappa_settings')

    app.wsgi_app = ZappaWSGIMiddleware(app.wsgi_app)

    # This is a normal HTTP request
    if event.get('method', None):
        # If we just want to inspect this,
        # return this event instead of processing the request
        # https://your_api.aws-api.com/?event_echo=true
        event_echo = getattr(settings, "EVENT_ECHO", True)
        if event_echo:
            if 'event_echo' in list(event['params'].values()):
                return {
                    'Content': str(event) + '\n' + str(context),
                    'Status': 200
                }

        # TODO: Enable Let's Encrypt
        # # If Let's Encrypt is defined in the settings,
        # # and the path is your.domain.com/.well-known/acme-challenge/{{lets_encrypt_challenge_content}},
        # # return a 200 of lets_encrypt_challenge_content.
        # lets_encrypt_challenge_path = getattr(settings, "LETS_ENCRYPT_CHALLENGE_PATH", None)
        # lets_encrypt_challenge_content = getattr(settings, "LETS_ENCRYPT_CHALLENGE_CONTENT", None)
        # if lets_encrypt_challenge_path:
        #     if len(event['params']) == 3:
        #         if event['params']['parameter_1'] == '.well-known' and \
        #             event['params']['parameter_2'] == 'acme-challenge' and \
        #             event['params']['parameter_3'] == lets_encrypt_challenge_path:
        #                 return {'Content': lets_encrypt_challenge_content, 'Status': 200}

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event,
                                      script_name=settings.SCRIPT_NAME,
                                      trailing_slash=False)

        # We are always on https on Lambda, so tell our wsgi app that.
        environ['wsgi.url_scheme'] = 'https'

        response = Response.from_app(app, environ)

        # This doesn't work. It should probably be set right after creation, not
        # at such a late stage.
        # response.autocorrect_location_header = False

        zappa_returndict = dict()

        if response.data:
            zappa_returndict['Content'] = response.data

        # Pack the WSGI response into our special dictionary.
        for (header_name, header_value) in response.headers:
            zappa_returndict[header_name] = header_value
        zappa_returndict['Status'] = response.status_code

        # TODO: No clue how to handle the flask-equivalent of this. Or is this
        # something entirely specified by the middleware?
        # # Parse the WSGI Cookie and pack it.
        # cookie = response.cookies.output()
        # if ': ' in cookie:
        #     zappa_returndict['Set-Cookie'] = response.cookies.output().split(': ')[1]

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        if response.status_code in [400, 401, 403, 404, 500]:
            content = "<!DOCTYPE html>" + str(
                response.status_code) + response.data
            b64_content = base64.b64encode(content)
            raise Exception(b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif response.status_code in [301, 302]:
            # Location is by default relative on Flask. Location is by default
            # absolute on Werkzeug. We can set autocorrect_location_header on
            # the response to False, but it doesn't work. We have to manually
            # remove the host part.
            location = response.location
            hostname = 'https://' + environ['HTTP_HOST']
            if location.startswith(hostname):
                location = location[len(hostname):]
            raise Exception(location)
        else:
            return zappa_returndict
Esempio n. 41
0
 def test_wsgi_multipart(self):
     event = {u'body': u'LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS03Njk1MjI4NDg0Njc4MTc2NTgwNjMwOTYxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9Im15c3RyaW5nIg0KDQpkZGQNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tNzY5NTIyODQ4NDY3ODE3NjU4MDYzMDk2MS0tDQo=', u'headers': {u'Content-Type': u'multipart/form-data; boundary=---------------------------7695228484678176580630961', u'Via': u'1.1 38205a04d96d60185e88658d3185ccee.cloudfront.net (CloudFront)', u'Accept-Language': u'en-US,en;q=0.5', u'Accept-Encoding': u'gzip, deflate, br', u'CloudFront-Is-SmartTV-Viewer': u'false', u'CloudFront-Forwarded-Proto': u'https', u'X-Forwarded-For': u'71.231.27.57, 104.246.180.51', u'CloudFront-Viewer-Country': u'US', u'Accept': u'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', u'User-Agent': u'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0', u'Host': u'xo2z7zafjh.execute-api.us-east-1.amazonaws.com', u'X-Forwarded-Proto': u'https', u'Cookie': u'zappa=AQ4', u'CloudFront-Is-Tablet-Viewer': u'false', u'X-Forwarded-Port': u'443', u'Referer': u'https://xo8z7zafjh.execute-api.us-east-1.amazonaws.com/former/post', u'CloudFront-Is-Mobile-Viewer': u'false', u'X-Amz-Cf-Id': u'31zxcUcVyUxBOMk320yh5NOhihn5knqrlYQYpGGyOngKKwJb0J0BAQ==', u'CloudFront-Is-Desktop-Viewer': u'true'}, u'params': {u'parameter_1': u'post'}, u'method': u'POST', u'query': {}}
     environ = create_wsgi_request(event, trailing_slash=False)
     response_tuple = collections.namedtuple('Response', ['status_code', 'content'])
     response = response_tuple(200, 'hello')
Esempio n. 42
0
    def test_wsgi_authorizer_handling(self):
        # With user
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u'user1'
                }
            },
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)
        self.assertEqual(environ['REMOTE_USER'], u'user1')

        # With empty authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {
                    u'principalId': u''
                }
            },
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')

        # With missing authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {},
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')

        # With empty authorizer, should not include REMOTE_USER
        event = {
            u'httpMethod': u'GET',
            u'queryStringParameters': None,
            u'path': u'/v1/runs',
            u'params': {},
            u'body': {},
            u'headers': {
                u'Content-Type': u'application/json'
            },
            u'pathParameters': {
                u'proxy': 'v1/runs'
            },
            u'requestContext': {
                u'authorizer': {}
            },
            u'query': {}
        }

        environ = create_wsgi_request(event,
                                      script_name='http://zappa.com/',
                                      trailing_slash=False)
        user = environ.get('REMOTE_USER', u'no_user')
        self.assertEqual(user, u'no_user')
Esempio n. 43
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))

        # This is the result of a keep alive, recertify
        # or scheduled event.
        if event.get('detail-type') == u'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 overriden 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 invokation.
        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 API Gateway authorizer event
        elif event.get('type') == u'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')

        # Normal web app flow
        try:
            # Timing
            time_start = datetime.datetime.now()

            # This is a normal HTTP request
            if event.get('httpMethod', None):

                script_name = ''
                headers = event.get('headers')
                if headers:
                    host = headers.get('Host')
                else:
                    host = None

                if host:
                    if 'amazonaws.com' 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

                # Create the environment for WSGI and handle the request
                environ = create_wsgi_request(
                    event,
                    script_name=script_name,
                    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

                # Execute the application
                response = Response.from_app(self.wsgi_app, environ)

                # This is the object we're going to return.
                # Pack the WSGI response into our special dictionary.
                zappa_returndict = dict()

                if response.data:
                    if settings.BINARY_SUPPORT:
                        if not response.mimetype.startswith("text/") \
                            or response.mimetype != "application/json":
                            zappa_returndict['body'] = base64.b64encode(
                                response.data).decode('utf-8')
                            zappa_returndict["isBase64Encoded"] = "true"
                        else:
                            zappa_returndict['body'] = response.data
                    else:
                        zappa_returndict['body'] = response.get_data(
                            as_text=True)

                zappa_returndict['statusCode'] = response.status_code
                zappa_returndict['headers'] = {}
                for key, value in response.headers:
                    zappa_returndict['headers'][key] = value

                # 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)

            # 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
Esempio n. 44
0
def lambda_handler(event, context, settings_name="zappa_settings"):
    """
    An AWS Lambda function which parses specific API Gateway input into a WSGI request,
    feeds it to Django, procceses the Django response, and returns that 
    back to the API Gateway.

    """
    time_start = datetime.datetime.now()

    logging.basicConfig()
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    # Django requires settings and an explicit call to setup()
    # if being used from inside a python context.
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_name)
    import django
    django.setup()
    from django.conf import settings

    # If in DEBUG mode, log all raw incoming events.
    if settings.DEBUG:
        logger.info('Zappa Event: {}'.format(event))

    # This is a normal HTTP request
    if event.get('method', None):

        # If we just want to inspect this,
        # return this event instead of processing the request
        # https://your_api.aws-api.com/?event_echo=true
        event_echo = getattr(settings, "EVENT_ECHO", True)
        if event_echo:
            if 'event_echo' in event['params'].values():
                return {'Content': str(event) + '\n' + str(context), 'Status': 200}

        # If Let's Encrypt is defined in the settings,
        # and the path is your.domain.com/.well-known/acme-challenge/{{lets_encrypt_challenge_content}},
        # return a 200 of lets_encrypt_challenge_content.
        lets_encrypt_challenge_path = getattr(settings, "LETS_ENCRYPT_CHALLENGE_PATH", None)
        lets_encrypt_challenge_content = getattr(settings, "LETS_ENCRYPT_CHALLENGE_CONTENT", None)
        if lets_encrypt_challenge_path:
            if len(event['params']) == 3:
                if event['params']['parameter_1'] == '.well-known' and \
                    event['params']['parameter_2'] == 'acme-challenge' and \
                    event['params']['parameter_3'] == lets_encrypt_challenge_path:
                        return {'Content': lets_encrypt_challenge_content, 'Status': 200}

        # This doesn't matter, but Django's handler requires it.
        def start(a, b):
            return

        # Create the environment for WSGI and handle the request
        environ = create_wsgi_request(event, script_name=settings.SCRIPT_NAME)
        handler = WSGIHandler()
        response = handler(environ, start)

        # Prepare the special dictionary which will be returned to the API GW.
        returnme = {'Content': response.content}

        # Pack the WSGI response into our special dictionary.
        for item in response.items():
            returnme[item[0]] = item[1]
        returnme['Status'] = response.status_code

        # Parse the WSGI Cookie and pack it.
        cookie = response.cookies.output()
        if ': ' in cookie:
            returnme['Set-Cookie'] = response.cookies.output().split(': ')[1]

        # To ensure correct status codes, we need to
        # pack the response as a deterministic B64 string and raise it
        # as an error to match our APIGW regex.
        # The DOCTYPE ensures that the page still renders in the browser.
        exception = None
        if response.status_code in [400, 401, 403, 500]:
            content = response.content
            content = "<!DOCTYPE html>" + str(response.status_code) + response.content
            b64_content = base64.b64encode(content)
            exception = (b64_content)
        # Internal are changed to become relative redirects
        # so they still work for apps on raw APIGW and on a domain.
        elif response.status_code in [301, 302]:
            location = returnme['Location']
            location = '/' + location.replace("http://zappa/", "")
            exception = location

        # 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
        common_log(environ, response, response_time=response_time_ms)

        # Finally, return the response to API Gateway.
        if exception:
            raise Exception(exception)
        else:
            return returnme

    # This is a management command invocation.
    elif event.get('command', None):
        from django.core import management

        # Couldn't figure out how to get the value into stdout with StringIO..
        # Read the log for now. :[]
        management.call_command(*event['command'].split(' '))
        return {}