Beispiel #1
0
    def test_wsgi_middleware_uglystring(self):
        ugly_string = "˝ÓÔÒÚÆ☃ЗИЙКЛМФХЦЧШ차를 타고 온 펲시맨(╯°□°)╯︵ ┻━┻)"

        # Pass some unicode through the middleware body
        def simple_app(environ, start_response):
            # String of weird characters
            status = '200 OK'
            response_headers = []
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))

        # Pass some unicode through the middleware headers
        def simple_app(environ, start_response):
            # String of weird characters
            status = '301 Moved Permanently'
            response_headers = [('Location',
                                 f'http://zappa.com/elsewhere{ugly_string}')]
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))
Beispiel #2
0
    def test_wsgi_middleware_uglystring(self):
        ugly_string = unicode("˝ÓÔÒÚÆ☃ЗИЙКЛМФХЦЧШ차를 타고 온 펲시맨(╯°□°)╯︵ ┻━┻)"
                              "לֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָt͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨ 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨",
                              encoding='utf8')

        # Pass some unicode through the middleware body
        def simple_app(environ, start_response):
            # String of weird characters
            status = '200 OK'
            response_headers = []
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))

        # Pass some unicode through the middleware headers
        def simple_app(environ, start_response):
            # String of weird characters
            status = '301 Moved Permanently'
            response_headers = [('Location', 'http://zappa.com/elsewhere' + ugly_string)]
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))
Beispiel #3
0
    def test_wsgi_middleware_redirect(self):
        url = 'http://bogus.com/target'
        body = 'Moved. Click <a href="' + url + '">here</a>!'

        # 301
        def simple_app(environ, start_response):
            status = '301 Moved Permanently'
            response_headers = [('Location', url), ('Set-Cookie', 'foo=456'),
                                ('Content-Type', 'text/html; charset blahblah')
                                ]
            start_response(status, response_headers)
            return [body]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        #self.assertEqual(self.status[0], '301 Moved Permanently')
        self.assertEqual(self.status[0], '200 OK')

        # Assert there is only one zappa cookie
        self.assertEqual(len(self.headers), 3)

        self.assertEqual(self.headers[0][0], 'Location')
        self.assertEqual(self.headers[0][1], url)

        self.assertEqual(self.headers[2][0], 'Set-Cookie')
        self.assertTrue(self.headers[2][1].startswith('zappa='))

        self.assertNotEqual(''.join(resp), body)

        # Same as above but with 302f
        def simple_app(environ, start_response):
            status = '302 Found'
            response_headers = [('Derp', 'Max-Squirt'), ('Location', url),
                                ('Set-Cookie', 'foo=456')]
            start_response(status, response_headers)
            return [body]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        self.assertEqual(self.status[0], '302 Found')
        #self.assertEqual(self.status[0], '200 OK')
        self.assertEqual(len(self.headers), 3)

        self.assertEqual(self.headers[1][0], 'Location')
        self.assertEqual(self.headers[1][1], url)

        self.assertEqual(self.headers[2][0], 'Set-Cookie')
        self.assertTrue(self.headers[2][1].startswith('zappa='))

        self.assertEqual(''.join(resp), body)
Beispiel #4
0
    def test_wsgi_middleware_expiry(self):
        # Setting the cookies
        def simple_app(environ, start_response):
            status = '200 OK'
            response_headers = [
                ('Set-Cookie',
                 'boss=hogg; Expires=Wed, 09 Jun 2021 10:18:14 GMT;'),
                ('Set-Cookie',
                 'spank=stank; Expires=Wed, 09 Jun 2010 10:18:14 GMT;'),
                ('Set-Cookie',
                 'out=lawz; Expires=Wed, 09 Jun 2001 10:18:14 GMT;')
            ]
            start_response(status, response_headers)
            return ['Stale cookies!']

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        # Ensure the encoded zappa cookie is set
        self.assertEqual(self.headers[0][0], 'Set-Cookie')
        zappa_cookie = self.headers[0][1]
        self.assertTrue(zappa_cookie.startswith('zappa='))
Beispiel #5
0
    def __init__(self, settings_name="zappa_settings", session=None):

        # We haven't cached our settings yet, load the settings and app.
        if not self.settings:
            # Loading settings from a python module
            self.settings = importlib.import_module(settings_name)
            self.settings_name = settings_name
            self.session = session

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

            remote_env = getattr(self.settings, 'REMOTE_ENV', None)
            remote_bucket, remote_file = parse_s3_url(remote_env)

            if remote_bucket and remote_file:
                self.load_remote_settings(remote_bucket, remote_file)

            # Let the system know that this will be a Lambda/Zappa/Stack
            os.environ["SERVERTYPE"] = "AWS Lambda"
            os.environ["FRAMEWORK"] = "Zappa"
            try:
                os.environ["PROJECT"] = self.settings.PROJECT_NAME
                os.environ["STAGE"] = self.settings.API_STAGE
            except Exception:  # pragma: no cover
                pass

            # Set any locally defined env vars
            # Environement variable keys can't be Unicode
            # https://github.com/Miserlou/Zappa/issues/604
            for key in self.settings.ENVIRONMENT_VARIABLES.keys():
                os.environ[str(key)] = self.settings.ENVIRONMENT_VARIABLES[key]

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

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

                try:  # Support both for tests
                    from zappa.ext.django import get_django_wsgi
                except ImportError:  # pragma: no cover
                    from django_zappa_app import get_django_wsgi

                # Get the Django WSGI app from our extension
                wsgi_app_function = get_django_wsgi(
                    self.settings.DJANGO_SETTINGS)
                self.trailing_slash = True

            self.wsgi_app = ZappaWSGIMiddleware(wsgi_app_function)
Beispiel #6
0
    def test_wsgi_middleware_uglystring(self):
        if sys.version_info[0] < 3:
            ugly_string = unicode(
                "�ÓÔÒÚÆ☃ЗИЙКЛМФХЦЧШ차를 타고 온 펲시맨(╯°□°)╯︵ ┻�┻)"
                "לֹהִי�, �ֵת הַשָּ�מַיִ�, וְ�ֵת הָt͔̦h̞̲e̢̤ �̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖�̗͓̳̮g� ̨ ����� ����",
                encoding='utf8')
        else:
            ugly_string = "�ÓÔÒÚÆ☃ЗИЙКЛМФХЦЧШ차를 타고 온 펲시맨(╯°□°)╯︵ ┻�┻)"

        # Pass some unicode through the middleware body
        def simple_app(environ, start_response):
            # String of weird characters
            status = '200 OK'
            response_headers = []
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))

        # Pass some unicode through the middleware headers
        def simple_app(environ, start_response):
            # String of weird characters
            status = '301 Moved Permanently'
            response_headers = [('Location',
                                 'http://zappa.com/elsewhere' + ugly_string)]
            start_response(status, response_headers)
            return [ugly_string]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)
        print(''.join(resp))
Beispiel #7
0
    def test_wsgi_middleware_unpackcookies(self):
        # Setting the cookies
        def simple_app(environ, start_response):
            status = '200 OK'
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456'),
                                ('Set-Cookie', 'baz=789')]
            start_response(status, response_headers)
            return ['Set cookies!']

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        # Ensure the encoded zappa cookie is set
        self.assertEqual(self.headers[0][0], 'Set-Cookie')
        zappa_cookie = self.headers[0][1]
        self.assertTrue(zappa_cookie.startswith('zappa='))

        # Reads the hopefully decoded cookies
        def simple_app(environ, start_response):
            status = '200 OK'
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call the app with the encoded cookie in the environment
        resp = app({'HTTP_COOKIE': zappa_cookie}, self._start_response)

        # Assert that the simple_app, received the decoded cookies
        excpected = {'foo': '123', 'bar': '456', 'baz': '789'}
        received = parse_cookie(''.join(resp))
        self.assertDictEqual(received, excpected)
Beispiel #8
0
    def test_wsgi_middleware_multiplecookies(self):
        def simple_app(environ, start_response):
            status = '200 OK'
            response_headers = [('Set-Cookie', 'foo=123'),
                                ('Set-Cookie', 'bar=456')]
            start_response(status, response_headers)
            return ['Hello zappa!']

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(simple_app)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        self.assertEqual(self.status[0], '200 OK')

        # Assert there is only one zappa cookie
        self.assertEqual(len(self.headers), 1)
        self.assertEqual(self.headers[0][0], 'Set-Cookie')
        self.assertTrue(self.headers[0][1].startswith('zappa='))

        self.assertEqual(''.join(resp), 'Hello zappa!')
Beispiel #9
0
    def __init__(self, settings_name="zappa_settings", session=None):

        # We haven't cached our settings yet, load the settings and app.
        if not self.settings:
            # Loading settings from a python module
            self.settings = importlib.import_module(settings_name)
            self.settings_name = settings_name
            self.session = session

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

            remote_env = getattr(self.settings, 'REMOTE_ENV', None)
            remote_bucket, remote_file = parse_s3_url(remote_env)

            if remote_bucket and remote_file:
                self.load_remote_settings(remote_bucket, remote_file)

            # Let the system know that this will be a Lambda/Zappa/Stack
            os.environ["SERVERTYPE"] = "AWS Lambda"
            os.environ["FRAMEWORK"] = "Zappa"
            try:
                os.environ["PROJECT"] = self.settings.PROJECT_NAME
                os.environ["STAGE"] = self.settings.API_STAGE
            except Exception:  # pragma: no cover
                pass

            # Set any locally defined env vars
            # Environement variable keys can't be Unicode
            # https://github.com/Miserlou/Zappa/issues/604
            for key in self.settings.ENVIRONMENT_VARIABLES.keys():
                os.environ[str(key)] = self.settings.ENVIRONMENT_VARIABLES[key]

            # Pulling from S3 if given a zip path
            project_archive_path = getattr(self.settings, 'ARCHIVE_PATH', None)
            if project_archive_path:
                self.load_remote_project_archive(project_archive_path)

            # Load compliled library to the PythonPath
            # checks if we are the slim_handler since this is not needed otherwise
            # https://github.com/Miserlou/Zappa/issues/776
            is_slim_handler = getattr(self.settings, 'SLIM_HANDLER', False)
            if is_slim_handler:
                included_libraries = getattr(self.settings, 'INCLUDE',
                                             ['libmysqlclient.so.18'])
                try:
                    from ctypes import cdll, util
                    for library in included_libraries:
                        try:
                            cdll.LoadLibrary(os.path.join(
                                os.getcwd(), library))
                        except OSError:
                            print("Failed to find library...right filename?")
                except ImportError:
                    print("Failed to import cytpes library")

            # This is a non-WSGI application
            # https://github.com/Miserlou/Zappa/pull/748
            if not hasattr(self.settings,
                           'APP_MODULE') and not self.settings.DJANGO_SETTINGS:
                self.app_module = None
                wsgi_app_function = None
            # This is probably a normal WSGI app
            elif not self.settings.DJANGO_SETTINGS:
                # The app module
                self.app_module = importlib.import_module(
                    self.settings.APP_MODULE)

                # The application
                wsgi_app_function = getattr(self.app_module,
                                            self.settings.APP_FUNCTION)
                self.trailing_slash = False
            # Django gets special treatment.
            else:

                try:  # Support both for tests
                    from zappa.ext.django_zappa import get_django_wsgi
                except ImportError:  # pragma: no cover
                    from django_zappa_app import get_django_wsgi

                # Get the Django WSGI app from our extension
                wsgi_app_function = get_django_wsgi(
                    self.settings.DJANGO_SETTINGS)
                self.trailing_slash = True

            self.wsgi_app = ZappaWSGIMiddleware(wsgi_app_function)
Beispiel #10
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
Beispiel #11
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)))
Beispiel #12
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.
Beispiel #13
0
    def test_wsgi_middleware_cookieoverwrite(self):
        """ This method do:
            * Sets a bunch of cookies.
            * Fetches the zappa cookie from the response
            * Overwrites only some of the cookies.
            * Fetches the zappa cookie from the response
            * Reads the cookie
        """

        # Setting the cookies
        def set_cookies(environ, start_response):
            status = '200 OK'
            response_headers = [
                ('Set-Cookie', 'foo=123'), ('Set-Cookie', 'bar=456'),
                ('Set-Cookie',
                 'baz=789; Expires=Wed, 09 Jun 2001 10:18:14 GMT;')
            ]
            start_response(status, response_headers)
            return ['Set cookies!']

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(set_cookies)

        # Call with empty WSGI Environment
        resp = app(dict(), self._start_response)

        # Retrieve the cookie
        zappa_cookie = self.headers[0][1]

        def change_cookies(environ, start_response):
            status = '200 OK'
            response_headers = [('Set-Cookie', 'foo=sdf'),
                                ('Set-Cookie', 'baz=jkl')]
            start_response(status, response_headers)
            return ['Set cookies!']

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(change_cookies)

        # Call the app with the encoded cookie in the environment
        resp = app({'HTTP_COOKIE': zappa_cookie}, self._start_response)

        # Retrieve the cookie
        zappa_cookie = self.headers[0][1]

        # Reads the hopefully decoded cookies
        def read_cookies(environ, start_response):
            status = '200 OK'
            response_headers = []
            start_response(status, response_headers)
            return [environ['HTTP_COOKIE']]

        # Wrap the app with the middleware
        app = ZappaWSGIMiddleware(read_cookies)

        # Call the app with the encoded cookie in the environment
        resp = app({'HTTP_COOKIE': zappa_cookie}, self._start_response)

        # Assert that read_cookies received the corrected decoded cookies
        excpected = {'foo': 'sdf', 'bar': '456', 'baz': 'jkl'}
        received = parse_cookie(''.join(resp))

        self.assertDictEqual(received, excpected)

        # Call the app with the encoded cookie in the environment
        resp = app({'HTTP_COOKIE': zappa_cookie}, self._start_response)
        received = parse_cookie(''.join(resp))
        self.assertDictEqual(received, excpected)
Beispiel #14
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')
Beispiel #15
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')
Beispiel #16
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