Server Transports

Spring for GraphQL 支持 HTTP、WebSocket 和 RSocket 上的 GraphQL 请求的服务器处理。

Spring for GraphQL supports server handling of GraphQL requests over HTTP, WebSocket, and RSocket.

HTTP

GraphQlHttpHandler 处理通过 HTTP 请求的 GraphQL,并委托给 Interception 链进行请求执行。有两种变体,一种适用于 Spring MVC,一种适用于 Spring WebFlux。两者都异步处理请求,并且具有同等功能,但分别依赖于阻塞式或非阻塞式 I/O 来编写 HTTP 响应。

GraphQlHttpHandler handles GraphQL over HTTP requests and delegates to the Interception chain for request execution. There are two variants, one for Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have equivalent functionality, but rely on blocking vs non-blocking I/O respectively for writing the HTTP response.

请求必须使用 HTTP POST,其中 "application/json" 为内容类型,并将在请求正文中以 JSON 形式包含 GraphQL 请求详细信息,如此提议的 GraphQL over HTTP 规范中所定义。一旦成功解码 JSON 正文,HTTP 响应状态始终为 200(正常),并且 GraphQL 请求执行中的任何错误都会显示在 GraphQL 响应的“错误”部分。媒体类型的默认首选是 "application/graphql-response+json",但 "application/json" 也受支持,如此规范中所述。

Requests must use HTTP POST with "application/json" as content type and GraphQL request details included as JSON in the request body, as defined in the proposed GraphQL over HTTP specification. Once the JSON body has been successfully decoded, the HTTP response status is always 200 (OK), and any errors from GraphQL request execution appear in the "errors" section of the GraphQL response. The default and preferred choice of media type is "application/graphql-response+json", but "application/json" is also supported, as described in the specification.

通过声明 RouterFunction bean 以及使用 Spring MVC 或 WebFlux 中的 RouterFunctions 来创建路由,可以将 GraphQlHttpHandler 作为一个 HTTP 终结点公开。Boot Starter 这样做,请参阅 Web 终结点 部分以获取详细信息,或检查它包含的 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 以获取实际配置。

GraphQlHttpHandler can be exposed as an HTTP endpoint by declaring a RouterFunction bean and using the RouterFunctions from Spring MVC or WebFlux to create the route. The Boot Starter does this, see the Web Endpoints section for details, or check GraphQlWebMvcAutoConfiguration or GraphQlWebFluxAutoConfiguration it contains, for the actual config.

此存储库的 1.0.x 分支包含一个 Spring MVC HTTP 样本 应用程序。

The 1.0.x branch of this repository contains a Spring MVC HTTP sample application.

Server-Sent Events

GraphQlSseHandler 与上面列出的 HTTP 处理器非常相似,但这次是通过 Server-Sent Events 协议处理 HTTP 上的 GraphQL 请求。通过此传输,客户端必须向使用 “application/json” 作为内容类型并且在请求正文中包含 JSON 形式的 GraphQL 请求详细信息的端点发送 HTTP POST 请求;与传统的 HTTP 类型唯一不同的是,客户端必须向 “Accept” 请求头发送 “text/event-stream”。响应将作为一或多个 Server-Sent Events 发送。

GraphQlSseHandler is very similar to the HTTP handler listed above, but this time handling GraphQL requests over HTTP using the Server-Sent Events protocol. With this transport, clients must send HTTP POST requests to the endpoint with "application/json" as content type and GraphQL request details included as JSON in the request body; the only difference with the vanilla HTTP variant is that the client must send "text/event-stream" as the "Accept" request header. The response will be sent as one or more Server-Sent Event(s).

这也在建议的 GraphQL over HTTP 规范中进行了定义。Spring for GraphQL 仅实现“明确连接模式”,因此应用程序必须考虑可扩展性问题,以及采用 HTTP/2 作为底层传输是否会有帮助。

This is also defined in the proposed GraphQL over HTTP specification. Spring for GraphQL only implements the "Distinct connections mode", so applications must consider scalability concerns and whether adopting HTTP/2 as the underlying transport would help.

GraphQlSseHandler 的主要用例是 WebSocket transport 的替代方案,接收的响应是一个订阅操作的项目流。其他类型的操作(如查询和突变)在此不受支持,并且应该使用普通 JSON 通过 HTTP 传输变体。

The main use case for GraphQlSseHandler is an alternative to the WebSocket transport, receiving a stream of items as a response to a subscription operation. Other types of operations, like queries and mutations, are not supported here and should be using the plain JSON over HTTP transport variant.

File Upload

作为一种协议,GraphQL 专注于文本数据的交换。这不包括二进制数据(如图像),但有一个单独的非正式 graphql-multipart-request-spec,它允许通过 HTTP 将文件上传到 GraphQL。

As a protocol GraphQL focuses on the exchange of textual data. This doesn’t include binary data such as images, but there is a separate, informal graphql-multipart-request-spec that allows file uploads with GraphQL over HTTP.

Spring for GraphQL 并不直接支持 graphql-multipart-request-spec。虽然规范确实提供了统一 GraphQL API 的好处,但实际经验引发了许多问题,最佳做法建议也在不断演进,请参见 Apollo Server File Upload Best Practices 进行更详细的探讨。

Spring for GraphQL does not support the graphql-multipart-request-spec directly. While the spec does provide the benefit of a unified GraphQL API, the actual experince has led to a number of issues, and best practice recommendations have evolved, see Apollo Server File Upload Best Practices for a more detailed discussion.

如果您想在应用程序中使用 graphql-multipart-request-spec,您可以通过库 multipart-spring-graphql 来实现。

If you would like to use graphql-multipart-request-spec in your application, you can do so through the library multipart-spring-graphql.

WebSocket

GraphQlWebSocketHandler 根据 protocol 库中定义的 graphql-ws 处理通过 WebSocket 请求的 GraphQL。使用通过 WebSocket 进行 GraphQL 的主要原因是订阅,它允许发送一个 GraphQL 响应流,但也可以用于具有单个响应的常规查询。该处理程序将每个请求委托给 Interception 链,以进行进一步的请求执行。

GraphQlWebSocketHandler handles GraphQL over WebSocket requests based on the protocol defined in the graphql-ws library. The main reason to use GraphQL over WebSocket is subscriptions which allow sending a stream of GraphQL responses, but it can also be used for regular queries with a single response. The handler delegates every request to the Interception chain for further request execution.

GraphQL Over WebSocket Protocols

有两种这样的协议,一个在 subscriptions-transport-ws 库中,另一个在 graphql-ws 库中。前者处于非活动状态,后者取而代之。阅读本 blog post 以了解历史。

There are two such protocols, one in the subscriptions-transport-ws library and another in the graphql-ws library. The former is not active and succeeded by the latter. Read this blog post for the history.

GraphQlWebSocketHandler 有两个变体,一个用于 Spring MVC,另一个用于 Spring WebFlux。它们都异步处理请求并具有等效的功能。WebFlux 处理程序还使用非阻塞 I/O 和反压来流式传输消息,这很好用,因为在 GraphQL Java 中订阅响应是 Reactive Streams Publisher

There are two variants of GraphQlWebSocketHandler, one for Spring MVC and one for Spring WebFlux. Both handle requests asynchronously and have equivalent functionality. The WebFlux handler also uses non-blocking I/O and back pressure to stream messages, which works well since in GraphQL Java a subscription response is a Reactive Streams Publisher.

graphql-ws 项目列出了很多可供客户端使用的 recipes

The graphql-ws project lists a number of recipes for client use.

通过声明 SimpleUrlHandlerMapping bean 并使用它将处理程序映射到 URL 路径,可以将 GraphQlWebSocketHandler 公开为 WebSocket 终结点。默认情况下,Boot Starter 不公开通过 WebSocket 的 GraphQL 终结点,但通过添加一个用于终结点路径的属性即可轻松启用它。请参阅 Web 终结点 部分以获取详细信息,或检查 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 以获取实际 Boot starter 配置。

GraphQlWebSocketHandler can be exposed as a WebSocket endpoint by declaring a SimpleUrlHandlerMapping bean and using it to map the handler to a URL path. By default, the Boot Starter does not expose a GraphQL over WebSocket endpoint, but it’s easy to enable it by adding a property for the endpoint path. Please, see the Web Endpoints section for details, or check the GraphQlWebMvcAutoConfiguration or the GraphQlWebFluxAutoConfiguration for the actual Boot starter config.

此存储库的 1.0.x 分支包含一个 WebFlux WebSocket 样本 应用程序。

The 1.0.x branch of this repository contains a WebFlux WebSocket sample application.

RSocket

GraphQlRSocketHandler 处理 GraphQL over RSocket 请求。期待查询和突变,并将其作为 RSocket request-response 交互来处理,而订阅则作为 request-stream 来处理。

GraphQlRSocketHandler handles GraphQL over RSocket requests. Queries and mutations are expected and handled as an RSocket request-response interaction while subscriptions are handled as request-stream.

GraphQlRSocketHandler 可以作为映射到 GraphQL 请求路由的 @Controller 中的委托使用。例如:

GraphQlRSocketHandler can be used a delegate from an @Controller that is mapped to the route for GraphQL requests. For example:

import java.util.Map;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.graphql.server.GraphQlRSocketHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;

@Controller
public class GraphQlRSocketController {

	private final GraphQlRSocketHandler handler;

	GraphQlRSocketController(GraphQlRSocketHandler handler) {
		this.handler = handler;
	}

	@MessageMapping("graphql")
	public Mono<Map<String, Object>> handle(Map<String, Object> payload) {
		return this.handler.handle(payload);
	}

	@MessageMapping("graphql")
	public Flux<Map<String, Object>> handleSubscription(Map<String, Object> payload) {
		return this.handler.handleSubscription(payload);
	}
}

Interception

服务器传输允许在调用 GraphQL Java 引擎处理请求之前和之后拦截请求。

Server transports allow intercepting requests before and after the GraphQL Java engine is called to process a request.

WebGraphQlInterceptor

HTTPWebSocket 传输调用一个 0 或更多 WebGraphQlInterceptor 的链,然后调用 GraphQL Java 引擎的 ExecutionGraphQlServiceWebGraphQlInterceptor 允许应用程序拦截传入请求并执行以下操作之一:

HTTP and WebSocket transports invoke a chain of 0 or more WebGraphQlInterceptor, followed by an ExecutionGraphQlService that calls the GraphQL Java engine. WebGraphQlInterceptor allows an application to intercept incoming requests and do one of the following:

  • Check HTTP request details

  • Customize the graphql.ExecutionInput

  • Add HTTP response headers

  • Customize the graphql.ExecutionResult

例如,拦截器可以将 HTTP 请求头传递给 DataFetcher

For example, an interceptor can pass an HTTP request header to a DataFetcher:

include-code::RequestHeaderInterceptor[]<1> 拦截器将 HTTP 请求标头值添加到 GraphQLContext<2> 数据控制器方法访问值

import java.util.Collections;

import reactor.core.publisher.Mono;

import org.springframework.graphql.data.method.annotation.ContextValue;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.stereotype.Controller;

class RequestHeaderInterceptor implements WebGraphQlInterceptor { (1)

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
		String value = request.getHeaders().getFirst("myHeader");
		request.configureExecutionInput((executionInput, builder) ->
				builder.graphQLContext(Collections.singletonMap("myHeader", value)).build());
		return chain.next(request);
	}
}

@Controller
class MyContextValueController { (2)

	@QueryMapping
	Person person(@ContextValue String myHeader) {
		...
	}
}
1 Interceptor adds HTTP request header value into GraphQLContext
2 Data controller method accesses the value

相反,拦截器可以访问控制器添加到 GraphQLContext 的值:

Reversely, an interceptor can access values added to the GraphQLContext by a controller:

include-code::ResponseHeaderInterceptor[]<1> 控制器将值添加到 GraphQLContext<2> 拦截器使用该值添加 HTTP 响应标头

import graphql.GraphQLContext;
import reactor.core.publisher.Mono;

import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Controller;

// Subsequent access from a WebGraphQlInterceptor

class ResponseHeaderInterceptor implements WebGraphQlInterceptor {

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) { (2)
		return chain.next(request).doOnNext(response -> {
			String value = response.getExecutionInput().getGraphQLContext().get("cookieName");
			ResponseCookie cookie = ResponseCookie.from("cookieName", value).build();
			response.getResponseHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString());
		});
	}
}

@Controller
class MyCookieController {

	@QueryMapping
	Person person(GraphQLContext context) { (1)
		context.put("cookieName", "123");
		...
	}
}
1 Controller adds value to the GraphQLContext
2 Interceptor uses the value to add an HTTP response header

WebGraphQlHandler 可以修改 ExecutionResult,例如,检查和修改在执行开始之前触发的请求验证错误,这些错误无法使用 DataFetcherExceptionResolver 处理:

WebGraphQlHandler can modify the ExecutionResult, for example, to inspect and modify request validation errors that are raised before execution begins and which cannot be handled with a DataFetcherExceptionResolver:

include-code::RequestErrorInterceptor[]<1> 如果 ExecutionResult 具有非空值的“数据”键,则返回相同<2> 检查并转换 GraphQL 错误<3> 使用已修改的错误更新 ExecutionResult

import java.util.List;

import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import reactor.core.publisher.Mono;

import org.springframework.graphql.server.WebGraphQlInterceptor;
import org.springframework.graphql.server.WebGraphQlRequest;
import org.springframework.graphql.server.WebGraphQlResponse;

class RequestErrorInterceptor implements WebGraphQlInterceptor {

	@Override
	public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
		return chain.next(request).map(response -> {
			if (response.isValid()) {
				return response; (1)
			}

			List<GraphQLError> errors = response.getErrors().stream() (2)
					.map(error -> {
						GraphqlErrorBuilder<?> builder = GraphqlErrorBuilder.newError();
						// ...
						return builder.build();
					})
					.toList();

			return response.transform(builder -> builder.errors(errors).build()); (3)
		});
	}
}
1 Return the same if ExecutionResult has a "data" key with non-null value
2 Check and transform the GraphQL errors
3 Update the ExecutionResult with the modified errors

使用 WebGraphQlHandler 配置 WebGraphQlInterceptor 链。这受 Boot Starter 支持,请参阅 Web 终结点

Use WebGraphQlHandler to configure the WebGraphQlInterceptor chain. This is supported by the Boot Starter, see Web Endpoints.

RSocketQlInterceptor

类似于 WebGraphQlInterceptor,一个 RSocketQlInterceptor 允许在 GraphQL Java 引擎执行之前和之后拦截通过 RSocket 请求的 GraphQL。您可以使用此功能自定义 graphql.ExecutionInputgraphql.ExecutionResult

Similar to WebGraphQlInterceptor, an RSocketQlInterceptor allows intercepting GraphQL over RSocket requests before and after GraphQL Java engine execution. You can use this to customize the graphql.ExecutionInput and the graphql.ExecutionResult.