ASGI (Asynchronous Server Gateway Interface) Specification¶
Version: 3.0 (2019-03-20)
Abstract¶
This document proposes a standard interface between network protocol servers (particularly web servers) and Python applications, intended to allow handling of multiple common protocol styles (including HTTP, HTTP/2, and WebSocket).
This base specification is intended to fix in place the set of APIs by which these servers interact and run application code; each supported protocol (such as HTTP) has a sub-specification that outlines how to encode and decode that protocol into messages.
Rationale¶
The WSGI specification has worked well since it was introduced, and allowed for great flexibility in Python framework and web server choice. However, its design is irrevocably tied to the HTTP-style request/response cycle, and more and more protocols that do not follow this pattern are becoming a standard part of web programming (most notably, WebSocket).
ASGI attempts to preserve a simple application interface, while providing an abstraction that allows for data to be sent and received at any time, and from different application threads or processes.
It also takes the principle of turning protocols into Python-compatible, asynchronous-friendly sets of messages and generalises it into two parts; a standardised interface for communication around which to build servers (this document), and a set of standard message formats for each protocol.
Its primary goal is to provide a way to write HTTP/2 and WebSocket code alongside normal HTTP handling code, however; part of this design means ensuring there is an easy path to use both existing WSGI servers and applications, as a large majority of Python web usage relies on WSGI and providing an easy path forward is critical to adoption. Details on that interoperability are covered in the ASGI-HTTP spec.
Overview¶
ASGI consists of two different components:
A protocol server, which terminates sockets and translates them into connections and per-connection event messages.
An application, which lives inside a protocol server, is instantiated once per connection, and handles event messages as they happen.
Like WSGI, the server hosts the application inside it, and dispatches incoming
requests to it in a standardized format. Unlike WSGI, however, applications
are instantiated objects that are fed events rather than simple callables,
and must run as asyncio
-compatible coroutines (on the main thread;
they are free to use threading or other processes if they need synchronous
code).
Unlike WSGI, there are two separate parts to an ASGI connection:
A connection scope, which represents a protocol connection to a user and survives until the connection closes.
Events, which are sent to the application as things happen on the connection.
Applications are instantiated with a connection scope, and then run in an event loop where they are expected to handle events and send data back to the client.
Each application instance maps to a single incoming “socket” or connection, and is expected to last the lifetime of that connection plus a little longer if there is cleanup to do. Some protocols may not use traditional sockets; ASGI specifications for those protocols are expected to define what the scope (instance) lifetime is and when it gets shut down.
Specification Details¶
Connection Scope¶
Every connection by a user to an ASGI application results in an instance of that application being created for the connection. How long this lives, and what information it receives upon creation, is called the connection scope.
For example, under HTTP the connection scope lasts just one request, but it contains most of the request data (apart from the HTTP request body, as this is streamed in via events).
Under WebSocket, though, the connection scope lasts for as long as the socket is connected. The scope contains information like the WebSocket’s path, but details like incoming messages come through as events instead.
Some protocols may give you a connection scope with very limited information up front because they encapsulate something like a handshake. Each protocol definition must contain information about how long its connection scope lasts, and what information you will get inside it.
Applications cannot communicate with the client when they are initialized and given their connection scope; they must wait until their event loop is entered and, depending on the protocol spec, may have to wait for an initial opening message.
Events¶
ASGI decomposes protocols into a series of events that an application must
react to. For HTTP, this is as simple as two events in order - http.request
and http.disconnect
. For something like a WebSocket, it could be more like
websocket.connect
, websocket.send
, websocket.receive
, and finally
websocket.disconnect
.
Each event is a dict
with a top-level type
key that contains a
Unicode string of the message type. Users are free to invent their own message
types and send them between application instances for high-level events - for
example, a chat application might send chat messages with a user type of
mychat.message
. It is expected that applications should be able to handle
a mixed set of events, some sourced from the incoming client connection and
some from other parts of the application.
Because these messages could be sent over a network, they need to be serializable, and so they are only allowed to contain the following types:
Byte strings
Unicode strings
Integers (within the signed 64-bit range)
Floating point numbers (within the IEEE 754 double precision range; no
Nan
or infinities)Lists (tuples should be encoded as lists)
Dicts (keys must be Unicode strings)
Booleans
None
Applications¶
Note
The application format changed in 3.0 to use a single callable, rather than
the prior two-callable format. Two-callable is documented below in
“Legacy Applications”; servers can easily implement support for it using
the asgiref.compatibility
library, and should try to support it.
ASGI applications should be a single async callable:
coroutine application(scope, receive, send)
scope
: The connection scope, a dictionary that contains at least atype
key specifying the protocol that is incomingreceive
, an awaitable callable that will yield a new event dictionary when one is availablesend
, an awaitable callable taking a single event dictionary as a positional argument that will return once the send has been completed or the connection has been closed
The application is called once per “connection.” The definition of a connection and its lifespan are dictated by the protocol specification in question. For example, with HTTP it is one request, whereas for a WebSocket it is a single WebSocket connection.
Both the scope
and the format of the messages you send and receive
are defined by one of the application protocols. scope
must be a
dict
. The key scope["type"]
will always be present, and can
be used to work out which protocol is incoming. The key
scope["asgi"]
will also be present as a dictionary containing a
scope["asgi"]["version"]
key that corresponds to the ASGI version
the server implements. If missing, the version should default to "2.0"
.
There may also be a spec-specific version present as
scope["asgi"]["spec_version"]
. This allows the individual protocol
specifications to make enhancements without bumping the overall ASGI version.
The protocol-specific sub-specifications cover these scope and message formats.
They are equivalent to the specification for keys in the environ
dict for
WSGI.
Legacy Applications¶
Legacy (v2.0) ASGI applications are defined as a callable:
application(scope)
Which returns another, awaitable callable:
coroutine application_instance(receive, send)
The meanings of scope
, receive
and send
are the same as in the
newer single-callable application, but note that the first callable is
synchronous.
The first callable is called when the connection is started, and then the second callable is called immediately afterwards.
This style was retired in version 3.0 as the two-callable layout was deemed unnecessary. It’s now legacy, but there are applications out there written in this style, and so it’s important to support them.
There is a compatibility suite available in the asgiref.compatibility
module which allows you to both detect legacy applications and convert them
to the new single-protocol style seamlessly. Servers are encouraged to support
both types as of ASGI 3.0 and gradually drop support by default over time.
Protocol Specifications¶
These describe the standardized scope and message formats for various protocols.
The one common key across all scopes and messages is type
, a way to
indicate what type of scope or message is being received.
In scopes, the type
key must be a Unicode string, like "http"
or
"websocket"
, as defined in the relevant protocol specification.
In messages, the type
should be namespaced as protocol.message_type
,
where the protocol
matches the scope type, and message_type
is
defined by the protocol spec. Examples of a message type
value include
http.request
and websocket.send
.
Note
Applications should actively reject any protocol that they do not understand with an Exception (of any type). Failure to do this may result in the server thinking you support a protocol you don’t, which can be confusing when using with the Lifespan protocol, as the server will wait to start until you tell it.
Current protocol specifications:
Middleware¶
It is possible to have ASGI “middleware” - code that plays the role of both server and application, taking in a scope and the send/receive awaitables, potentially modifying them, and then calling an inner application.
When middleware is modifying the scope, it should make a copy of the scope
object before mutating it and passing it to the inner application, as changes
may leak upstream otherwise. In particular, you should not assume that the copy
of the scope you pass down to the application is the one that it ends up using,
as there may be other middleware in the way; thus, do not keep a reference to
it and try to mutate it outside of the initial ASGI constructor callable that
receives scope
. Your one and only chance to add to it is before you hand
control to the child application.
Error Handling¶
If a server receives an invalid event dictionary - for example, having an
unknown type
, missing keys an event type should have, or with wrong Python
types for objects (e.g. Unicode strings for HTTP headers) - it should raise an
exception out of the send
awaitable back to the application.
If an application receives an invalid event dictionary from receive
, it
should raise an exception.
In both cases, the presence of additional keys in the event dictionary should not raise an exception. This allows non-breaking upgrades to protocol specifications over time.
Servers are free to surface errors that bubble up out of application instances they are running however they wish - log to console, send to syslog, or other options - but they must terminate the application instance and its associated connection if this happens.
Note that messages received by a server after the connection has been
closed are not considered errors. In this case the send
awaitable
callable should act as a no-op.
Extra Coroutines¶
Frameworks or applications may want to run extra coroutines in addition to the coroutine launched for each application instance. Since there is no way to parent these to the instance’s coroutine in Python 3.7, applications should ensure that all coroutines launched as part of running an application instance are terminated either before or at the same time as the application instance’s coroutine.
Any coroutines that continue to run outside of this window have no guarantees about their lifetime and may be killed at any time.
Extensions¶
There are times when protocol servers may want to provide server-specific extensions outside of a core ASGI protocol specification, or when a change to a specification is being trialled before being rolled in.
For this use case, we define a common pattern for extensions
- named
additions to a protocol specification that are optional but that, if provided
by the server and understood by the application, can be used to get more
functionality.
This is achieved via an extensions
entry in the scope
dictionary, which
is itself a dict
. Extensions have a Unicode string name that
is agreed upon between servers and applications.
If the server supports an extension, it should place an entry into the
extensions
dictionary under the extension’s name, and the value of that
entry should itself be a dict
. Servers can provide any extra scope
information that is part of the extension inside this value or, if the
extension is only to indicate that the server accepts additional events via
the send
callable, it may just be an empty dict
.
As an example, imagine a HTTP protocol server wishes to provide an extension
that allows a new event to be sent back to the server that tries to flush the
network send buffer all the way through the OS level. It provides an empty
entry in the extensions
dictionary to signal that it can handle the event:
scope = {
"type": "http",
"method": "GET",
...
"extensions": {
"fullflush": {},
},
}
If an application sees this it then knows it can send the custom event
(say, of type http.fullflush
) via the send
callable.
Strings and Unicode¶
In this document, and all sub-specifications, byte string refers to
the bytes
type in Python 3. Unicode string refers to the str
type
in Python 3.
This document will never specify just string - all strings are one of the two exact types.
All dict
keys mentioned (including those for scopes and events) are
Unicode strings.
Version History¶
3.0 (2019-03-04): Changed to single-callable application style
2.0 (2017-11-28): Initial non-channel-layer based ASGI spec
Copyright¶
This document has been placed in the public domain.