Multipart HTTP protocol for GraphQL Subscriptions
Enable clients to receive real-time updates via multipart HTTP protocol
To execute GraphQL subscription operations on the GraphOS 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 GraphOS Router uses to support the @defer
directive.
Use this reference if you're adding protocol support to a new GraphQL client library. Apollo Client, Apollo Kotlin, and Apollo iOS all support this protocol. Apollo Client also provides network adapters for the Relay and urql libraries.
Executing a subscription
To execute a subscription on the GraphOS 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:
1Accept: multipart/mixed;subscriptionSpec="1.0", application/json
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:
1--graphql
2Content-Type: application/json
3
4{}
5--graphql
6Content-Type: application/json
7
8{"payload": {"data": { "newPost": { "id": 123, "title": "Hello!"}}}}
9--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 GraphOS 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:
1--graphql
2Content-Type: application/json
3
4{}
5--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 GraphOS Router can sometimes still return partial data, and the subscription connection should remain open. These errors are provided within the payload
property:
1{
2 "payload": {
3 "errors": [...],
4 "data": {...},
5 "extensions": {...}
6 }
7}
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:
1{
2 "payload": null,
3 "errors": [...]
4}
Both types of errors
follow the GraphQL error format, but top-level errors
never include locations
or path
.
Additional resources
Check out the federated subscriptions course to explore an end-to-end implementation with Apollo Router, Apollo Server, and Typescript. You can also see the Apollo Solutions federated subscriptions repository for an example of federated subscriptions via an HTTP Multipart based subscription with the router in HTTP callback mode.