Skip to content

Commit

Permalink
(WIP) Refactor and proper WebSockets support (Kludex#99)
Browse files Browse the repository at this point in the history
* Decouple WebSocket support from DynamoDB and introduce multiple websocket backends for storing connection ids

* Refactor WebSocket behaviour in general and fix logging throughout

* Documentation for HTTP and WebSockets
  • Loading branch information
jordaneremieff authored May 3, 2020
1 parent 3789e95 commit 709ed06
Show file tree
Hide file tree
Showing 27 changed files with 1,424 additions and 865 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ source =
mangum
omit =
tests/*
mangum/__version__.py
mangum/backends/base.py
150 changes: 115 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@
<a href="https://travis-ci.org/erm/mangum">
<img src="https://travis-ci.org/erm/mangum.svg?branch=master" alt="Build Status">
</a>
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mangum.svg?style=flat-square">

Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) applications with AWS Lambda & API Gateway.
Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) applications with AWS Lambda & API Gateway. It is intended to provide an easy-to-use, configurable wrapper for any ASGI application deployed in an AWS Lambda function to handle API Gateway requests and responses.

**Documentation**: [https://erm.github.io/mangum](https://erm.github.io/mangum)
## Features

- 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.

- Multiple storage backend interfaces for managing WebSocket connections.

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

- Support for binary media types and payload compression in API Gateway.

- Works with existing deployment and configuration tools, including [Serverless Framework](https://www.serverless.com/) and [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html).

- Startup and shutdown [lifespan](https://asgi.readthedocs.io/en/latest/specs/lifespan.html) events.

## Requirements

Expand All @@ -21,49 +34,46 @@ Python 3.6+
pip install mangum
```

## Usage

The adapter class `Mangum` accepts the following optional arguments:
You can install the required dependencies for the WebSocket backends with one the following:

- `enable_lifespan` : bool (default=True)

Specify whether or not to enable lifespan support.
```shell
pip install mangum[aws]
pip install mangum[postgresql]
pip install mangum[redis]
```

- `api_gateway_base_path` : str (default=None)

Base path to strip from URL when using a custom domain name.
## Usage

- `text_mime_types` : list (default=None)

The list of MIME types (in addition to the defaults) that should not return binary responses in API Gateway.
The `Mangum` adapter class is designed to wrap any ASGI application, accepting various configuration options, returning a callable. It can wrap an application and be assigned to the handler:

### Binary support
```python
from mangum import Mangum

Binary response support is available depending on the `Content-Type` and `Content-Encoding` headers. The default text mime types are the following:
# Define an ASGI application

- `application/json`
- `application/javascript`
- `application/xml`
- `application/vnd.api+json`
handler = Mangum(app)
```

All `Content-Type` headers starting with `text/` are included by default.
However, this is just one convention, you may also intercept events and construct the adapter instance separately:

If the `Content-Encoding` header is set to `gzip`, then a binary response will be returned regardless of mime type.
```python
def handler(event, context):
if event.get("some-key"):
# Do something or return, etc.

Binary response bodies will be base64 encoded and `isBase64Encoded` will be `True`.
asgi_handler = Mangum(app)
response = asgi_handler(event, context) # Call the instance with the event arguments

### Event and context
return response
```

The AWS Lambda handler has `event` and `context` parameters. These are available in the ASGI `scope` object:
## Examples

```python3
scope['aws.event']
scope['aws.context']
```
The examples below are "raw" ASGI applications with minimal configurations. You are more likely than not going to be using a framework, but you should be able to replace the `app` in these example with most ASGI framework applications. Please read the [HTTP](https://erm.github.io/mangum/http/) and [WebSocket](https://erm.github.io/mangum/websocket/) docs for more detailed configuration information.

## Example
### HTTP

```python3
```python
from mangum import Mangum

async def app(scope, receive, send):
Expand All @@ -80,10 +90,80 @@ async def app(scope, receive, send):
handler = Mangum(app)
```

## WebSockets (experimental)
### WebSocket

The adapter currently provides some basic WebSocket support using `boto3` with [DynamoDB](https://aws.amazon.com/dynamodb/). To install Mangum with the optional dependency:
```python
from mangum import Mangum

```shell
pip install mangum[full]
html = b"""
<!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>
""" % os.environ.get("WEBSOCKET_URL", "ws://localhost:3000")

async def app(scope, receive, send):
assert scope["type"] in ("http", "websocket")
if scope["type"] == "http":
message = await receive()
if message["type"] == "http.request":
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"text/html; charset=utf-8"]],
}
)
await send({"type": "http.response.body", "body": html})
if scope["type"] == "websocket":
while True:
message = await receive()
if message["type"] == "websocket.connect":
await send({"type": "websocket.accept"})

if message["type"] == "websocket.receive":
text = f"Received message: {message['text']}"
await send({"type": "websocket.send", "text": text})

if message["type"] == "websocket.disconnect":
await send({"type": "websocket.close", "code": 1000})

handler = Mangum(
app,
ws_config={
"backend": "s3",
"params": {
"bucket": "<s3-bucket-to-store-connections>"
}
}
)
```
48 changes: 48 additions & 0 deletions docs/http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# HTTP

Mangum provides 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 in API Gateway. The adapter class handles parsing the incoming requests and managing the ASGI cycle.

## Configuration

```python
handler = Mangum(
app,
enable_lifespan=True,
log_level="info",
api_gateway_base_path=None,
text_mime_types=None,
)
```

The adapter class accepts the following optional arguments:

- `enable_lifespan` : **bool** (default=`True`)

Specify whether or not to enable lifespan support. The adapter will automatically determine if lifespan is supported by the framework unless explicitly disabled.

- `log_level` : **str** (default="info")

Level parameter for the logger.

- `api_gateway_base_path` : **str**

Base path to strip from URL when using a custom domain name.

- `text_mime_types` : **list**

The list of MIME types (in addition to the defaults) that should not return binary responses in API Gateway.

## Binary support

Binary response support is available depending on the `Content-Type` and `Content-Encoding` headers. The default text mime types are the following:

- `application/json`
- `application/javascript`
- `application/xml`
- `application/vnd.api+json`

All `Content-Type` headers starting with `text/` are included by default.

If the `Content-Encoding` header is set to `gzip`, then a binary response will be returned regardless of mime type.

Binary response bodies will be base64 encoded and `isBase64Encoded` will be `True`.
89 changes: 0 additions & 89 deletions docs/index.md

This file was deleted.

Loading

0 comments on commit 709ed06

Please sign in to comment.