-
-
Notifications
You must be signed in to change notification settings - Fork 130
Feature - Websockets #190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Feature - Websockets #190
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
549b407
Adds original websockets implementation
eduardovra 89542cf
Adds websockets backends
eduardovra ff16bf6
Adds original test files for websockets
eduardovra e2ee064
WIP for the connect event
eduardovra 213c39b
WIP websockets
eduardovra eadd3dc
Renamed file for websocket protocol
eduardovra 38f914b
Adds websockets docs
eduardovra d1f7333
Fixed websockets tests
eduardovra 8d27e66
WIP - websockets
eduardovra 814d255
Fixed tests for sqlite and redis backends
eduardovra 0e2c067
Fixed S3 backend tests
eduardovra 3e3b781
S3 tests using mock server
eduardovra 909167b
Basic tests for dynamodb passing
eduardovra f74f0bf
Fix redis backend
eduardovra d0a9d56
Fix postgresql tests
eduardovra 3a7b52b
Fixed all tests
eduardovra 505da43
Adds tests dependencies for websockets
eduardovra 2003acc
Adds OS package dependencies
eduardovra 88c3ce3
Fixed most of linter problems
eduardovra e37cdbe
Fixed remaining linter problems
eduardovra e8e741c
Implemented backend connect function using a context manager
eduardovra d61629b
Fixed dependencies for tests on 3.8
eduardovra a1bd662
Tests and linters passing on 3.6
eduardovra 54f71b6
100% coverage
eduardovra 242f73b
Remove aws.context from data storage
eduardovra 713bd59
Use relative import paths
eduardovra 9efc356
Undo changes in base type for Scope
eduardovra c2fde58
Standardize s3 and dynamodb query parameters
eduardovra 7cf7c8b
Replace print statements with logging calls on mock server
eduardovra a4017a8
Use respx to mock httpx on tests
eduardovra 64c788b
Add test for scope layout
eduardovra 03adecf
Instancing the backend in the main adapter class
eduardovra 8436958
Implement context manager for the backends
eduardovra 0d555cf
Fix connection url on the backend
eduardovra 261e44e
Fix broken dependency
eduardovra 8cf22c3
Move integration tests into the tests/integrations folder and use doc…
eduardovra 8902526
Updated the documentation on WebSocket support
eduardovra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,300 @@ | ||
| # WebSockets | ||
|
|
||
| Mangum provides support for [WebSocket API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html) events in API Gateway. The adapter class handles parsing the incoming requests and managing the ASGI cycle using a configured storage backend. | ||
|
|
||
| ```python | ||
| import os | ||
|
|
||
| from fastapi import FastAPI, WebSocket | ||
| from fastapi.responses import HTMLResponse | ||
| from mangum import Mangum | ||
|
|
||
| DSN_URL = os.environ["DSN_URL"] | ||
| WEBSOCKET_URL = os.environ["WEBSOCKET_URL"] | ||
| HTML = """ | ||
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Chat</title> | ||
| </head> | ||
|
|
||
| <body> | ||
| <h1>WebSocket Chat</h1> | ||
| <form action="" onsubmit="sendMessage(event)"> | ||
| <input type="text" id="messageText" autocomplete="off"/> | ||
| <button>Send</button> | ||
| </form> | ||
|
|
||
| <ul id='messages'></ul> | ||
|
|
||
| <script> | ||
| var ws = new WebSocket('%s'); | ||
| ws.onmessage = function(event) { | ||
| var messages = document.getElementById('messages') | ||
| var message = document.createElement('li') | ||
| var content = document.createTextNode(event.data) | ||
| message.appendChild(content) | ||
| messages.appendChild(message) | ||
| }; | ||
| function sendMessage(event) { | ||
| var input = document.getElementById('messageText') | ||
| ws.send(input.value) | ||
| input.value = '' | ||
| event.preventDefault() | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> | ||
| """ % WEBSOCKET_URL | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| @app.get("/") | ||
| async def get(): | ||
| return HTMLResponse(HTML) | ||
|
|
||
| @app.websocket("/") | ||
| async def websocket_endpoint(websocket: WebSocket): | ||
| await websocket.accept() | ||
| while True: | ||
| data = await websocket.receive_text() | ||
| await websocket.send_text(f"Message text was: {data}") | ||
|
|
||
| handler = Mangum(app, dsn=DSN_URL) | ||
| ``` | ||
|
|
||
| ## Dependencies | ||
|
|
||
| The WebSocket implementation requires the following extra packages: | ||
|
|
||
| ``` | ||
| pip install httpx boto3 | ||
| ``` | ||
|
|
||
| ## Configuring a storage backend | ||
|
|
||
| A data source is required in order to persist the WebSocket client connections stored in API Gateway*. Any data source can be used as long as it is accessible remotely to the AWS Lambda function. All supported backends require a `dsn` connection string argument to configure the connection between the adapter and the data source. | ||
|
|
||
| ```python | ||
| handler = Mangum(app, dsn="[postgresql|redis|dynamodb|s3|sqlite]://[...]") | ||
| ``` | ||
|
|
||
| <small>*Read the section on ([handling events in API Gateway](https://mangum.io/websockets/#handling-api-gateway-events) for more information.)</small> | ||
|
|
||
| ### Supported backends | ||
|
|
||
| The following backends are currently supported: | ||
|
|
||
| - `dynamodb` | ||
| - `s3` | ||
| - `postgresql` | ||
| - `redis` | ||
| - `sqlite` (for local debugging) | ||
|
|
||
| #### DynamoDB | ||
|
|
||
| The `DynamoDBBackend` uses a [DynamoDB](https://aws.amazon.com/dynamodb/) table to store the connection details. | ||
|
|
||
| ##### Usage | ||
|
|
||
| ```python | ||
| handler = Mangum( | ||
| app, | ||
| dsn="dynamodb://mytable" | ||
| ) | ||
| ``` | ||
|
|
||
| ###### Parameters | ||
|
|
||
| The DynamoDB backend `dsn` uses the following connection string syntax: | ||
|
|
||
| ``` | ||
| dynamodb://<table_name>[?region=<region-name>&endpoint_url=<url>] | ||
| ``` | ||
|
|
||
| - `table_name` (Required) | ||
|
|
||
| The name of the table in DynamoDB. | ||
|
|
||
| - `region_name` | ||
|
|
||
| The region name of the DynamoDB table. | ||
|
|
||
| - `endpoint_url` | ||
|
|
||
| The endpoint url to use in DynamoDB calls. This is useful if you are debugging locally with a package such as [serverless-dynamodb-local](https://github.com/99xt/serverless-dynamodb-local). | ||
|
|
||
| ###### Dependencies | ||
|
|
||
| This backend requires the following extra package: | ||
|
|
||
| ``` | ||
| pip install aioboto3 | ||
| ``` | ||
|
|
||
| #### S3 | ||
|
|
||
| The `S3Backend` uses an [S3](https://aws.amazon.com/s3/) bucket as a key-value store to store the connection details. | ||
|
|
||
| ##### Usage | ||
|
|
||
| ```python | ||
| handler = Mangum( | ||
| app, | ||
| dsn="s3://my-bucket-12345" | ||
| ) | ||
| ``` | ||
|
|
||
| ###### Parameters | ||
|
|
||
| The S3 backend `dsn` uses the following connection string syntax: | ||
|
|
||
| ``` | ||
| s3://<bucket>[/key/...][?region=<region-name>&endpoint_url=<url>] | ||
| ``` | ||
|
|
||
| - `bucket` (Required) | ||
|
|
||
| The name of the bucket in S3. | ||
|
|
||
| - `region_name` | ||
|
|
||
| The region name of the S3 bucket. | ||
|
|
||
| - `endpoint_url` | ||
|
|
||
| The endpoint url to use in S3 calls. This is useful if you are debugging locally with a package such as [serverless-s3-local](https://github.com/ar90n/serverless-s3-local). | ||
|
|
||
| ###### Dependencies | ||
|
|
||
| This backend requires the following extra package: | ||
|
|
||
| ``` | ||
| pip install aioboto3 | ||
| ``` | ||
|
|
||
| #### PostgreSQL | ||
|
|
||
| The `PostgreSQLBackend` requires [psycopg2](https://github.com/psycopg/psycopg2) and access to a remote PostgreSQL database. | ||
|
|
||
| ##### Usage | ||
|
|
||
| ```python | ||
| handler = Mangum( | ||
| app, | ||
| dsn="postgresql://myuser:mysecret@my.host:5432/mydb" | ||
| ) | ||
| ``` | ||
|
|
||
| ###### Parameters | ||
|
|
||
| The PostgreSQL backend `dsn` uses the following connection string syntax: | ||
|
|
||
| ``` | ||
| postgresql://[user[:password]@][host][:port][,...][/dbname][?param1=value1&...] | ||
| ``` | ||
|
|
||
| - `host` (Required) | ||
|
|
||
| The network location of the PostgreSQL database | ||
|
|
||
| Read more about the supported uri schemes and additional parameters [here](https://www.postgresql.org/docs/10/libpq-connect.html#LIBPQ-CONNSTRING). | ||
|
|
||
| ###### Dependencies | ||
|
|
||
| This backend requires the following extra package: | ||
|
|
||
| ``` | ||
| pip install aiopg | ||
| ``` | ||
|
|
||
| #### Redis | ||
|
|
||
| The `RedisBackend` requires [redis-py](https://github.com/andymccurdy/redis-py) and access to a Redis server. | ||
|
|
||
| ##### Usage | ||
|
|
||
| ```python | ||
| handler = Mangum( | ||
| app, | ||
| dsn="redis://:mysecret@my.host:6379/0" | ||
| ) | ||
| ``` | ||
|
|
||
| ##### Parameters | ||
|
|
||
| The Redis backend `dsn` uses the following connection string syntax: | ||
|
|
||
| ``` | ||
| redis://[[user:]password@]host[:port][/database] | ||
| ``` | ||
|
|
||
| - `host` (Required) | ||
|
|
||
| The network location of the Redis server. | ||
|
|
||
| Read more about the supported uri schemes and additional parameters [here](https://www.iana.org/assignments/uri-schemes/prov/redis). | ||
|
|
||
| ###### Dependencies | ||
|
|
||
| This backend requires the following extra package: | ||
|
|
||
| ``` | ||
| pip install aioredis | ||
| ``` | ||
|
|
||
| #### SQLite | ||
|
|
||
| The `sqlite` backend uses a local [sqlite3](https://docs.python.org/3/library/sqlite3.html) database to store connection. It is intended for local debugging. | ||
|
|
||
| ##### Usage | ||
|
|
||
| ```python | ||
| handler = Mangum( | ||
| app, | ||
| dsn="sqlite://mydbfile.sqlite3" | ||
| ) | ||
| ``` | ||
|
|
||
| ##### Parameters | ||
|
|
||
| The SQLite backend uses the following connection string syntax: | ||
|
|
||
| ``` | ||
| sqlite://[file_path].db | ||
| ``` | ||
|
|
||
| - `file_path` (Required) | ||
|
|
||
| The file name or path to an sqlite3 database file. If one does not exist, then it will be created automatically. | ||
|
|
||
| ## State machine | ||
|
|
||
| The `WebSocketCycle` is used by the adapter to communicate message events between the application and WebSocket client connections in API Gateway using a storage backend to persist the connection `scope`. It is a state machine that handles the ASGI request and response cycle for each individual message sent by a client. | ||
|
|
||
| ### WebSocketCycle | ||
|
|
||
| ::: mangum.protocols.websocket.WebSocketCycle | ||
| :docstring: | ||
| :members: run receive send | ||
|
|
||
| #### API Gateway events | ||
|
|
||
| There are three WebSocket events sent by API Gateway for a WebSocket API connection. Each event requires returning a response immediately, and the information required to create the connection scope is only available in the initial `CONNECT` event. Messages are only sent in `MESSAGE` events that occur after the initial connection is established, and they do not include the details of the initial connect event. Due to the stateless nature of AWS Lambda, a storage backend is required to persist the WebSocket connection details for the duration of a client connection. | ||
|
|
||
| ##### CONNECT | ||
|
|
||
| A persistent connection between the client and a WebSocket API is being initiated. The adapter uses a supported WebSocket backend to store the connection id and initial request information. | ||
|
|
||
| ##### MESSAGE | ||
|
|
||
| A connected client has sent a message. The adapter will retrieve the initial request information from the backend using the connection id to form the ASGI connection `scope` and run the ASGI application cycle. | ||
|
|
||
| ##### DISCONNECT | ||
|
|
||
| The client or the server disconnects from the API. The adapter will remove the connection from the backend. | ||
|
|
||
| ### WebSocketCycleState | ||
|
|
||
| ::: mangum.protocols.websockets.WebSocketCycleState | ||
| :docstring: | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.