跳到主要内容

服务器传输协议

DeepSeek V3 中英对照 Server Transports

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

HTTP​

GraphQlHttpHandler 用于处理基于 HTTP 的 GraphQL 请求,并将请求执行委托给拦截器链。它有两种变体:一种用于 Spring MVC,另一种用于 Spring WebFlux。两者均以异步方式处理请求,并具备相同的功能,但在写入 HTTP 响应时分别依赖于阻塞式与非阻塞式 I/O。

请求必须使用 HTTP POST 方法,内容类型为 "application/json",并将 GraphQL 请求详情作为 JSON 包含在请求体中。客户端可以请求 "application/graphql-response+json" 媒体类型,以获得官方 GraphQL over HTTP 规范中定义的行为。如果客户端未表达任何偏好,这将是默认的内容类型。客户端也可以请求传统的 "application/json" 媒体类型,以获得传统的 HTTP 行为。

在实践中,如果服务器不可用、安全凭证缺失或请求体不是有效的JSON,GraphQL HTTP客户端应预期收到4xx/5xx HTTP响应。当客户端发送的GraphQL文档无法解析或被GraphQL引擎视为无效时,"application/graphql-response+json" 响应也会使用4xx状态码。而在此情况下,"application/json" 响应仍会使用200(OK)状态码。一旦GraphQL请求成功通过验证,HTTP响应状态将始终为200(OK),任何GraphQL请求执行过程中的错误都会出现在GraphQL响应的"errors"部分。

GraphQlHttpHandler 可以通过声明一个 RouterFunction bean 并使用 Spring MVC 或 WebFlux 中的 RouterFunctions 来创建路由,从而将其暴露为 HTTP 端点。Boot Starter 会完成此操作,详情请参阅 Web Endpoints 部分,或者查看其中包含的 GraphQlWebMvcAutoConfiguration 或 GraphQlWebFluxAutoConfiguration 以了解实际配置。

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

服务器发送事件​

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

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

GraphQlSseHandler 的主要使用场景是作为 WebSocket 传输方式 的替代方案,用于接收作为订阅操作响应的数据流。其他类型的操作,例如查询和变更,在此处不受支持,应使用基于 HTTP 传输的普通 JSON 格式。

文件上传​

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 基于 graphql-ws 库中定义的协议处理 WebSocket 上的 GraphQL 请求。使用 WebSocket 承载 GraphQL 的主要原因是支持订阅功能,该功能允许发送 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 Endpoints 以及支持的 spring.graphql.websocket 属性列表。你也可以查看 GraphQlWebMvcAutoConfiguration 或 GraphQlWebFluxAutoConfiguration 以了解实际的 Boot 自动配置细节。

该仓库的 1.0.x 分支包含一个 WebFlux WebSocket 示例应用。

RSocket​

GraphQlRSocketHandler 用于处理通过 RSocket 传输的 GraphQL 请求。查询和变更操作以 RSocket 的 request-response 交互模式进行处理,而订阅操作则以 request-stream 模式处理。

GraphQlRSocketHandler 可以作为 @Controller 的委托,映射到处理 GraphQL 请求的路由。例如:

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​

HTTP 与 WebSocket 传输层会调用一个由 0 个或多个 WebGraphQlInterceptor 组成的链,随后由 ExecutionGraphQlService 调用 GraphQL Java 引擎。拦截器允许应用程序拦截传入的请求,以便:

  • 检查 HTTP 请求详情

  • 自定义 graphql.ExecutionInput

  • 添加 HTTP 响应头

  • 自定义 graphql.ExecutionResult

  • 以及更多

Spring for GraphQL 提供了一个内置的 HttpRequestHeaderInterceptor,它会将 HTTP 请求头复制到 GraphQL 上下文中,从而使数据获取器(例如带注解的控制器)能够访问这些请求头。例如,在 Spring Boot 应用程序中,可以按如下方式实现:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.data.method.annotation.ContextValue;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.support.HttpRequestHeaderInterceptor;
import org.springframework.stereotype.Controller;

@Configuration
class RequestHeaderInterceptorConfig {

@Bean
public HttpRequestHeaderInterceptor headerInterceptor() { 1
return HttpRequestHeaderInterceptor.builder().mapHeader("myHeader").build();
}
}

@Controller
class MyContextValueController { 2

@QueryMapping
Person person(@ContextValue String myHeader) {
...
}
}
  • 创建拦截器,将 HTTP 请求头值复制到 GraphQLContext 中

  • 数据控制器方法访问该值

拦截器也可以访问控制器添加到 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");
...
}
}
  • Controller 向 GraphQLContext 添加值

  • Interceptor 使用该值添加 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
});
}
}
  • 如果 ExecutionResult 的 "data" 键包含非空值,则返回相同结果

  • 检查并转换 GraphQL 错误

  • 使用修改后的错误更新 ExecutionResult

使用 WebGraphQlHandler 来配置 WebGraphQlInterceptor 链。这由 Boot Starter 支持,请参阅 Web Endpoints。

WebSocketGraphQlInterceptor​

WebSocketGraphQlInterceptor 继承自 WebGraphQlInterceptor,并增加了额外的回调函数来处理 WebSocket 连接的开始和结束,以及客户端取消订阅的操作。同时,它也会拦截 WebSocket 连接上的每一个 GraphQL 请求。

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

有两个内置的 WebSocket 拦截器,名为 AuthenticationWebSocketInterceptor,分别用于 WebMVC 和 WebFlux 传输方式。它们的作用是从 WebSocket 上 GraphQL 消息 "connection_init" 的有效负载中提取认证信息,进行身份验证,然后将 SecurityContext 传播到 WebSocket 连接上的后续请求。

提示

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

RSocketQlInterceptor​

类似于 WebGraphQlInterceptor,RSocketQlInterceptor 允许在 GraphQL Java 引擎执行前后拦截通过 RSocket 传输的 GraphQL 请求。您可以使用它来自定义 graphql.ExecutionInput 和 graphql.ExecutionResult。