Package: serverless-wsgi
A Serverless Framework plugin to build your deploy Python WSGI applications using Serverless. Compatible
WSGI application frameworks include Flask, Django and Pyramid - for a complete list, see:http://wsgi.readthedocs.io/en/latest/frameworks.html.FeaturesTransparently converts API Gateway and ALB requests to and from standard WSGI requestsSupports anything you'd expect from WSGI such as redirects, cookies, file uploads etc.Automatically downloads Python packages that you specify inrequirements.txtand deploys them along with your applicationConvenientwsgi servecommand for serving your application locally during developmentIncludes CLI commands for remote execution of Python code (wsgi exec), shell commands (wsgi command), Flask CLI commands (wsgi flask) and Django management commands (wsgi manage)Supports both APIGatewayV1 and APIGatewayV2 payloadsInstallsls plugin install -n serverless-wsgiThis will automatically add the plugin topackage.jsonand the plugins section ofserverless.yml.Flask configuration exampleThis example assumes that you have intialized your application asappinsideapi.py.project
├── api.py
├── requirements.txt
└── serverless.ymlapi.pyA regular Flask application.fromflaskimportFlaskapp=Flask(__name__)@app.route("/cats")defcats():return"Cats"@app.route("/dogs/<id>")defdog(id):return"Dog"serverless.ymlLoad the plugin and set thecustom.wsgi.appconfiguration inserverless.ymlto the
module path of your Flask application.All functions that will use WSGI need to havewsgi_handler.handlerset as the Lambda handler and
use the defaultlambda-proxyintegration for API Gateway. This configuration example treats
API Gateway as a transparent proxy, passing all requests directly to your Flask application,
and letting the application handle errors, 404s etc.Note: The WSGI handler was calledwsgi.handlerearlier, but was renamed towsgi_handler.handlerin1.7.0. The old name is still supported but using it will cause a deprecation warning.service:exampleprovider:name:awsruntime:python3.6plugins:-serverless-wsgifunctions:api:handler:wsgi_handler.handlerevents:-http:ANY /-http:ANY /{proxy+}custom:wsgi:app:api.apprequirements.txtAdd Flask to the application bundle.Flask==1.0.2DeploymentSimply run the serverless deploy command as usual:$ sls deploy
Serverless: Using Python specified in "runtime": python3.6
Serverless: Packaging Python WSGI handler...
Serverless: Packaging required Python packages...
Serverless: Linking required Python packages...
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Unlinking required Python packages...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (864.57 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...Other frameworksSetcustom.wsgi.appinserverless.ymlaccording to your WSGI callable:For Pyramid, usemake_wsgi_appto intialize the callableDjango is configured for WSGI by default, set the callable to<project_name>.wsgi.application. Seehttps://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/for more information.UsageAutomatic requirement packagingYou'll need to include any packages that your application uses in the bundle
that's deployed to AWS Lambda. This plugin helps you out by doing this automatically,
as long as you specify your required packages in arequirements.txtfile in the root
of your Serverless service path:Flask==1.0.2
requests==2.21.0For more information, seehttps://pip.pypa.io/en/latest/user_guide/#requirements-files.Theserverless-wsgiplugin itself depends onwerkzeugand will package it automatically,
even ifwerkzeugis not present in yourrequirements.txt.You can use the requirement packaging functionality ofserverless-wsgiwithout the WSGI
handler itself by including the plugin in yourserverless.ymlconfiguration, without specifying
thecustom.wsgi.appsetting. This will omit the WSGI handler from the package, but include
any requirements specified inrequirements.txt.If you don't want to use automatic requirement packaging you can setcustom.wsgi.packRequirementsto false:custom:wsgi:app:api.apppackRequirements:falseIn order to pass additional arguments topipwhen installing requirements, thepipArgsconfiguration
option is available:custom:wsgi:app:api.apppipArgs:--no-depsFor a more advanced approach to packaging requirements, consider usinghttps://github.com/UnitedIncome/serverless-python-requirements.
When theserverless-python-requirementsis added toserverless.yml, thepackRequirementsoption
is set tofalseby default.If you havepackRequirementsset tofalse, or if you useserverless-python-requirements, remember to addwerkzeugexplicitly in yourrequirements.txt.Python versionPython is used for packaging requirements and serving the app when invokingsls wsgi serve. By
default, the current runtime setting is expected to be the name of the Python binary inPATH,
for instancepython3.6. If this is not the name of your Python binary, override it using thepythonBinoption:custom:wsgi:app:api.apppythonBin:python3Local serverFor convenience, asls wsgi servecommand is provided to run your WSGI application
locally. This command requires thewerkzeugPython package to be installed,
and acts as a simple wrapper for starting werkzeug's built-in HTTP server.By default, the server will start on port 5000.
(Note: macOSreserves port 5000for AirPlay by default, see below for instructions on changing the port.)$ sls wsgi serve
 * Running on http://localhost:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!Configure the port using the-pparameter:$ sls wsgi serve -p 8000
 * Running on http://localhost:8000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!When running locally, an environment variable namedIS_OFFLINEwill be set toTrue.
So, if you want to know when the application is running locally, checkos.environ["IS_OFFLINE"].Remote command executionThewsgi execcommand lets you execute Python code remotely:$ sls wsgi exec -c "import math; print((1 + math.sqrt(5)) / 2)"
1.618033988749895

$ cat count.py
for i in range(3):
    print(i)

$ sls wsgi exec -f count.py
0
1
2Thewsgi commandcommand lets you execute shell commands remotely:$ sls wsgi command -c "pwd"
/var/task

$ cat script.sh
#!/bin/bash
echo "dlrow olleh" | rev

$ sls wsgi command -f script.sh
hello worldThewsgi flaskcommand lets you executeFlask CLI custom commandsremotely:$ sls wsgi flask -c "my command"
Hello world!Thewsgi managecommand lets you execute Django management commands remotely:$ sls wsgi manage -c "check --list-tags"
admin
caches
database
models
staticfiles
templates
urlsAll commands havelocalequivalents that let you run commands throughsls invoke localrather
thansls invoke, i.e. on the local machine instead of through Lambda. Thelocalcommands (sls wsgi command local,sls wsgi exec local,sls wsgi flask localandsls wsgi manage local) take the same arguments
as their remote counterparts documented above.Explicit routesIf you'd like to be explicit about which routes and HTTP methods should pass through to your
application, see the following example:service:exampleprovider:name:awsruntime:python3.6plugins:-serverless-wsgifunctions:api:handler:wsgi_handler.handlerevents:-http:path:catsmethod:getintegration:lambda-proxy-http:path:dogs/{id}method:getintegration:lambda-proxycustom:wsgi:app:api.appCustom domain namesIf you use custom domain names with API Gateway, you might have a base path that is
at the beginning of your path, such as the stage (/dev,/stage,/prod). In this case, set
theAPI_GATEWAY_BASE_PATHenvironment variable to letserverless-wsgiknow.
E.g, if you deploy your WSGI application tohttps://mydomain.com/api/myservice,
setAPI_GATEWAY_BASE_PATHtoapi/myservice(no/first).The example below uses theserverless-domain-managerplugin to handle custom domains in API Gateway:service:exampleprovider:name:awsruntime:python3.6environment:API_GATEWAY_BASE_PATH:${self:custom.customDomain.basePath}plugins:-serverless-wsgi-serverless-domain-managerfunctions:api:handler:wsgi_handler.handlerevents:-http:ANY /-http:ANY {proxy+}custom:wsgi:app:api.appcustomDomain:basePath:${opt:stage}domainName:mydomain.name.comstage:${opt:stage}createRoute53Record:trueNote: TheAPI_GATEWAY_BASE_PATHconfiguration is only needed when using the payload V1. In the V2, the path does not have thebasePathin the beginning.Using CloudFrontIf you're configuring CloudFront manually in front of your API and setting
the Path in the CloudFront Origin to include your stage name, you'll need
to strip it out from the path supplied to WSGI. This is so that your app
doesn't generate URLs starting with/production.Pass theSTRIP_STAGE_PATH=yesenvironment variable to your application
to set this:service:exampleprovider:name:awsruntime:python3.6environment:STRIP_STAGE_PATH:yesFile uploadsIn order to accept file uploads from HTML forms, make sure to addmultipart/form-datato
the list of content types withBinary Supportin your API Gateway API. Theserverless-apigw-binaryServerless plugin can be used to automate this process.Keep in mind that, when building Serverless applications, uploadingdirectly to S3from the browser is usually the preferred approach.Raw context and eventThe raw context and event from AWS Lambda are both accessible through the WSGI
request. The following example shows how to access them when using Flask:fromflaskimportFlask,requestapp=Flask(__name__)@app.route("/")defindex():print(request.environ['serverless.context'])print(request.environ['serverless.event'])For more information on these objects, read the documentation oneventsand theinvocation context.Text MIME typesBy default, all MIME types starting withtext/and the following whitelist are sent
through API Gateway in plain text. All other MIME types will have their response body
base64 encoded (and theisBase64EncodedAPI Gateway flag set) in order to be
delivered by API Gateway as binary data (remember to add any binary MIME types that
you're using to theBinary Supportlist in API Gateway).This is the default whitelist of plain text MIME types:application/jsonapplication/javascriptapplication/xmlapplication/vnd.api+jsonimage/svg+xmlIn order to add additional plain text MIME types to this whitelist, use thetextMimeTypesconfiguration option:custom:wsgi:app:api.apptextMimeTypes:-application/custom+json-application/vnd.company+jsonPreventing cold startsCommon ways to keep lambda functions warm includescheduled eventsand theWarmUP plugin. Both these event sources
are supported by default and will be ignored byserverless-wsgi.Alternative directory structureIf you have several functions inserverless.ymland want to organize them in
directories, e.g.:project
├── web
│   ├── api.py
│   └── requirements.txt
├── serverless.yml
└── another_function.pyIn this case, tellserverless-wsgiwhere to find the handler by prepending the
directory:service:exampleprovider:name:awsruntime:python3.6plugins:-serverless-wsgifunctions:api:handler:wsgi_handler.handlerevents:-http:ANY /-http:ANY {proxy+}another_function:handler:another_function.handlercustom:wsgi:app:web/api.appRequirements will now be installed intoweb/, rather than at in the service root directory.The same rule applies when using theindividually: trueflag in thepackagesettings, together
with themoduleoption provided byserverless-python-requirements. In that case, both the requirements
and the WSGI handler will be installed intoweb/, if the function is configured withmodule: "web".Usage without ServerlessThe AWS API Gateway to WSGI mapping module is available on PyPI in theserverless-wsgipackage.Use this package if you need to deploy Python Lambda functions to handle
API Gateway events directly, without using the Serverless framework.pip install serverless-wsgiInitialize your WSGI application and in your Lambda event handler, call
the request mapper:importapp# Replace with your actual applicationimportserverless_wsgi# If you need to send additional content types as text, add then directly# to the whitelist:## serverless_wsgi.TEXT_MIME_TYPES.append("application/custom+json")defhandler(event,context):returnserverless_wsgi.handle_request(app.app,event,context)ThanksThanks toZappa, which has been both the
inspiration and source of several implementations that went into this project.Thanks tochalicefor the
requirement packaging implementation.