- Notifications
You must be signed in to change notification settings - Fork89
Serverless plugin to deploy WSGI applications (Flask/Django/Pyramid etc.) and bundle Python packages
License
logandk/serverless-wsgi
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
A Serverless Framework plugin to build your deploy Python WSGI applications using Serverless. CompatibleWSGI application frameworks include Flask, Django and Pyramid - for a complete list, see:http://wsgi.readthedocs.io/en/latest/frameworks.html.
- Transparently converts API Gateway and ALB requests to and from standard WSGI requests
- Supports anything you'd expect from WSGI such as redirects, cookies, file uploads etc.
- Automatically downloads Python packages that you specify in
requirements.txtand deploys them along with your application - Convenient
wsgi servecommand for serving your application locally during development - Includes 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 payloads
sls plugin install -n serverless-wsgiThis will automatically add the plugin topackage.json and the plugins section ofserverless.yml.
This example assumes that you have intialized your application asapp insideapi.py.
project├── api.py├── requirements.txt└── serverless.ymlA regular Flask application.
fromflaskimportFlaskapp=Flask(__name__)@app.route("/cats")defcats():return"Cats"@app.route("/dogs/<id>")defdog(id):return"Dog"
Load the plugin and set thecustom.wsgi.app configuration inserverless.yml to themodule path of your Flask application.
All functions that will use WSGI need to havewsgi_handler.handler set as the Lambda handler anduse the defaultlambda-proxy integration for API Gateway. This configuration example treatsAPI 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.handler earlier, 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.app
Add Flask to the application bundle.
Flask==1.0.2Simply run the serverless deploy command as usual:
$ sls deployServerless: Using Python specified in "runtime": python3.6Serverless: 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...Setcustom.wsgi.app inserverless.yml according to your WSGI callable:
- For Pyramid, usemake_wsgi_app to intialize the callable
- Django 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.
You'll need to include any packages that your application uses in the bundlethat's deployed to AWS Lambda. This plugin helps you out by doing this automatically,as long as you specify your required packages in arequirements.txt file in the rootof your Serverless service path:
Flask==1.0.2requests==2.21.0For more information, seehttps://pip.pypa.io/en/latest/user_guide/#requirements-files.
Theserverless-wsgi plugin itself depends onwerkzeug and will package it automatically,even ifwerkzeug is not present in yourrequirements.txt.
You can use the requirement packaging functionality ofserverless-wsgi without the WSGIhandler itself by including the plugin in yourserverless.yml configuration, without specifyingthecustom.wsgi.app setting. This will omit the WSGI handler from the package, but includeany requirements specified inrequirements.txt.
If you don't want to use automatic requirement packaging you can setcustom.wsgi.packRequirements to false:
custom:wsgi:app:api.apppackRequirements:false
In order to pass additional arguments topip when installing requirements, thepipArgs configurationoption is available:
custom:wsgi:app:api.apppipArgs:--no-deps
For a more advanced approach to packaging requirements, consider usinghttps://github.com/UnitedIncome/serverless-python-requirements.When theserverless-python-requirements is added toserverless.yml, thepackRequirements optionis set tofalse by default.
If you havepackRequirements set tofalse, or if you useserverless-python-requirements, remember to addwerkzeug explicitly in yourrequirements.txt.
Python is used for packaging requirements and serving the app when invokingsls wsgi serve. Bydefault, 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 thepythonBin option:
custom:wsgi:app:api.apppythonBin:python3
For convenience, asls wsgi serve command is provided to run your WSGI applicationlocally. This command requires thewerkzeug Python 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-p parameter:
$ 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_OFFLINE will be set toTrue.So, if you want to know when the application is running locally, checkos.environ["IS_OFFLINE"].
Thewsgi exec command lets you execute Python code remotely:
$ sls wsgi exec -c "import math; print((1 + math.sqrt(5)) / 2)"1.618033988749895$ cat count.pyfor i in range(3): print(i)$ sls wsgi exec -f count.py012Thewsgi command command lets you execute shell commands remotely:
$ sls wsgi command -c "pwd"/var/task$ cat script.sh#!/bin/bashecho "dlrow olleh" | rev$ sls wsgi command -f script.shhello worldThewsgi flask command lets you executeFlask CLI custom commands remotely:
$ sls wsgi flask -c "my command"Hello world!Thewsgi manage command lets you execute Django management commands remotely:
$ sls wsgi manage -c "check --list-tags"admincachesdatabasemodelsstaticfilestemplatesurlsAll commands havelocal equivalents that let you run commands throughsls invoke local ratherthansls invoke, i.e. on the local machine instead of through Lambda. Thelocal commands (sls wsgi command local,sls wsgi exec local,sls wsgi flask local andsls wsgi manage local) take the same argumentsas their remote counterparts documented above.
If you'd like to be explicit about which routes and HTTP methods should pass through to yourapplication, 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.app
If you use custom domain names with API Gateway, you might have a base path that isat the beginning of your path, such as the stage (/dev,/stage,/prod). In this case, settheAPI_GATEWAY_BASE_PATH environment variable to letserverless-wsgi know.E.g, if you deploy your WSGI application tohttps://mydomain.com/api/myservice,setAPI_GATEWAY_BASE_PATH toapi/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:true
Note: TheAPI_GATEWAY_BASE_PATH configuration is only needed when using the payload V1. In the V2, the path does not have thebasePath in the beginning.
If you're configuring CloudFront manually in front of your API and settingthe Path in the CloudFront Origin to include your stage name, you'll needto strip it out from the path supplied to WSGI. This is so that your appdoesn't generate URLs starting with/production.
Pass theSTRIP_STAGE_PATH=yes environment variable to your applicationto set this:
service:exampleprovider:name:awsruntime:python3.6environment:STRIP_STAGE_PATH:yes
In order to accept file uploads from HTML forms, make sure to addmultipart/form-data tothe list of content types withBinary Support in 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.
The raw context and event from AWS Lambda are both accessible through the WSGIrequest. 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.
By default, all MIME types starting withtext/ and the following whitelist are sentthrough API Gateway in plain text. All other MIME types will have their response bodybase64 encoded (and theisBase64Encoded API Gateway flag set) in order to bedelivered by API Gateway as binary data (remember to add any binary MIME types thatyou're using to theBinary Support list in API Gateway).
This is the default whitelist of plain text MIME types:
application/jsonapplication/javascriptapplication/xmlapplication/vnd.api+jsonimage/svg+xml
In order to add additional plain text MIME types to this whitelist, use thetextMimeTypes configuration option:
custom:wsgi:app:api.apptextMimeTypes: -application/custom+json -application/vnd.company+json
Common ways to keep lambda functions warm includescheduled eventsand theWarmUP plugin. Both these event sourcesare supported by default and will be ignored byserverless-wsgi.
If you have several functions inserverless.yml and want to organize them indirectories, e.g.:
project├── web│ ├── api.py│ └── requirements.txt├── serverless.yml└── another_function.pyIn this case, tellserverless-wsgi where to find the handler by prepending thedirectory:
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.app
Requirements will now be installed intoweb/, rather than at in the service root directory.
The same rule applies when using theindividually: true flag in thepackage settings, togetherwith themodule option provided byserverless-python-requirements. In that case, both the requirementsand the WSGI handler will be installed intoweb/, if the function is configured withmodule: "web".
The AWS API Gateway to WSGI mapping module is available on PyPI in theserverless-wsgi package.
Use this package if you need to deploy Python Lambda functions to handleAPI Gateway events directly, without using the Serverless framework.
pip install serverless-wsgiInitialize your WSGI application and in your Lambda event handler, callthe 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)
Thanks toZappa, which has been both theinspiration and source of several implementations that went into this project.
Thanks tochalice for therequirement packaging implementation.
About
Serverless plugin to deploy WSGI applications (Flask/Django/Pyramid etc.) and bundle Python packages
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.