Skip to content

Commit 17aa446

Browse files
eduardovrakhamaileon
authored andcommitted
Feature - Websockets (Kludex#190)
Reintroduce WebSocket support.
1 parent 7519227 commit 17aa446

28 files changed

Lines changed: 1973 additions & 25 deletions

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) ap
1414

1515
## Features
1616

17-
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs.
17+
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html), [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html), and [WebSocket](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html) APIs.
18+
19+
- Multiple storage backend interfaces for managing WebSocket connections.
1820

1921
- Compatibility with ASGI application frameworks, such as [Starlette](https://www.starlette.io/), [FastAPI](https://fastapi.tiangolo.com/), and [Quart](https://pgjones.gitlab.io/quart/).
2022

docs/adapter.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,15 @@ handler = Mangum(
88
lifespan="auto",
99
log_level="info",
1010
api_gateway_base_path=None,
11-
text_mime_types=None
11+
text_mime_types=None,
12+
dsn=None,
13+
api_gateway_endpoint_url=None,
14+
api_gateway_region_name=None
1215
)
1316
```
1417

18+
All arguments are optional, but some may be necessary for specific use-cases (e.g. dsn is only required for WebSocket support).
19+
1520
## Configuring an adapter instance
1621

1722
::: mangum.adapter.Mangum

docs/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) ap
1414

1515
## Features
1616

17-
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs.
17+
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html), [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html), and [WebSocket](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html) APIs.
18+
19+
- Multiple storage backend interfaces for managing WebSocket connections.
1820

1921
- Compatibility with ASGI application frameworks, such as [Starlette](https://www.starlette.io/), [FastAPI](https://fastapi.tiangolo.com/), and [Quart](https://pgjones.gitlab.io/quart/).
2022

docs/websockets.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# WebSockets
2+
3+
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.
4+
5+
```python
6+
import os
7+
8+
from fastapi import FastAPI, WebSocket
9+
from fastapi.responses import HTMLResponse
10+
from mangum import Mangum
11+
12+
DSN_URL = os.environ["DSN_URL"]
13+
WEBSOCKET_URL = os.environ["WEBSOCKET_URL"]
14+
HTML = """
15+
<!DOCTYPE html>
16+
<html>
17+
<head>
18+
<title>Chat</title>
19+
</head>
20+
21+
<body>
22+
<h1>WebSocket Chat</h1>
23+
<form action="" onsubmit="sendMessage(event)">
24+
<input type="text" id="messageText" autocomplete="off"/>
25+
<button>Send</button>
26+
</form>
27+
28+
<ul id='messages'></ul>
29+
30+
<script>
31+
var ws = new WebSocket('%s');
32+
ws.onmessage = function(event) {
33+
var messages = document.getElementById('messages')
34+
var message = document.createElement('li')
35+
var content = document.createTextNode(event.data)
36+
message.appendChild(content)
37+
messages.appendChild(message)
38+
};
39+
function sendMessage(event) {
40+
var input = document.getElementById('messageText')
41+
ws.send(input.value)
42+
input.value = ''
43+
event.preventDefault()
44+
}
45+
</script>
46+
</body>
47+
</html>
48+
""" % WEBSOCKET_URL
49+
50+
app = FastAPI()
51+
52+
@app.get("/")
53+
async def get():
54+
return HTMLResponse(HTML)
55+
56+
@app.websocket("/")
57+
async def websocket_endpoint(websocket: WebSocket):
58+
await websocket.accept()
59+
while True:
60+
data = await websocket.receive_text()
61+
await websocket.send_text(f"Message text was: {data}")
62+
63+
handler = Mangum(app, dsn=DSN_URL)
64+
```
65+
66+
## Dependencies
67+
68+
The WebSocket implementation requires the following extra packages:
69+
70+
```
71+
pip install httpx boto3
72+
```
73+
74+
## Configuring a storage backend
75+
76+
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.
77+
78+
```python
79+
handler = Mangum(app, dsn="[postgresql|redis|dynamodb|s3|sqlite]://[...]")
80+
```
81+
82+
<small>*Read the section on ([handling events in API Gateway](https://mangum.io/websockets/#handling-api-gateway-events) for more information.)</small>
83+
84+
### Supported backends
85+
86+
The following backends are currently supported:
87+
88+
- `dynamodb`
89+
- `s3`
90+
- `postgresql`
91+
- `redis`
92+
- `sqlite` (for local debugging)
93+
94+
#### DynamoDB
95+
96+
The `DynamoDBBackend` uses a [DynamoDB](https://aws.amazon.com/dynamodb/) table to store the connection details.
97+
98+
##### Usage
99+
100+
```python
101+
handler = Mangum(
102+
app,
103+
dsn="dynamodb://mytable"
104+
)
105+
```
106+
107+
###### Parameters
108+
109+
The DynamoDB backend `dsn` uses the following connection string syntax:
110+
111+
```
112+
dynamodb://<table_name>[?region=<region-name>&endpoint_url=<url>]
113+
```
114+
115+
- `table_name` (Required)
116+
117+
The name of the table in DynamoDB.
118+
119+
- `region_name`
120+
121+
The region name of the DynamoDB table.
122+
123+
- `endpoint_url`
124+
125+
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).
126+
127+
###### Dependencies
128+
129+
This backend requires the following extra package:
130+
131+
```
132+
pip install aioboto3
133+
```
134+
135+
#### S3
136+
137+
The `S3Backend` uses an [S3](https://aws.amazon.com/s3/) bucket as a key-value store to store the connection details.
138+
139+
##### Usage
140+
141+
```python
142+
handler = Mangum(
143+
app,
144+
dsn="s3://my-bucket-12345"
145+
)
146+
```
147+
148+
###### Parameters
149+
150+
The S3 backend `dsn` uses the following connection string syntax:
151+
152+
```
153+
s3://<bucket>[/key/...][?region=<region-name>&endpoint_url=<url>]
154+
```
155+
156+
- `bucket` (Required)
157+
158+
The name of the bucket in S3.
159+
160+
- `region_name`
161+
162+
The region name of the S3 bucket.
163+
164+
- `endpoint_url`
165+
166+
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).
167+
168+
###### Dependencies
169+
170+
This backend requires the following extra package:
171+
172+
```
173+
pip install aioboto3
174+
```
175+
176+
#### PostgreSQL
177+
178+
The `PostgreSQLBackend` requires [psycopg2](https://github.com/psycopg/psycopg2) and access to a remote PostgreSQL database.
179+
180+
##### Usage
181+
182+
```python
183+
handler = Mangum(
184+
app,
185+
dsn="postgresql://myuser:mysecret@my.host:5432/mydb"
186+
)
187+
```
188+
189+
###### Parameters
190+
191+
The PostgreSQL backend `dsn` uses the following connection string syntax:
192+
193+
```
194+
postgresql://[user[:password]@][host][:port][,...][/dbname][?param1=value1&...]
195+
```
196+
197+
- `host` (Required)
198+
199+
The network location of the PostgreSQL database
200+
201+
Read more about the supported uri schemes and additional parameters [here](https://www.postgresql.org/docs/10/libpq-connect.html#LIBPQ-CONNSTRING).
202+
203+
###### Dependencies
204+
205+
This backend requires the following extra package:
206+
207+
```
208+
pip install aiopg
209+
```
210+
211+
#### Redis
212+
213+
The `RedisBackend` requires [redis-py](https://github.com/andymccurdy/redis-py) and access to a Redis server.
214+
215+
##### Usage
216+
217+
```python
218+
handler = Mangum(
219+
app,
220+
dsn="redis://:mysecret@my.host:6379/0"
221+
)
222+
```
223+
224+
##### Parameters
225+
226+
The Redis backend `dsn` uses the following connection string syntax:
227+
228+
```
229+
redis://[[user:]password@]host[:port][/database]
230+
```
231+
232+
- `host` (Required)
233+
234+
The network location of the Redis server.
235+
236+
Read more about the supported uri schemes and additional parameters [here](https://www.iana.org/assignments/uri-schemes/prov/redis).
237+
238+
###### Dependencies
239+
240+
This backend requires the following extra package:
241+
242+
```
243+
pip install aioredis
244+
```
245+
246+
#### SQLite
247+
248+
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.
249+
250+
##### Usage
251+
252+
```python
253+
handler = Mangum(
254+
app,
255+
dsn="sqlite://mydbfile.sqlite3"
256+
)
257+
```
258+
259+
##### Parameters
260+
261+
The SQLite backend uses the following connection string syntax:
262+
263+
```
264+
sqlite://[file_path].db
265+
```
266+
267+
- `file_path` (Required)
268+
269+
The file name or path to an sqlite3 database file. If one does not exist, then it will be created automatically.
270+
271+
## State machine
272+
273+
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.
274+
275+
### WebSocketCycle
276+
277+
::: mangum.protocols.websocket.WebSocketCycle
278+
:docstring:
279+
:members: run receive send
280+
281+
#### API Gateway events
282+
283+
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.
284+
285+
##### CONNECT
286+
287+
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.
288+
289+
##### MESSAGE
290+
291+
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.
292+
293+
##### DISCONNECT
294+
295+
The client or the server disconnects from the API. The adapter will remove the connection from the backend.
296+
297+
### WebSocketCycleState
298+
299+
::: mangum.protocols.websockets.WebSocketCycleState
300+
:docstring:

0 commit comments

Comments
 (0)