Client
Spring for GraphQL 包括通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求的客户端支持。
GraphQlClient
GraphQlClient
为与底层传输无关的 GraphQL 请求定义了一个通用工作流,因此无论使用何种传输,执行请求的方式都是相同的。
提供以下特定于传输的 GraphQlClient
扩展:
每个都定义一个 Builder
,其中包含与传输相关选项。所有构建器都从一个通用的基本 GraphQlClient xref:client.adoc#client.graphqlclient.builder[Builder
扩展而来,该客户端具有适用于所有传输的选项。
构建 `GraphQlClient`后,便可以开始制作 requests。
通常,请求的 GraphQL 操作以文本形式提供。或者,你也可以通过 DgsGraphQlClient,使用 DGS Codegen 客户端 API 类,它可以包装任何上述 `GraphQlClient`扩展。
HTTP Sync
HttpSyncGraphQlClient
使用 RestClient 通过拦截器的阻塞传输契约和链来通过 HTTP 执行 GraphQL 请求。
RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
创建 `HttpSyncGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpSyncGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
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 请求。
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
创建 `HttpGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
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 构建的,您可以按照如下方式创建它:
String url = "wss://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
与 HttpGraphQlClient
相反,WebSocketGraphQlClient
是面向连接的,这意味着它需要在发出任何请求之前建立一个连接。当开始发出请求时,连接会以透明的方式建立。或者,使用客户端的 start()
方法在任何请求之前显式地建立连接。
除了面向连接之外,WebSocketGraphQlClient
也是复用的。它为所有请求维护一个单一的共享连接。如果连接丢失,它将在下一个请求中或在再次调用 start()
时重新建立。你还可以使用客户端的 stop()
方法,该方法会取消正在进行的请求、关闭连接并拒绝新的请求。
对每个服务器使用一个`WebSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享连接。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。 |
创建 `WebSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `WebSocketGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:
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"`响应,作为连接启动时的响应。
为了进行 WebSocket 传输特定的拦截,你可以创建一个 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
的拦截器。
RSocket
RSocketGraphQlClient
使用 RSocketRequester 通过 RSocket 请求执行 GraphQL 请求。
URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);
RSocketGraphQlClient client = RSocketGraphQlClient.builder()
.clientTransport(transport)
.build();
与 HttpGraphQlClient
相反,RSocketGraphQlClient
是面向连接的,这意味着它需要在发出任何请求之前建立一个会话。当你开始发出请求时,会话将以透明的方式建立。或者,使用客户端的 start()
方法在发出任何请求之前显式地建立会话。
RSocketGraphQlClient
也是复用的。它为所有请求维护一个单一的、共享的会话。如果会话丢失,它将在下一个请求中重新建立,或者如果再次调用 start()
。你还可以使用客户端的 stop()
方法,该方法会取消正在进行的请求,关闭会话,并拒绝新请求。
对每个服务器使用一个`RSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享会话。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。 |
创建 `RSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。
Builder
GraphQlClient
定义了一个父级 BaseBuilder
,其中包含所有扩展的构建器的公共配置选项。目前,它允许你配置:
-
DocumentSource
策略可从文件为请求加载文档 -
Interception of executed requests
BaseBuilder
进一步由以下内容扩展:
-
SyncBuilder
- 使用 `SyncGraphQlInterceptor’s 的链来阻止执行堆栈。 -
Builder
- 使用 `GraphQlInterceptor’s 的链来非阻塞执行堆栈。
Requests
获得 GraphQlClient
后,便可以通过 retrieve或 execute方法开始执行请求。
Retrieve
以下内容检索和解码查询数据:
-
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 | 从其中解码响应映射的“data”键下的路径。 |
3 | 将路径中的数据解码为目标类型。 |
输入文档是一个 String
,它可能是文本或通过代码生成的请求对象生成的。你还可以定义文件中的文档,并使用 Document Source按文件名重新确定它们。
该路径是相对于“data”键的,它使用简单的点号(“.”)分隔符号表示嵌套字段,列表元素可以使用可选的数组索引,例如 "project.name"
或 "project.releases[0].version"
。
如果给定的路径不存在,或者字段值是 null
并存在错误,解码将导致 FieldAccessException
。FieldAccessException
提供对响应和字段的访问权限:
-
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`方法并处理响应:
例如:
-
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 | 响应中没有数据,只有错误 |
2 | 字段为 null 并且有相关错误 |
3 | 字段通过其 DataFetcher 设置为 null |
4 | 解码给定路径中的数据 |
Document Source
请求的文档是一个 String
,它可以在局部变量或常量中定义,或者可以通过代码生成请求对象生成。
你还可以创建扩展名为 .graphql
或 .gql
的文档文件,这些文件位于类路径上的 "graphql-documents/"
下,并通过文件名引用它们。
例如,给定名为 projectReleases.graphql
、内容为:的文件在 src/main/resources/graphql-documents
中:
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
然后你可以:
Project project = graphQlClient.documentName("projectReleases") 1
.variable("slug", "spring-framework") 2
.retrieveSync()
.toEntity(Project.class);
1 | 从“projectReleases.graphql”加载文档 |
2 | Provide variable values. |
IntelliJ 的 “JS GraphQL” 插件支持带有代码补全功能的 GraphQL 查询文件。
你可以使用 Builder定制 DocumentSource
,以按名称加载文档。
Subscription Requests
订阅请求需要一种能够流式传输数据的客户端传输。你需要创建支持此功能的`GraphQlClient`:
-
HttpGraphQlClient with Server-Sent Events
-
WebSocketGraphQlClient with WebSocket
-
RSocketGraphQlClient with RSocket
Retrieve
要启动订阅流,请使用与 retrieve类似的 retrieveSubscription
,但对于单个响应返回一个响应流,每个响应都解码为一些数据:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
如果订阅由于服务器端出现“错误”消息而结束,则`Flux`可能会以`SubscriptionErrorException`终止。该异常提供对从“错误”消息中解码的GraphQL错误的访问。
如果底层连接关闭或丢失,则`Flux`可能会以`GraphQlTransportException`(如`WebSocketDisconnectedException`)终止。在这种情况下,你可以使用`retry`运算符重新启动订阅。
要从客户端结束订阅,必须取消`Flux`,反过来WebSocket传输会向服务器发送“完成”消息。如何取消`Flux`取决于它的使用方式。一些运算符(例如`take`或`timeout`)本身会取消`Flux`。如果你使用`Subscriber`订阅`Flux`,你可以获取对`Subscription`的引用并通过它取消。`onSubscribe`运算符还提供对`Subscription`的访问。
Execute
Retrieve只是一个快捷方式,用于从每个响应映射中的单个路径进行解码。要获得更多控制,请使用 `executeSubscription`方法并直接处理每个响应:
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`来拦截通过客户端的所有请求:
static class MyInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
对于使用`GraphQlClient.Builder`创建的非阻塞式传输,你可以创建一个`GraphQlClientInterceptor`来拦截通过客户端的所有请求:
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);
}
}
一旦创建了拦截器,就通过客户端构建器注册它。例如:
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.interceptor(new MyInterceptor())
.build();
DGS Codegen
除了将操作(如 mutation、query 或 subscription)作为文本提供之外,你还可以使用 DGS Codegen库来生成客户端 API 类,让你可以使用流畅的 API 来定义请求。
Spring for GraphQL 提供了 DgsGraphQlClient,它封装了任何 GraphQlClient
,并帮助使用生成的 clientAPI 类准备请求。
例如,给定以下模式:
type Query {
books: [Book]
}
type Book {
id: ID
name: String
}
你可以这样执行请求:
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 | - 通过封装任何 GraphQlClient 来创建 DgsGraphQlClient 。 |
2 | - 指定请求的操作。 |
3 | - 定义选择集。 |