-
Notifications
You must be signed in to change notification settings - Fork 66
GraphQL over WebSocket #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d052336
Add GraphQL over WebSocket RFC
enisdenjo 3f75a34
Typo
enisdenjo 423f81f
Multiple `ConnectionInit` messages result in closure
enisdenjo dab5819
`subscription` operations are distinct on the message ID
enisdenjo 7302a0d
Close is not the same as terminate and small refinements
enisdenjo 88858d0
Support returning multiple results from `execute`
enisdenjo 2f3de95
Further wording improvements
enisdenjo 7fa1007
Example improvements
enisdenjo 4854c40
Repo rename
enisdenjo e4b7e6a
Further refinements
enisdenjo 7d900fc
query must be a string
enisdenjo 2859ed2
Optional payload with connection ack
enisdenjo edc4ab5
Enforce ID uniqueness and all operations are cancelable
enisdenjo 478e46f
add extensions field to subscibe message payload
enisdenjo cdf3695
unnecessary implementations section
enisdenjo 574a0cf
ping and pong messages
enisdenjo 2e687b7
optional payload for ping and pong messages
enisdenjo 3afac4a
smaller improvements and clarifications
enisdenjo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| # GraphQL over WebSocket Protocol | ||
|
|
||
| ## Nomenclature | ||
|
|
||
| - **Socket** is the main WebSocket communication channel between the _server_ and the _client_ | ||
| - **Connection** is a connection **within the established socket** describing a "connection" through which the operation requests will be communicated | ||
|
|
||
| ## Communication | ||
|
|
||
| The WebSocket sub-protocol for this specification is: `graphql-transport-ws`. | ||
|
|
||
| Messages are represented through the JSON structure and are stringified before being sent over the network. They are bidirectional, meaning both the server and the client must conform to the specified message structure. | ||
|
|
||
| **All** messages contain the `type` field outlining the action this message describes. Depending on the type, the message can contain two more _optional_ fields: | ||
|
|
||
| - `id` used for uniquely identifying server responses and connecting them with the client's requests | ||
| - `payload` holding the extra "payload" information to go with the specific message type | ||
|
|
||
| The server can close the socket (kick the client off) at any time. The close event dispatched by the server is used to describe the fatal error to the client. | ||
|
|
||
| The client closes the socket and the connection by dispatching a `1000: Normal Closure` close event to the server indicating a normal closure. | ||
|
|
||
| ## Message types | ||
|
|
||
| ### `ConnectionInit` | ||
|
|
||
| Direction: **Client -> Server** | ||
|
|
||
| Indicates that the client wants to establish a connection within the existing socket. This connection is **not** the actual WebSocket communication channel, but is rather a frame within it asking the server to allow future operation requests. | ||
|
|
||
| The server must receive the connection initialisation message within the allowed waiting time specified in the `connectionInitWaitTimeout` parameter during the server setup. If the client does not request a connection within the allowed timeout, the server will close the socket with the event: `4408: Connection initialisation timeout`. | ||
|
|
||
| If the server receives more than one `ConnectionInit` message at any given time, the server will close the socket with the event `4429: Too many initialisation requests`. | ||
|
|
||
| ```typescript | ||
| interface ConnectionInitMessage { | ||
| type: 'connection_init'; | ||
| payload?: Record<string, unknown>; | ||
| } | ||
| ``` | ||
|
|
||
| ### `ConnectionAck` | ||
|
|
||
| Direction: **Server -> Client** | ||
|
|
||
| Expected response to the `ConnectionInit` message from the client acknowledging a successful connection with the server. | ||
|
|
||
| ```typescript | ||
| interface ConnectionAckMessage { | ||
| type: 'connection_ack'; | ||
| } | ||
| ``` | ||
|
|
||
| The client is now **ready** to request subscription operations. | ||
|
|
||
| ### `Subscribe` | ||
|
|
||
| Direction: **Client -> Server** | ||
|
|
||
| Requests an operation specified in the message `payload`. This message provides a unique ID field to connect published messages to the operation requested by this message. | ||
|
|
||
| If there is already an active subscriber for a streaming operation matching the provided ID, the server will close the socket immediately with the event `4409: Subscriber for <unique-operation-id> already exists`. The server may not assert this rule for operations returning a single result as they do not require reservations for additional future events. | ||
enisdenjo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```typescript | ||
| import { DocumentNode } from 'graphql'; | ||
|
|
||
| interface SubscribeMessage { | ||
| id: '<unique-operation-id>'; | ||
| type: 'subscribe'; | ||
| payload: { | ||
| operationName?: string | null; | ||
| query: string | DocumentNode; | ||
enisdenjo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| variables?: Record<string, unknown> | null; | ||
| }; | ||
| } | ||
| ``` | ||
|
|
||
| Executing operations is allowed **only** after the server has acknowledged the connection through the `ConnectionAck` message, if the connection is not acknowledged, the socket will be closed immediately with the event `4401: Unauthorized`. | ||
|
|
||
| ### `Next` | ||
|
|
||
| Direction: **Server -> Client** | ||
|
|
||
| Operation execution result(s) from the source stream created by the binding `Subscribe` message. After all results have been emitted, the `Complete` message will follow indicating stream completion. | ||
|
|
||
| ```typescript | ||
| import { ExecutionResult } from 'graphql'; | ||
|
|
||
| interface NextMessage { | ||
| id: '<unique-operation-id>'; | ||
| type: 'next'; | ||
| payload: ExecutionResult; | ||
| } | ||
| ``` | ||
|
|
||
| ### `Error` | ||
|
|
||
| Direction: **Server -> Client** | ||
|
|
||
| Operation execution error(s) triggered by the `Next` message happening before the actual execution, usually due to validation errors. | ||
|
|
||
| ```typescript | ||
| import { GraphQLError } from 'graphql'; | ||
|
|
||
| interface ErrorMessage { | ||
| id: '<unique-operation-id>'; | ||
| type: 'error'; | ||
| payload: GraphQLError[]; | ||
| } | ||
| ``` | ||
|
|
||
| ### `Complete` | ||
|
|
||
| Direction: **bidirectional** | ||
|
|
||
| - **Server -> Client** indicates that the requested operation execution has completed. If the server dispatched the `Error` message relative to the original `Subscribe` message, no `Complete` message will be emitted. | ||
|
|
||
| - **Client -> Server** indicates that the client has stopped listening and wants to complete the source stream. No further events, relevant to the original subscription, should be sent through. | ||
|
|
||
| ```typescript | ||
| interface CompleteMessage { | ||
| id: '<unique-operation-id>'; | ||
| type: 'complete'; | ||
| } | ||
| ``` | ||
|
|
||
| ### Invalid message | ||
|
|
||
| Direction: **bidirectional** | ||
|
|
||
| Receiving a message of a type or format which is not specified in this document will result in an **immediate** socket closure with the event `4400: <error-message>`. The `<error-message>` can be vaguely descriptive on why the received message is invalid. | ||
|
|
||
| ## Examples | ||
|
|
||
| For the sake of clarity, the following examples demonstrate the communication protocol. | ||
|
|
||
| <h3 id="successful-connection-initialisation">Successful connection initialisation</h3> | ||
|
|
||
| 1. _Client_ sends a WebSocket handshake request with the sub-protocol: `graphql-transport-ws` | ||
| 1. _Server_ accepts the handshake and establishes a WebSocket communication channel (which we call "socket") | ||
| 1. _Client_ immediately dispatches a `ConnectionInit` message optionally providing a payload as agreed with the server | ||
| 1. _Server_ validates the connection initialisation request and dispatches a `ConnectionAck` message to the client on successful connection | ||
| 1. _Client_ has received the acknowledgement message and is now ready to request operation executions | ||
|
|
||
| ### Connection initialisation timeout | ||
|
|
||
| 1. _Client_ sends a WebSocket handshake request with the sub-protocol: `graphql-transport-ws` | ||
| 1. _Server_ accepts the handshake and establishes a WebSocket communication channel (which we call "socket") | ||
| 1. _Client_ does not dispatch a `ConnectionInit` message | ||
| 1. _Server_ waits for the `ConnectionInit` message for the duration specified in the `connectionInitWaitTimeout` parameter | ||
| 1. _Server_ waiting time has passed | ||
| 1. _Server_ closes the socket by dispatching the event `4408: Connection initialisation timeout` | ||
|
|
||
| ### Single result operation | ||
|
|
||
| #### `query` and `mutation` operations without streaming directives | ||
|
|
||
| _The client and the server has already gone through [successful connection initialisation](#successful-connection-initialisation)._ | ||
|
|
||
| 1. _Client_ generates a unique ID for the following operation | ||
| 1. _Client_ dispatches the `Subscribe` message with the generated ID through the `id` field and the requested operation passed through the `payload` field | ||
| <br>_All future communication is linked through this unique ID_ | ||
| 1. _Server_ executes the single result GraphQL operation | ||
| 1. _Server_ dispatches the result with the `Next` message | ||
| 1. _Server_ dispatches the `Complete` message indicating that the execution has completed | ||
|
|
||
| ### Streaming operation | ||
|
|
||
| #### `subscription` operation and queries with streaming directives | ||
|
|
||
| _The client and the server has already gone through [successful connection initialisation](#successful-connection-initialisation)._ | ||
|
|
||
| 1. _Client_ generates a unique ID for the following operation | ||
| 1. _Client_ dispatches the `Subscribe` message with the generated ID through the `id` field and the requested operation passed through the `payload` field | ||
| <br>_All future communication is linked through this unique ID_ | ||
| 1. _Server_ executes the streaming GraphQL operation | ||
| 1. _Server_ checks if the generated ID is unique across active streaming subscriptions | ||
|
|
||
| - If **not** unique, the _server_ will close the socket with the event `4409: Subscriber for <generated-id> already exists` | ||
| - If unique, continue... | ||
|
|
||
| 1. _Server_ dispatches results over time with the `Next` message | ||
| 1. - _Client_ stops the subscription by dispatching a `Complete` message | ||
| - _Server_ completes the source stream | ||
| <br>_or_ | ||
| - _Server_ dispatches the `Complete` message indicating that the source stream has completed | ||
| - _Client_ completes the stream observer | ||
|
|
||
| ## Implementations | ||
|
|
||
| - [graphql-ws](https://github.com/enisdenjo/graphql-ws) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.