HTTP & WebSocket ASGI Message Format (Draft Spec)¶
Note
This is still in-progress, but is now mostly complete.
The HTTP+WebSocket ASGI sub-specification outlines how to transport HTTP/1.1, HTTP/2 and WebSocket connections over an ASGI-compatible channel layer.
It is deliberately intended and designed to be a superset of the WSGI format and specifies how to translate between the two for the set of requests that are able to be handled by WSGI.
HTTP¶
The HTTP format covers HTTP/1.0, HTTP/1.1 and HTTP/2, as the changes in HTTP/2 are largely on the transport level. A protocol server should give different requests on the same connection different reply channels, and correctly multiplex the responses back into the same stream as they come in. The HTTP version is available as a string in the request message.
Multiple header fields with the same name are complex in HTTP. RFC 7230 states that for any header field that can appear multiple times, it is exactly equivalent to sending that header field only once with all the values joined by commas.
However, RFC 7230 and RFC 6265 make it clear that this rule does not apply to
the various headers used by HTTP cookies (Cookie and Set-Cookie). The
Cookie header must only be sent once by a user-agent, but the
Set-Cookie header may appear repeatedly and cannot be joined by commas.
The ASGI design decision is to transport both request and response headers as
lists of 2-element [name, value] lists and preserve headers exactly as they
were provided.
The HTTP protocol should be signified to ASGI applications with a type value
of http.
Connection Scope¶
HTTP connections have a single-request connection scope - that is, your applications will be instantiated at the start of the request, and destroyed at the end, even if the underlying socket is still open and serving multiple requests.
The connection scope contains:
type:httphttp_version: Unicode string, one of1.0,1.1or2.method: Unicode string HTTP method name, uppercased.scheme: Unicode string URL scheme portion (likelyhttporhttps). Optional (but must not be empty), default is"http".path: Unicode string HTTP path from URL, with percent escapes decoded and UTF8 byte sequences decoded into characters.query_string: Byte string URL portion after the?, not url-decoded.root_path: Unicode string that indicates the root path this application is mounted at; same asSCRIPT_NAMEin WSGI. Optional, defaults to"".headers: A list of[name, value]lists, wherenameis the byte string header name, andvalueis the byte string header value. Order of header values must be preserved from the original HTTP request; order of header names is not important. Duplicates are possible and must be preserved in the message as received. Header names must be lowercased.client: List of[host, port]wherehostis a unicode string of the remote host’s IPv4 or IPv6 address, andportis the remote port as an integer. Optional, defaults toNone.server: List of[host, port]wherehostis the listening address for this server as a unicode string, andportis the integer listening port. Optional, defaults toNone.
Request¶
Sent to indicate an incoming request. Most of the request information is in the connection scope; the body message serves as a way to stream large incoming HTTP bodies in chunks, and as a trigger to actually run request code (as you cannot trigger on a connection opening alone).
Keys:
type:http.requestbody: Body of the request, as a byte string. Optional, defaults to"". Ifmore_contentis set, treat as start of body and concatenate on further chunks.more_content: Boolean value signifying if there is additional content to come (as part of a Request Body Chunk message). IfTrue, the consuming application should wait until it gets a chunk with this set toFalse. IfFalse, the request is complete and should be processed.
Response¶
Starts, or continues, sending a response to the client. Protocol servers must flush any data passed to them into the send buffer before returning from a send call.
Keys:
type:http.responsestatus: Integer HTTP status code.headers: A list of[name, value]lists, wherenameis the byte string header name, andvalueis the byte string header value. Order must be preserved in the HTTP response. Header names must be lowercased. Optional, defaults to an empty list. Only allowed on the first response message.content: Byte string of HTTP body content. Concatenated onto any previouscontentvalues sent in this connection scope. Optional, defaults to"".more_content: Boolean value signifying if there is additional content to come (as part of a Response message). IfFalse, response will be taken as complete and closed off, and any further messages on the channel will be ignored. Optional, defaults toFalse.
Disconnect¶
Sent when a HTTP connection is closed. This is mainly useful for long-polling, where you may want to trigger cleanup code if the connection closes early.
Keys:
type:http.disconnect
WebSocket¶
WebSockets share some HTTP details - they have a path and headers - but also have more state. Path and header details are only sent in the connection message; applications that need to refer to these during later messages should store them in a cache or database.
WebSocket protocol servers should handle PING/PONG requests themselves, and send PING frames as necessary to ensure the connection is alive.
The WebSocket protocol should be signified to ASGI applications with
a type value of websocket.
Connection Scope¶
WebSocket connections’ scope lives as long as the socket itself - if the application dies the socket should be closed, and vice-versa. The scope contains the initial connection metadata (mostly from HTTP headers):
type:websocketscheme: Unicode string URL scheme portion (likelywsorwss). Optional (but must not be empty), default isws.path: Unicode HTTP path from URL, already urldecoded.query_string: Byte string URL portion after the?. Optional, default is empty string.root_path: Byte string that indicates the root path this application is mounted at; same asSCRIPT_NAMEin WSGI. Optional, defaults to empty string.headers: List of[name, value], wherenameis the header name as byte string andvalueis the header value as a byte string. Order should be preserved from the original HTTP request; duplicates are possible and must be preserved in the message as received. Header names must be lowercased.client: List of[host, port]wherehostis a unicode string of the remote host’s IPv4 or IPv6 address, andportis the remote port as an integer. Optional, defaults toNone.server: List of[host, port]wherehostis the listening address for this server as a unicode string, andportis the integer listening port. Optional, defaults toNone.subprotocols: List of subprotocols the client advertised as unicode strings. Optional, defaults to empty list.
Connection¶
Sent when the client initially opens a connection and is about to finish the WebSocket handshake.
This message must be responded to with either an Accept message
or a Close message before the socket will pass websocket.receive
messages. The protocol server must ideally send this message
during the handshake phase of the WebSocket and not complete the handshake
until it gets a reply, returning HTTP status code 403 if the connection is
denied.
Keys:
type:websocket.connect
Accept¶
Sent by the application when it wishes to accept an incoming connection.
type:websocket.acceptsubprotocol: The subprotocol the server wishes to accept, as a unicode string. Optional, defaults toNone.
Receive¶
Sent when a data frame is received from the client.
Keys:
type:websocket.receivebytes: Byte string of frame content, if it was binary mode, orNone.text: Unicode string of frame content, if it was text mode, orNone.
Exactly one of bytes or text must be non-None.
Send¶
Sends a data frame to the client.
Keys:
type:websocket.sendbytes: Byte string of binary frame content, orNone.text: Unicode string of text frame content, orNone.
Exactly one of bytes or text must be non-None.
Disconnection¶
Sent when either connection to the client is lost, either from the client closing the connection, the server closing the connection, or loss of the socket.
Keys:
type:websocket.disconnectcode: The WebSocket close code (integer), as per the WebSocket spec.
Close¶
type:websocket.closecode: The WebSocket close code (integer), as per the WebSocket spec. Optional, defaults to1000.
WSGI Compatibility¶
Part of the design of the HTTP portion of this spec is to make sure it aligns well with the WSGI specification, to ensure easy adaptability between both specifications and the ability to keep using WSGI servers or applications with ASGI.
The adaptability works in two ways:
- WSGI to ASGI: A WSGI application can be written that transforms
environinto ahttpscope and ahttp.requestmessage, running the application inside a temporary async event loop. This solution would not allow full use of async to share CPU between requests, but would allow use of existing WSGI servers. - ASGI to WSGI: An ASGI application can be written that translates the
httpscope andhttp.requestmessage down into a WSGIenvironand calls it inside a threadpool.
There is an almost direct mapping for the various special keys in
WSGI’s environ variable to the http scope:
REQUEST_METHODis themethodkeySCRIPT_NAMEisroot_pathPATH_INFOcan be derived frompathandroot_pathQUERY_STRINGisquery_stringCONTENT_TYPEcan be extracted fromheadersCONTENT_LENGTHcan be extracted fromheadersSERVER_NAMEandSERVER_PORTare inserverREMOTE_HOST/REMOTE_ADDRandREMOTE_PORTare inclientSERVER_PROTOCOLis encoded inhttp_versionwsgi.url_schemeisschemewsgi.inputis a StringIO based around thehttp.requestmessageswsgi.errorsis directed by the wrapper as needed
The start_response callable maps similarly to Response:
- The
statusargument becomesstatus, with the reason phrase dropped. response_headersmaps toheaders
It may even be possible to map Request Body Chunks in a way that allows streaming of body data, though it would likely be easier and sufficient for many applications to simply buffer the whole body into memory before calling the WSGI application.