客户端

Spring for GraphQL 包含客户端支持,用于通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求。

GraphQlClient

GraphQlClient 定义了 GraphQL 请求的通用工作流程,与底层传输无关,因此无论使用哪种传输,执行请求的方式都相同。

以下传输特定的 GraphQlClient 扩展可用

每个都定义了一个 Builder,其中包含与传输相关的选项。所有构建器都从一个通用的基本 GraphQlClient Builder 扩展,该构建器包含适用于所有传输的选项。

构建 GraphQlClient 后,您可以开始进行 请求

通常,请求的 GraphQL 操作以文本形式提供。或者,您可以通过 DgsGraphQlClient 使用 DGS Codegen 客户端 API 类,它可以包装上述任何 GraphQlClient 扩展。

HTTP 同步

HttpSyncGraphQlClient 使用 RestClient 通过阻塞传输契约和拦截器链在 HTTP 上执行 GraphQL 请求。

RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);

创建 HttpSyncGraphQlClient 后,您可以开始使用相同的 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 执行请求,与底层传输无关。如果您需要更改任何传输特定的详细信息,请使用现有 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://127.0.0.1:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();

HttpGraphQlClient 相比,WebSocketGraphQlClient 是面向连接的,这意味着它需要在进行任何请求之前建立连接。当您开始进行请求时,连接会透明地建立。或者,使用客户端的 start() 方法在任何请求之前显式建立连接。

除了面向连接之外,WebSocketGraphQlClient 也是多路复用的。它为所有请求维护一个单一的共享连接。如果连接丢失,它将在下一个请求或再次调用 start() 时重新建立。您也可以使用客户端的 stop() 方法,它会取消正在进行的请求,关闭连接并拒绝新的请求。

对每个服务器使用一个 WebSocketGraphQlClient 实例,以便对该服务器的所有请求都使用一个单一的共享连接。每个客户端实例都建立自己的连接,这通常不是单个服务器的意图。

创建 WebSocketGraphQlClient 后,您可以开始使用相同的 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...

WebSocketGraphQlClient 支持发送定期的 ping 消息,以便在没有其他消息发送或接收时保持连接处于活动状态。您可以按如下方式启用它

URI url = ... ;
WebSocketClient client = ... ;

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
		.keepAlive(Duration.ofSeconds(30))
		.build();

拦截器

除了执行请求之外,通过 WebSocket 的 GraphQL 协议还定义了一些面向连接的消息。例如,客户端在连接开始时发送 "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://127.0.0.1:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);

RSocketGraphQlClient client = RSocketGraphQlClient.builder()
		.clientTransport(transport)
		.build();

HttpGraphQlClient 相比,RSocketGraphQlClient 是面向连接的,这意味着它需要在发出任何请求之前建立会话。当您开始发出请求时,会话将透明地建立。或者,使用客户端的 start() 方法在任何请求之前显式地建立会话。

RSocketGraphQlClient 也是多路复用的。它为所有请求维护一个单一的共享会话。如果会话丢失,它将在下次请求或再次调用 start() 时重新建立。您也可以使用客户端的 stop() 方法,它会取消正在进行的请求,关闭会话并拒绝新的请求。

为每个服务器使用一个 RSocketGraphQlClient 实例,以便为对该服务器的所有请求拥有一个单一的共享会话。每个客户端实例都建立自己的连接,这通常不是单个服务器的意图。

创建 RSocketGraphQlClient 后,您可以开始使用相同的 API 执行请求,与底层传输无关。

构建器

GraphQlClient 定义了一个父 BaseBuilder,其中包含所有扩展构建器的通用配置选项。目前,它允许您配置

  • DocumentSource 策略,从文件加载请求的文档

  • 拦截 执行的请求

BaseBuilder 由以下内容进一步扩展

  • SyncBuilder - 带有 SyncGraphQlInterceptor 链的阻塞执行堆栈。

  • Builder - 带有 GraphQlInterceptor 链的非阻塞执行堆栈。

请求

拥有 GraphQlClient 后,您可以通过 检索执行 方法开始执行请求。

检索

以下检索并解码查询的数据

  • 同步

  • 非阻塞

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 要执行的操作。
2 响应映射中“data”键下的路径,用于从中解码。
3 将路径处的数据解码为目标类型。

输入文档是一个 String,它可以是文字或通过代码生成的请求对象生成。您也可以在文件中定义文档,并使用 文档源 通过文件名解析它们。

该路径相对于“data”键,并使用简单的点 (".") 分隔的符号表示嵌套字段,可选的数组索引表示列表元素,例如 "project.name""project.releases[0].version"

如果给定的路径不存在,或者字段值为 null 且存在错误,则解码可能会导致 FieldAccessExceptionFieldAccessException 提供对响应和字段的访问。

  • 同步

  • 非阻塞

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 方法并处理响应

例如

  • 同步

  • 非阻塞

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 解码给定路径处的数据

文档来源

请求的文档是一个 String,它可能在本地变量或常量中定义,也可能通过代码生成的请求对象生成。

您还可以创建扩展名为 .graphql.gql 的文档文件,并将它们放在类路径上的 "graphql-documents/" 下,并通过文件名引用它们。

例如,给定一个名为 projectReleases.graphql 的文件,位于 src/main/resources/graphql-documents 中,其内容为

src/main/resources/graphql-documents/projectReleases.graphql
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 提供变量值。

IntelliJ 的 "JS GraphQL" 插件支持具有代码完成功能的 GraphQL 查询文件。

您可以使用 GraphQlClient 构建器 自定义 DocumentSource,以便通过名称加载文档。

订阅请求

订阅请求需要能够流式传输数据的客户端传输。 您需要创建一个支持此功能的 GraphQlClient

获取

要启动订阅流,请使用 retrieveSubscription,它类似于 获取 用于单个响应,但返回响应流,每个响应都解码为一些数据

Flux<String> greetingFlux = client.document("subscription { greetings }")
		.retrieveSubscription("greeting")
		.toEntity(String.class);

如果订阅从服务器端以 "error" 消息结束,则 Flux 可能会以 SubscriptionErrorException 终止。 该异常提供对从 "error" 消息解码的 GraphQL 错误的访问。

如果底层连接关闭或丢失,则 Flux 可能会以 GraphQlTransportException(例如 WebSocketDisconnectedException)终止。 在这种情况下,您可以使用 retry 运算符重新启动订阅。

要从客户端结束订阅,必须取消Flux,进而 WebSocket 传输会向服务器发送“完成”消息。如何取消Flux取决于它的使用方式。一些操作符,例如taketimeout,本身就会取消Flux。如果您使用Subscriber订阅Flux,您可以获取对Subscription的引用并通过它进行取消。onSubscribe操作符也提供了对Subscription的访问。

执行

检索只是一个从每个响应映射中的单个路径解码的快捷方式。为了获得更多控制,请使用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)
		});

拦截

对于使用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 代码生成

作为提供操作(例如突变、查询或订阅)作为文本的替代方案,您可以使用DGS 代码生成库来生成客户端 API 类,这些类允许您使用流畅的 API 来定义请求。

Spring for GraphQL 提供了DgsGraphQlClient,它包装任何GraphQlClient并帮助使用生成的客户端 API 类准备请求。

例如,给定以下模式

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 - 定义选择集。