8. 客户
Spring for GraphQL 包括对通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求的客户端支持。
8.1.GraphQlClient
GraphQlClient
是一个合约,它声明了 GraphQL 请求的通用工作流程,即独立于底层传输。这意味着请求使用相同的 API 执行无论底层传输是什么,以及任何特定于传输的内容都是在构建时配置的。
要创建GraphQlClient
您需要以下扩展之一:
每个定义了一个Builder
具有与传输相关的选项。所有构建器都扩展从通用的基本 GraphQlClientBuilder
带选项与所有扩展相关。
一旦你有一个GraphQlClient
您可以开始提出请求。
8.1.1. HTTP
HttpGraphQlClient
使用 WebClient 执行GraphQL 请求。
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
一次HttpGraphQlClient
创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。 如果您需要更改任何特定于传输的详细信息,请使用mutate()
在 现存HttpGraphQlClient
要使用自定义设置创建新实例,请执行以下作:
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...
8.1.2. Web套接字
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 执行请求,而不受底层 运输。 如果您需要更改任何特定于传输的详细信息,请使用mutate()
在 现存WebSocketGraphQlClient
要使用自定义设置创建新实例,请执行以下作:
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...
拦截 器
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
.
8.1.3. 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()
取消in-progress 请求、关闭会话并拒绝新请求的方法。
使用单个RSocketGraphQlClient 实例,以便为对该服务器的所有请求提供单个共享会话。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。 |
一次RSocketGraphQlClient
创建时,您可以开始使用相同的 API 执行请求,而不受底层 运输。
8.2. 请求
一旦你有一个GraphQlClient
,您可以通过 retrieve() 或 execute() 开始执行请求,其中前者只是后者的快捷方式。
8.2.1. 检索
以下内容检索并解码查询的数据:
String document = "{" +
" project(slug:\"spring-framework\") {" +
" name" +
" releases {" +
" version" +
" }"+
" }" +
"}";
Mono<Project> projectMono = graphQlClient.document(document) (1)
.retrieve("project") (2)
.toEntity(Project.class); (3)
1 | 要执行的作。 |
2 | 响应映射中“data”键下的路径,要从中解码。 |
3 | 在目标类型的路径处解码数据。 |
输入文档是String
可以是文字,也可以是通过代码生成生成的请求对象。您还可以在文件中定义文档,并使用文档源按文件名重新定位它们。
该路径相对于“data”键,并使用简单的点 (“.”) 分隔表示法对于具有列表元素可选数组索引的嵌套字段,例如"project.name"
或"project.releases[0].version"
.
解码可能导致FieldAccessException
如果给定的路径不存在,或者
字段值为null
并且有一个错误。FieldAccessException
提供对
response 和字段:
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ResponseField field = ex.getField();
// ...
});
8.2.2. 执行
检索只是从单个路径解码的快捷方式
响应图。要获得更多控制,请使用execute
方法并处理响应:
例如:
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map(response -> {
if (!response.isValid()) {
// Request failure... (1)
}
ResponseField 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 | 设置为null 通过其DataFetcher |
4 | 在给定路径上解码数据 |
8.2.3. 文档源
请求的文档是String
可以在局部变量或constant 中定义,也可以通过代码生成的请求对象生成。
您还可以创建带有扩展名的文档文件.graphql
或.gql
下"graphql-documents/"
并按文件名引用它们。
例如,给定一个名为projectReleases.graphql
在src/main/resources/graphql-documents
,内容:
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
然后,您可以:
Mono<Project> projectMono = graphQlClient.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.retrieve()
.toEntity(Project.class);
1 | 从“projectReleases.graphql”加载文档 |
2 | 提供变量值。 |
IntelliJ 的“JS GraphQL”插件支持具有代码补全功能的 GraphQL 查询文件。
您可以使用GraphQlClient
构建器来自定义DocumentSource
用于按名称加载文档。
8.3. 订阅请求
GraphQlClient
可以通过支持它的传输执行订阅。目前,只有WebSocket 传输支持 GraphQL 流,因此您需要创建一个 WebSocketGraphQlClient。
8.3.1. 检索
要启动订阅流,请使用retrieveSubscription
这类似于检索单个响应,但返回响应流,每个响应都解码为一些数据:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
订阅流可能以以下内容结尾:
-
SubscriptionErrorException
如果服务器结束订阅,其中包含包含一个或多个 GraphQL 错误的显式“错误”消息。该异常提供对从该消息解码的 GraphQL 错误的访问权限。 -
GraphQlTransportException
如WebSocketDisconnectedException
如果底层连接已关闭或丢失,在这种情况下,您可以使用retry
操作员重新建立连接并重新启动订阅。
8.3.2. 执行
检索只是从每个响应映射中的单个路径解码的快捷方式。要获得更多控制,请使用executeSubscription
方法并直接处理每个响应:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}
ResponseField 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)
});
8.4. 拦截
您创建了一个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();