Client
Spring for GraphQL 包括通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求的客户端支持。
Spring for GraphQL includes client support to execute GraphQL requests over HTTP, WebSocket, and RSocket.
GraphQlClient
GraphQlClient
为与底层传输无关的 GraphQL 请求定义了一个通用工作流,因此无论使用何种传输,执行请求的方式都是相同的。
GraphQlClient
defines a common workflow for GraphQL requests independent of the underlying
transport, so the way you perform requests is the same no matter what transport is in use.
提供以下特定于传输的 GraphQlClient
扩展:
The following transport specific GraphQlClient
extensions are available:
每个都定义一个 Builder
,其中包含与传输相关选项。所有构建器都从一个通用的基本 GraphQlClient xref:client.adoc#client.graphqlclient.builder[Builder
扩展而来,该客户端具有适用于所有传输的选项。
Each defines a Builder
with options relevant to the transport. All builders extend
from a common, base GraphQlClient Builder
with options applicable to all transports.
构建 `GraphQlClient`后,便可以开始制作 requests。
Once GraphQlClient
is built you can begin to make requests.
通常,请求的 GraphQL 操作以文本形式提供。或者,你也可以通过 DgsGraphQlClient,使用 DGS Codegen 客户端 API 类,它可以包装任何上述 `GraphQlClient`扩展。
Typically, the GraphQL operation for a request is provided as text. Alternatively, you
can use DGS Codegen client API classes through
DgsGraphQlClient, which can wrap any of the
above GraphQlClient
extensions.
HTTP Sync
HttpSyncGraphQlClient
使用 RestClient 通过拦截器的阻塞传输契约和链来通过 HTTP 执行 GraphQL 请求。
HttpSyncGraphQlClient
uses
RestClient
to execute GraphQL requests over HTTP through a blocking transport contract and chain of
interceptors.
RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
创建 `HttpSyncGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpSyncGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
Once HttpSyncGraphQlClient
is created, you can begin to
execute requests using the same API, independent of the underlying
transport. If you need to change any transport specific details, use mutate()
on an
existing HttpSyncGraphQlClient
to create a new instance with customized settings:
RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
HTTP
HttpGraphQlClient
使用 WebClient 通过拦截器的非阻塞传输契约和链来通过 HTTP 执行 GraphQL 请求。
HttpGraphQlClient
uses
WebClient to execute
GraphQL requests over HTTP through a non-blocking transport contract and chain of
interceptors.
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
创建 `HttpGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
Once HttpGraphQlClient
is created, you can begin to
execute requests using the same API, independent of the underlying
transport. If you need to change any transport specific details, use mutate()
on an
existing HttpGraphQlClient
to create a new instance with customized settings:
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
WebSocket
WebSocketGraphQlClient
通过共享的 WebSocket 连接来执行 GraphQL 请求。它是使用 Spring WebFlux 中的 WebSocketClient 构建的,您可以按照如下方式创建它:
WebSocketGraphQlClient
executes GraphQL requests over a shared WebSocket connection.
It is built using the
WebSocketClient
from Spring WebFlux and you can create it as follows:
String url = "wss://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
与 HttpGraphQlClient
相反,WebSocketGraphQlClient
是面向连接的,这意味着它需要在发出任何请求之前建立一个连接。当开始发出请求时,连接会以透明的方式建立。或者,使用客户端的 start()
方法在任何请求之前显式地建立连接。
In contrast to HttpGraphQlClient
, the WebSocketGraphQlClient
is connection oriented,
which means it needs to establish a connection before making any requests. As you begin
to make requests, the connection is established transparently. Alternatively, use the
client’s start()
method to establish the connection explicitly before any requests.
除了面向连接之外,WebSocketGraphQlClient
也是复用的。它为所有请求维护一个单一的共享连接。如果连接丢失,它将在下一个请求中或在再次调用 start()
时重新建立。你还可以使用客户端的 stop()
方法,该方法会取消正在进行的请求、关闭连接并拒绝新的请求。
In addition to being connection-oriented, WebSocketGraphQlClient
is also multiplexed.
It maintains a single, shared connection for all requests. If the connection is lost,
it is re-established on the next request or if start()
is called again. You can also
use the client’s stop()
method which cancels in-progress requests, closes the
connection, and rejects new requests.
对每个服务器使用一个`WebSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享连接。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。 |
Use a single |
创建 `WebSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `WebSocketGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
Once WebSocketGraphQlClient
is created, you can begin to
execute requests using the same API, independent of the underlying
transport. If you need to change any transport specific details, use mutate()
on an
existing WebSocketGraphQlClient
to create a new instance with customized settings:
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use graphQlClient...
WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherGraphQlClient...
Interceptor
除了执行请求之外, GraphQL over WebSocket协议还定义了一些面向连接的消息。例如,客户端发送 `"connection_init"`和服务器以 `"connection_ack"`响应,作为连接启动时的响应。
The 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.
为了进行 WebSocket 传输特定的拦截,你可以创建一个 WebSocketGraphQlClientInterceptor
:
For WebSocket transport specific interception, you can create a
WebSocketGraphQlClientInterceptor
:
static class MyInterceptor implements WebSocketGraphQlClientInterceptor {
@Override
public Mono<Object> connectionInitPayload() {
// ... the "connection_init" payload to send
}
@Override
public Mono<Void> handleConnectionAck(Map<String, Object> ackPayload) {
// ... the "connection_ack" payload received
}
}
将上述拦截器作为任何其他 GraphQlClientInterceptor
使用,并使用它来拦截 GraphQL 请求,但请注意,最多只能有一个类型为 WebSocketGraphQlClientInterceptor
的拦截器。
Register 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
.
RSocket
RSocketGraphQlClient
使用 RSocketRequester 通过 RSocket 请求执行 GraphQL 请求。
RSocketGraphQlClient
uses
RSocketRequester
to execute GraphQL requests over RSocket requests.
URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);
RSocketGraphQlClient client = RSocketGraphQlClient.builder()
.clientTransport(transport)
.build();
与 HttpGraphQlClient
相反,RSocketGraphQlClient
是面向连接的,这意味着它需要在发出任何请求之前建立一个会话。当你开始发出请求时,会话将以透明的方式建立。或者,使用客户端的 start()
方法在发出任何请求之前显式地建立会话。
In contrast to HttpGraphQlClient
, the RSocketGraphQlClient
is connection oriented,
which means it needs to establish a session before making any requests. As you begin
to make requests, the session is established transparently. Alternatively, use the
client’s start()
method to establish the session explicitly before any requests.
RSocketGraphQlClient
也是复用的。它为所有请求维护一个单一的、共享的会话。如果会话丢失,它将在下一个请求中重新建立,或者如果再次调用 start()
。你还可以使用客户端的 stop()
方法,该方法会取消正在进行的请求,关闭会话,并拒绝新请求。
RSocketGraphQlClient
is also multiplexed. It maintains a single, shared session for
all requests. If the session is lost, it is re-established on the next request or if
start()
is called again. You can also use the client’s stop()
method which cancels
in-progress requests, closes the session, and rejects new requests.
对每个服务器使用一个`RSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享会话。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。 |
Use a single |
创建 `RSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。
Once RSocketGraphQlClient
is created, you can begin to
execute requests using the same API, independent of the underlying
transport.
Builder
GraphQlClient
定义了一个父级 BaseBuilder
,其中包含所有扩展的构建器的公共配置选项。目前,它允许你配置:
GraphQlClient
defines a parent BaseBuilder
with common configuration options for the
builders of all extensions. Currently, it has lets you configure:
-
DocumentSource
strategy to load the document for a request from a file -
Interception of executed requests
BaseBuilder
进一步由以下内容扩展:
BaseBuilder
is further extended by the following:
-
SyncBuilder
- blocking execution stack with a chain of `SyncGraphQlInterceptor’s. -
Builder
- non-blocking execution stack with chain of `GraphQlInterceptor’s.
Requests
获得 GraphQlClient
后,便可以通过 retrieve或 execute方法开始执行请求。
Once you have a GraphQlClient
, you can begin to perform requests via
retrieve or execute
methods.
Retrieve
以下内容检索和解码查询数据:
The below retrieves and decodes the data for a query:
-
Sync
-
Non-Blocking
String document = "{" +
" project(slug:\"spring-framework\") {" +
" name" +
" releases {" +
" version" +
" }"+
" }" +
"}";
Project project = graphQlClient.document(document) 1
.retrieveSync("project") 2
.toEntity(Project.class); 3
String document = "{" +
" project(slug:\"spring-framework\") {" +
" name" +
" releases {" +
" version" +
" }"+
" }" +
"}";
Mono<Project> projectMono = graphQlClient.document(document) 1
.retrieve("project") 2
.toEntity(Project.class); 3
1 | The operation to perform. |
2 | The path under the "data" key in the response map to decode from. |
3 | Decode the data at the path to the target type. |
输入文档是一个 String
,它可能是文本或通过代码生成的请求对象生成的。你还可以定义文件中的文档,并使用 Document Source按文件名重新确定它们。
The input document is a String
that could be a literal or produced through a code
generated request object. You can also define documents in files and use a
Document Source to resole them by file name.
该路径是相对于“data”键的,它使用简单的点号(“.”)分隔符号表示嵌套字段,列表元素可以使用可选的数组索引,例如 "project.name"
或 "project.releases[0].version"
。
The path is relative to the "data" key and uses a simple dot (".") separated notation
for nested fields with optional array indices for list elements, e.g. "project.name"
or "project.releases[0].version"
.
如果给定的路径不存在,或者字段值是 null
并存在错误,解码将导致 FieldAccessException
。FieldAccessException
提供对响应和字段的访问权限:
Decoding can result in FieldAccessException
if the given path is not present, or the
field value is null
and has an error. FieldAccessException
provides access to the
response and the field:
-
Sync
-
Non-Blocking
try {
Project project = graphQlClient.document(document)
.retrieveSync("project")
.toEntity(Project.class);
}
catch (FieldAccessException ex) {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
}
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
});
Execute
Retrieve只是一个快捷方式,用于从响应映射中的单个路径进行解码。要获得更多控制,请使用 `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:
例如:
For example:
-
Sync
-
Non-Blocking
ClientGraphQlResponse response = graphQlClient.document(document).executeSync();
if (!response.isValid()) {
// Request failure... 1
}
ClientResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure... 2
}
else {
// Optional field set to null... 3
}
}
Project project = field.toEntity(Project.class); 4
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map(response -> {
if (!response.isValid()) {
// Request failure... 1
}
ClientResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure... 2
}
else {
// Optional field set to null... 3
}
}
return field.toEntity(Project.class); 4
});
1 | The response does not have data, only errors |
2 | Field that is null and has an associated error |
3 | Field that was set to null by its DataFetcher |
4 | Decode the data at the given path |
Document Source
请求的文档是一个 String
,它可以在局部变量或常量中定义,或者可以通过代码生成请求对象生成。
The document for a request is a String
that may be defined in a local variable or
constant, or it may be produced through a code generated request object.
你还可以创建扩展名为 .graphql
或 .gql
的文档文件,这些文件位于类路径上的 "graphql-documents/"
下,并通过文件名引用它们。
You can also create document files with extensions .graphql
or .gql
under
"graphql-documents/"
on the classpath and refer to them by file name.
例如,给定名为 projectReleases.graphql
、内容为:的文件在 src/main/resources/graphql-documents
中:
For example, given a file called projectReleases.graphql
in
src/main/resources/graphql-documents
, with content:
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
然后你可以:
You can then:
Project project = graphQlClient.documentName("projectReleases") 1
.variable("slug", "spring-framework") 2
.retrieveSync()
.toEntity(Project.class);
1 | Load the document from "projectReleases.graphql" |
2 | Provide variable values. |
IntelliJ 的 “JS GraphQL” 插件支持带有代码补全功能的 GraphQL 查询文件。
The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion.
你可以使用 Builder定制 DocumentSource
,以按名称加载文档。
You can use the GraphQlClient
Builder to customize the
DocumentSource
for loading documents by names.
Subscription Requests
订阅请求需要一种能够流式传输数据的客户端传输。你需要创建支持此功能的`GraphQlClient`:
Subscription requests require a client transport that is capable of streaming data.
You will need to create a GraphQlClient
that support this:
-
HttpGraphQlClient with Server-Sent Events
-
WebSocketGraphQlClient with WebSocket
-
RSocketGraphQlClient with RSocket
Retrieve
要启动订阅流,请使用与 retrieve类似的 retrieveSubscription
,但对于单个响应返回一个响应流,每个响应都解码为一些数据:
To start a subscription stream, use retrieveSubscription
which is similar to
retrieve for a single response but returning a stream of
responses, each decoded to some data:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
如果订阅由于服务器端出现“错误”消息而结束,则`Flux`可能会以`SubscriptionErrorException`终止。该异常提供对从“错误”消息中解码的GraphQL错误的访问。
The Flux
may terminate with SubscriptionErrorException
if the subscription ends from
the server side with an "error" message. The exception provides access to GraphQL errors
decoded from the "error" message.
如果底层连接关闭或丢失,则`Flux`可能会以`GraphQlTransportException`(如`WebSocketDisconnectedException`)终止。在这种情况下,你可以使用`retry`运算符重新启动订阅。
The Flux
may termiate with GraphQlTransportException
such as
WebSocketDisconnectedException
if the underlying connection is closed or lost. In that
case you can use the retry
operator to restart the subscription.
要从客户端结束订阅,必须取消`Flux`,反过来WebSocket传输会向服务器发送“完成”消息。如何取消`Flux`取决于它的使用方式。一些运算符(例如`take`或`timeout`)本身会取消`Flux`。如果你使用`Subscriber`订阅`Flux`,你可以获取对`Subscription`的引用并通过它取消。`onSubscribe`运算符还提供对`Subscription`的访问。
To end the subscription from the client side, the Flux
must be cancelled, and in turn
the WebSocket transport sends a "complete" message to the server. How to cancel the
Flux
depends on how it is used. Some operators such as take
or timeout
themselves
cancel the Flux
. If you subscribe to the Flux
with a Subscriber
, you can get a
reference to the Subscription
and cancel through it. The onSubscribe
operator also
provides access to the Subscription
.
Execute
Retrieve只是一个快捷方式,用于从每个响应映射中的单个路径进行解码。要获得更多控制,请使用 `executeSubscription`方法并直接处理每个响应:
Retrieve 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:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}
ClientResponseField field = response.field("project");
if (!field.hasValue()) {
if (field.getError() != null) {
// Field failure...
}
else {
// Optional field set to null... 3
}
}
return field.toEntity(String.class)
});
Interception
对于使用`GraphQlClient.SyncBuilder`创建的阻塞式传输,你可以创建一个`SyncGraphQlClientInterceptor`来拦截通过客户端的所有请求:
For blocking transports created with the GraphQlClient.SyncBuilder
, you create a
SyncGraphQlClientInterceptor
to intercept all requests through the client:
static class MyInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
对于使用`GraphQlClient.Builder`创建的非阻塞式传输,你可以创建一个`GraphQlClientInterceptor`来拦截通过客户端的所有请求:
For non-blocking transports created with GraphQlClient.Builder
, you create a
GraphQlClientInterceptor
to intercept all requests through the client:
static class MyInterceptor implements GraphQlClientInterceptor {
@Override
public Mono<ClientGraphQlResponse> intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
@Override
public Flux<ClientGraphQlResponse> interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) {
// ...
return chain.next(request);
}
}
一旦创建了拦截器,就通过客户端构建器注册它。例如:
Once the interceptor is created, register it through the client builder. For example:
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.interceptor(new MyInterceptor())
.build();
DGS Codegen
除了将操作(如 mutation、query 或 subscription)作为文本提供之外,你还可以使用 DGS Codegen库来生成客户端 API 类,让你可以使用流畅的 API 来定义请求。
As an alternative to providing the operation such as a mutation, query, or subscription as text, you can use the DGS Codegen library to generate client API classes that let you use a fluent API to define the request.
Spring for GraphQL 提供了 DgsGraphQlClient,它封装了任何 GraphQlClient
,并帮助使用生成的 clientAPI 类准备请求。
Spring for GraphQL provides DgsGraphQlClient
that wraps any GraphQlClient
and helps to prepare the request with generated client
API classes.
例如,给定以下模式:
For example, given the following schema:
type Query {
books: [Book]
}
type Book {
id: ID
name: String
}
你可以这样执行请求:
You can perform a request as follows:
HttpGraphQlClient client = ... ;
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); (1)
List<Book> books = dgsClient.request(new BooksGraphQLQuery()) (2)
.projection(new BooksProjectionRoot<>().id().name()) (3)
.retrieveSync()
.toEntityList(Book.class);
1 | - Create DgsGraphQlClient by wrapping any GraphQlClient . |
2 | - Specify the operation for the request. |
3 | - Define the selection set. |