Multipart HTTP protocol for GraphQL subscriptions
For GraphQL clients communicating with the Apollo Router
To execute GraphQL subscription operations on the Apollo Router, client apps do not communicate over WebSocket. Instead, they use HTTP with multipart responses. This multipart protocol is built on the same Incremental Delivery over HTTP spec that the Apollo Router uses to support the @defer
directive.
You only need to read this reference if you're adding protocol support to a new GraphQL client library! Apollo Client for Web, Kotlin, and iOS all support this protocol.
Executing a subscription
To execute a subscription on the Apollo Router, a GraphQL client sends an HTTP request with almost the exact same format that it uses for query and mutation requests.
The only difference is that the request should include the following Accept
header:
Accept: multipart/mixed; boundary="graphql"; subscriptionSpec="1.0", application/json
At this time, the value for boundary
should always be graphql
, and the value for subscriptionSpec
should always be 1.0
.
As subscription events occur, the router sends back HTTP response "parts" that conform to the definition of multipart content specified in RFC1341.
An example response might look like this:
--graphqlContent-Type: application/json{}--graphqlContent-Type: application/json{"payload": {"data": { "newPost": { "id": 123, "title": "Hello!"}}}}--graphql--
- If the request uses HTTP/1, the response includes the
Transfer-Encoding: chunked
header. - If the request uses HTTP/2 (which provides built-in support for data streaming), chunked encoding is not used (and is in fact disallowed).
Heartbeats
While a client subscription remains active, the Apollo Router sends periodic "heartbeat" response parts to prevent any intermediaries from closing the connection. The body of a heartbeat is an empty JSON object, which clients should ignore silently:
--graphqlContent-Type: application/json{}--graphql--
Message and error format
This protocol differentiates between transport-level errors and GraphQL errors in response payloads themselves. This is because the GraphQL response format is defined in the GraphQL spec, and unexpected fields might be confusing or could even break client typing.
With the exception of heartbeats, every response part body includes a payload
property, which contains standard GraphQL response properties. The payload
property can be null if a transport-level error occurs.
If a GraphQL-level error occurs, the Apollo Router can sometimes still return partial data, and the subscription connection should remain open. These errors are provided within the payload
property:
{"payload": {"errors": [...],"data": {...},"extensions": {...}}}
If a fatal transport-level error occurs, the router sends a message with a top-level errors
field and null payload
field, then closes the connection:
{"payload": null,"errors": [...]}
Both types of errors
follow the GraphQL error format, but top-level errors
never include locations
or path
.