Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Critical unresolved issues include operation ID reuse races, connections surviving shutdown, unbounded workers, and cancelled mutations still executing.
9 open issues
Close connections registered after shutdown begins · New Make operation ID retirement atomic with reuse · New Bound in-flight operations before starting workers · New Skip GraphQL execution when an operation is cancelled · New Bound adapter shutdown time across client closes · New Synchronize initialization with the timeout transition · New Validate ping and pong payload types · New Keep WebSocket close reasons within the frame limit · New Reserve subscription capacity before execution · New
What changed in this PR
Adds the server-side G4 WebSocket transport to the GraphQL extension, allowing graphql-transport-ws alongside HTTP and SSE on the same endpoint. Client-side WebSocket support remains a separate follow-up.
Changes:
- Adds connection initialization, multiplexed operations, cancellation, protocol errors, and shutdown handling.
- Reuses the SSE result queue for subscription back-pressure and limits.
- Keeps WebSocket dependencies optional and adds protocol and module-path tests.
| File | Description |
|---|---|
org.restlet.ext.graphql/src/test/java/org/restlet/ext/graphql/GraphqlWebSocketServerTestCase.java |
Adds WebSocket protocol and lifecycle tests. |
org.restlet.ext.graphql/src/test/java/org/restlet/ext/graphql/GraphqlTestSchemas.java |
Adds a fetcher exposing the initialization payload. |
org.restlet.ext.graphql/src/test/java/org/restlet/ext/graphql/GraphqlModulePathTestCase.java |
Tests operation without the optional WebSocket module. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/RestletGraphql.java |
Defines the initialization-payload context key. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/internal/GraphqlResultQueue.java |
Extracts the shared bounded subscription queue. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/internal/GraphqlEventStream.java |
Adopts the shared queue for SSE. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/GraphqlWebSocketSupport.java |
Implements WebSocket connections and GraphQL operations. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/GraphqlUpgradeSupport.java |
Isolates optional upgrade handling behind an interface. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/GraphqlTransport.java |
Adds the WebSocket transport option. |
org.restlet.ext.graphql/src/main/java/org/restlet/ext/graphql/GraphqlServerAdapter.java |
Integrates upgrades, timeout configuration, and execution context. |
org.restlet.ext.graphql/src/main/java/module-info.java |
Declares the optional WebSocket module dependency. |
org.restlet.ext.graphql/pom.xml |
Adds the optional dependency and IDE test-module configuration. |
changes.md |
Records server-side WebSocket support. |
Review effort: Balanced
| if (operations.putIfAbsent(id, operation) != null) { | ||
| return close(SUBSCRIBER_EXISTS, "Subscriber for " + id + " already exists"); | ||
| } | ||
| Thread.ofVirtual().name("restlet-graphql-ws").start(operation); |
There was a problem hiding this comment.
Leaving this open for a decision: bounding in-flight operations per connection needs a new setting (or reusing an existing one with different semantics), which is an API choice for the G4 design rather than a review fix. Subscriptions are now reserved before execution, so maxSubscriptions does bound them; slow queries and mutations are not bounded yet.
- GraphqlTransport.WEBSOCKET: the server adapter accepts WebSocket upgrades offering the graphql-transport-ws subprotocol on its own path, after the same filters and authenticators; other subprotocols get 400. HTTP keeps working on the same path. - Protocol: connection_init and connection_ack with a configurable timeout (3 s, close 4408), ping and pong, several operations per connection on their own virtual threads, complete in both directions, error messages for errors before execution, and close codes 4400 (invalid message), 4401 (subscribe before ack), 4409 (duplicate id) and 4429 (repeated connection_init). - The connection_init payload reaches data fetchers under RestletGraphql.CONNECTION_INIT_PAYLOAD. - Subscriptions share the SSE limits and back-pressure: the queueing subscriber of the SSE stream is extracted as GraphqlResultQueue. Closing a connection cancels its subscriptions; stopping the adapter closes connections with 1001. - org.restlet.ext.websocket is optional (requires static, optional Maven dependency) and only loaded when WEBSOCKET is enabled. The named-module test now runs without it. Refs #1586 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
A 300 ms connection_init timeout closed the connection before a slow CI runner sent connection_init. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
Close connections accepted while stopping and close them concurrently, reserve a subscription slot before executing a subscription, skip operations cancelled before they start, and retire operation IDs atomically with their messages. Validate ping and pong payloads, bound close reasons to 123 bytes and let connection_init win or lose against the timeout atomically. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ab5539d to
63ed523
Compare
|






Part of #1586 (server side) and #1581.
Summary
GraphqlServerAdapterservesgraphql-transport-wswhenGraphqlTransport.WEBSOCKETis enabled:graphql-transport-wsare accepted on the adapter's own path, after the same filters and authenticators as HTTP requests. Other subprotocols get400. HTTP/1.1 only, as for the WebSocket extension.connection_init/connection_ackwithinconnectionInitTimeout(3 s, else close4408),ping/pong, several operations per connection (each on its own virtual thread),completein both directions,errorfor errors before execution (nocompleteafter it), and close codes4400(invalid or binary message),4401(subscribebefore the ack),4409(duplicate active id) and4429(repeatedconnection_init).connection_initpayload reaches data fetchers underRestletGraphql.CONNECTION_INIT_PAYLOAD, next toREQUEST(the upgrade request) andCLIENT_INFO.GraphqlResultQueueand reused.1001.Optional dependency.
org.restlet.ext.websocketisrequires staticand an optional Maven dependency. The adapter only refers to the WebSocket code through a package-private interface, instantiated instart()whenWEBSOCKETis enabled.GraphqlModulePathTestCasenow resolves and runs its consumer without the WebSocket module.Validation
./mvnw -B verify -P default,test -pl org.restlet.ext.graphql: 56 tests, 0 failures; 92.5% instruction coverage (gate 80%); Spotless, SpotBugs, Javadoc,-Werrorand dependency convergence pass.New
GraphqlWebSocketServerTestCase(9 tests), driven by the JDKjava.net.http.WebSocketclient as an independent peer: handshake, ping/pong and the init payload in a data fetcher; subscriptionnexts andcomplete;errorbefore execution; two subscriptions on one connection, clientcompletecancelling one, id reuse, connection close cancelling the rest; every close code (4400 for six kinds of invalid messages and binary frames, 4401, 4408, 4409, 4429); subprotocol required (400); HTTP on the same path; adapter stop closes with1001; configuration validation.The pom gets the same
m2e-test-modulesprofile as the WebSocket extension, for the JDK HTTP client and server used by tests in Eclipse and VS Code.🤖 Generated with Claude Code