服务器传输

Spring for GraphQL 支持通过 HTTP、WebSocket 和 RSocket 处理 GraphQL 请求。

HTTP

GraphQlHttpHandler 处理通过 HTTP 请求的 GraphQL,并委托给拦截链进行请求执行。它有两个变体,一个用于 Spring MVC,另一个用于 Spring WebFlux。两者都异步处理请求并具有等效的功能,但分别依赖于阻塞和非阻塞 I/O 来写入 HTTP 响应。

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

GraphQlHttpHandler 可以通过声明一个 RouterFunction bean 并使用 Spring MVC 或 WebFlux 中的 RouterFunctions 创建路由来公开为 HTTP 端点。启动器 会执行此操作,有关详细信息,请参阅Web 端点部分,或查看包含它的 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration,以获取实际的配置。

默认情况下,GraphQlHttpHandler 将使用 Web 框架中配置的 HttpMessageConverter (Spring MVC) 和 DecoderHttpMessageReader/EncoderHttpMessageWriter (WebFlux) 序列化和反序列化 JSON 有效负载。在某些情况下,应用程序会以与 GraphQL 有效负载不兼容的方式配置 HTTP 端点的 JSON 编解码器。应用程序可以使用自定义 JSON 编解码器实例化 GraphQlHttpHandler,该编解码器将用于 GraphQL 有效负载。

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

服务器发送事件

GraphQlSseHandler 与上面列出的 HTTP 处理程序非常相似,但这次是使用服务器发送事件协议通过 HTTP 处理 GraphQL 请求。使用此传输,客户端必须向端点发送 HTTP POST 请求,内容类型为 "application/json",并将 GraphQL 请求详细信息作为 JSON 包含在请求正文中;与普通 HTTP 变体唯一的区别是客户端必须发送 "text/event-stream" 作为 "Accept" 请求头。响应将作为一个或多个服务器发送事件发送。

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

GraphQlSseHandler 的主要用例是 WebSocket 传输 的替代方案,作为对订阅操作的响应接收项目流。此处不支持其他类型的操作,例如查询和变异,应使用普通 JSON over HTTP 传输变体。

文件上传

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

Spring for GraphQL 不直接支持 graphql-multipart-request-spec。虽然该规范确实提供了统一 GraphQL API 的好处,但实际体验导致了许多问题,最佳实践建议也发生了变化,请参阅Apollo Server 文件上传最佳实践以获取更详细的讨论。

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

WebSocket

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

GraphQL Over WebSocket 协议

有两种这样的协议,一种在subscriptions-transport-ws库中,另一种在graphql-ws库中。前者不再活跃,后者取代了前者。阅读这篇博文了解历史。

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

graphql-ws 项目列出了许多示例供客户端使用。

GraphQlWebSocketHandler可以通过声明一个SimpleUrlHandlerMapping bean 并使用它将处理程序映射到 URL 路径来作为 WebSocket 端点公开。默认情况下,Boot Starter 不会公开 GraphQL over WebSocket 端点,但您可以添加端点路径的属性来启用它。请查看 Boot 参考文档中的Web 端点,以及受支持的spring.graphql.websocket 属性列表。您还可以查看GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 以了解实际的 Boot 自动配置详细信息。

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

RSocket

GraphQlRSocketHandler处理 GraphQL over RSocket 请求。查询和变异被期望并作为 RSocket request-response 交互处理,而订阅则作为request-stream处理。

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

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);
	}
}

拦截

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

WebGraphQlInterceptor

HTTPWebSocket 传输调用 0 个或多个WebGraphQlInterceptor的链,然后是调用 GraphQL Java 引擎的ExecutionGraphQlService。拦截器允许应用程序拦截传入请求以

  • 检查 HTTP 请求详细信息

  • 自定义graphql.ExecutionInput

  • 添加 HTTP 响应头

  • 自定义graphql.ExecutionResult

  • 等等

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

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 拦截器将 HTTP 请求头值添加到 GraphQLContext 中
2 数据控制器方法访问该值

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

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 控制器将值添加到GraphQLContext
2 拦截器使用该值添加 HTTP 响应头

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

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 如果ExecutionResult具有一个带有非空值的“data”键,则返回相同的值
2 检查和转换 GraphQL 错误
3 使用修改后的错误更新ExecutionResult

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

WebSocketGraphQlInterceptor

WebSocketGraphQlInterceptor扩展了WebGraphQlInterceptor,并提供了额外的回调来处理 WebSocket 连接的开始和结束,以及客户端对订阅的取消。它还拦截 WebSocket 连接上的每个 GraphQL 请求。

使用WebGraphQlHandler配置WebGraphQlInterceptor链。这受Boot Starter支持,请参阅Web 端点。拦截器链中最多只能有一个WebSocketGraphQlInterceptor

有两个内置的 WebSocket 拦截器称为AuthenticationWebSocketInterceptor,一个用于 WebMVC,另一个用于 WebFlux 传输。这些有助于从"connection_init" GraphQL over WebSocket 消息的有效负载中提取身份验证详细信息,进行身份验证,然后将SecurityContext传播到 WebSocket 连接上的后续请求。

spring-graphql-examples中有一个 websocket-authentication 示例。

RSocketQlInterceptor

类似于WebGraphQlInterceptorRSocketQlInterceptor允许在 GraphQL Java 引擎执行之前和之后拦截 GraphQL over RSocket 请求。您可以使用它来自定义graphql.ExecutionInputgraphql.ExecutionResult