diff --git a/spring-graphql-docs/src/docs/asciidoc/client.adoc b/spring-graphql-docs/src/docs/asciidoc/client.adoc index 014c4820..aac06605 100644 --- a/spring-graphql-docs/src/docs/asciidoc/client.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/client.adoc @@ -106,9 +106,14 @@ on an existing `WebSocketGraphQlClient` to create another with different configu ---- + + +[[client-websocketgraphqlclient-connection]] +==== Connection + A connection is established transparently when requests are made. There is only one -shared, active connection at a time. If the connection is lost, it is automatically -re-established on the next request. +shared, active connection at a time. If the connection is lost, it is re-established on +the next request. `WebSocketGraphQlClient` also exposes lifecycle methods: @@ -120,12 +125,48 @@ allow requests again. +[[client-websocketgraphqlclient-interceptor]] +==== Interceptor + +The https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md[GraphQL over WebSocket] +protocol defines a number of connection oriented messages in addition to executing +requests. For example, a client sends `"connection_init"` and the server responds with +`"connection_ack"` at the start of a connection. + +For WebSocket transport specific interception, you can create a +`WebSocketGraphQlClientInterceptor`: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + static class MyInterceptor implements WebSocketGraphQlClientInterceptor { + + @Override + public Mono connectionInitPayload() { + // ... the "connection_init" payload to send + } + + @Override + public Mono handleConnectionAck(Map ackPayload) { + // ... the "connection_ack" payload received + } + + } +---- + +<> the above interceptor as any other +`GraphQlClientInterceptor` and use it also to intercept GraphQL requests, but note there +can be at most one interceptor of type `WebSocketGraphQlClientInterceptor`. + + + [[client-graphqlclient-builder]] === Builder `GraphQlClient` defines a parent `Builder` with common configuration options for the -builders of all extensions. Currently, it has lets you configure a `DocumentSource`, -which is a strategy for loading the document for a request by file name. +builders of all extensions. Currently, it has lets you configure: + +- `DocumentSource` strategy to load the document for a request from a file +- <> of executed requests @@ -193,8 +234,8 @@ response and the field: [[client-requests-execute]] === Execute -`retrieve` is only a shortcut to decode from a single path in the response map. For more -control, use the `execute` method and handle the response: +<> is only a shortcut to decode from a single path in the +response map. For more control, use the `execute` method and handle the response: For example: @@ -270,11 +311,22 @@ You can use the `GraphQlClient` <> to customize th -[[client-subscriptions]] -== Subscriptions -For a subscription operation, call `retrieveSubscription` instead of `retrieve` to -obtain a stream of responses, each decoded to a target object: +[[client-subscriptions]] +== Subscription Requests + +`GraphQlClient` can execute subscriptions over transports that support it. Currently, only +the WebSocket transport supports GraphQL streams, so you'll need to create a +<>. + + + +[[client-subscriptions-retrieve]] +=== Retrieve + +To start a subscription stream, use `retrieveSubscription` which is similar to +<> for a single response but returning a stream of +responses, each decoded to some data: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -283,9 +335,26 @@ obtain a stream of responses, each decoded to a target object: .toEntity(String.class); ---- -Similar to the <> vs <> -alternatives for single response requests, the same is also available for subscriptions. -For more control over each response, use `executeSubscription`: +A subscription stream may end with: + +- `SubscriptionErrorException` if the server ends the +subscription with an explicit "error" message that contains one or more GraphQL errors. +The exception provides access to the GraphQL errors decoded from that message. +- `GraphQlTransportException` such as `WebSocketDisconnectedException` if the underlying +connection is closed or lost in which case you can use the `retry` operator to reestablish +the connection and start the subscription again. + + + + + + +[[client-subscriptions-execute]] +=== Execute + +<> is only a shortcut to decode from a single path in each +response map. For more control, use the `executeSubscription` method and handle each +response directly: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -310,12 +379,42 @@ For more control over each response, use `executeSubscription`: }); ---- -NOTE: Subscriptions are supported only over <>. +[[client-interception]] +== Interception +You create a `GraphQlClientInterceptor` to intercept all requests through a client: +[source,java,indent=0,subs="verbatim,quotes"] +---- +static class MyInterceptor implements GraphQlClientInterceptor { + @Override + public Mono intercept(ClientGraphQlRequest request, Chain chain) { + // ... + return chain.next(request); + } + @Override + public Flux interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) { + // ... + return chain.next(request); + } + +} +---- + +Once the interceptor is created, register it through the client builder: + +[source,java,indent=0,subs="verbatim,quotes"] +---- + URI url = ... ; + WebSocketClient client = ... ; + + WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client) + .interceptor(new MyInterceptor()) + .build(); +---- diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java index 242c22b8..74abde66 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlClient.java @@ -197,8 +197,8 @@ public interface GraphQlClient { *
    *
  • Completes if the subscription completes before the connection is closed. *
  • {@link SubscriptionErrorException} if the subscription ends with an error. - *
  • {@link IllegalStateException} if the connection is closed or lost - * before the stream terminates. + *
  • {@link WebSocketDisconnectedException} if the connection is closed or + * lost before the stream terminates. *
  • Exception for connection and GraphQL session initialization issues. *
*

The {@code Flux} may be cancelled to notify the server to end the diff --git a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java index 2beee9c0..44b0a7a7 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/client/GraphQlTransport.java @@ -50,8 +50,8 @@ public interface GraphQlTransport { *

    *
  • Completes if the subscription completes before the connection is closed. *
  • {@link SubscriptionErrorException} if the subscription ends with an error. - *
  • {@link IllegalStateException} if the connection is closed or lost - * before the stream terminates. + *
  • {@link WebSocketDisconnectedException} if the connection is closed or + * lost before the stream terminates. *
  • Exception for connection and GraphQL session initialization issues. *
*

The {@code Flux} may be cancelled to notify the server to end the