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)
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
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
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.
def test_dj_wsgi(self): # Sanity settings_modules = detect_django_settings() settings = """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'alskdfjalsdkf=0*%do-ayvy*m2k=vss*$7)j8q!@u0+d^na7mi2(^!l!d' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'blah.urls' WSGI_APPLICATION = 'hackathon_starter.wsgi.application' # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True """ djts = open("dj_test_settings.py", "w") djts.write(settings) djts.close() app = get_django_wsgi('dj_test_settings') os.remove('dj_test_settings.py') os.remove('dj_test_settings.pyc')
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_zip_path = getattr(self.settings, 'ZIP_PATH', None) if project_zip_path: self.load_remote_project_zip(project_zip_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)
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
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.
def test_dj_wsgi(self): # Sanity settings_modules = detect_django_settings() settings = """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'alskdfjalsdkf=0*%do-ayvy*m2k=vss*$7)j8q!@u0+d^na7mi2(^!l!d' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'blah.urls' WSGI_APPLICATION = 'hackathon_starter.wsgi.application' # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Internationalization # https://docs.djangoproject.com/en/1.7/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True """ djts = open("dj_test_settings.py", "w") djts.write(settings) djts.close() app = get_django_wsgi('dj_test_settings') os.remove('dj_test_settings.py') os.remove('dj_test_settings.pyc')
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