服务器传输
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"
,如规范所述。
通过声明一个 RouterFunction
bean 并使用 Spring MVC 或 WebFlux 中的 RouterFunctions
来创建路由,可以将 GraphQlHttpHandler
暴露为 HTTP 端点。Boot Starter 就做了这件事,详情请参阅Web 端点部分,或者查看其中包含的 GraphQlWebMvcAutoConfiguration
或 GraphQlWebFluxAutoConfiguration
获取实际配置。
默认情况下,GraphQlHttpHandler
会使用 Web 框架中配置的 HttpMessageConverter
(Spring MVC) 和 DecoderHttpMessageReader/EncoderHttpMessageWriter
(WebFlux) 来序列化和反序列化 JSON 有效载荷。在某些情况下,应用程序为 HTTP 端点配置的 JSON 编解码器可能与 GraphQL 有效载荷不兼容。应用程序可以使用自定义 JSON 编解码器实例化 GraphQlHttpHandler
,该编解码器将用于 GraphQL 有效载荷。
此仓库的 1.0.x 分支包含一个 Spring MVC HTTP 示例应用程序。
服务器发送事件
GraphQlSseHandler
与上面列出的 HTTP handler 非常相似,但它使用 Server-Sent Events 协议处理基于 HTTP 的 GraphQL 请求。使用此传输方式时,客户端必须向端点发送 HTTP POST 请求,内容类型为 "application/json"
,GraphQL 请求详情作为 JSON 包含在请求体中;与普通 HTTP 变体的唯一区别在于客户端必须发送 "text/event-stream"
作为 "Accept"
请求头。响应将作为一条或多条 Server-Sent Event 发送。
这也在提议的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 响应流,但它也可以用于单个响应的常规查询。该 handler 将每个请求委托给拦截器链以进一步执行请求。
GraphQL Over WebSocket 协议
存在两种此类协议,一种在 subscriptions-transport-ws 库中,另一种在 graphql-ws 库中。前者不再活跃,已被后者取代。请阅读这篇博客文章了解其历史。 |
GraphQlWebSocketHandler
有两种变体,一种用于 Spring MVC,一种用于 Spring WebFlux。两者都异步处理请求,功能等效。WebFlux handler 还使用非阻塞 I/O 和背压来流式传输消息,这非常有效,因为在 GraphQL Java 中,订阅响应是一个 Reactive Streams Publisher
。
graphql-ws
项目列出了许多客户端使用的示例。
通过声明一个 SimpleUrlHandlerMapping
bean 并使用它将 handler 映射到 URL 路径,可以将 GraphQlWebSocketHandler
暴露为 WebSocket 端点。默认情况下,Boot Starter 不会暴露 GraphQL over WebSocket 端点,但您可以添加一个指定端点路径的属性来启用它。请查阅 Boot 参考文档中的Web 端点部分,以及支持的 spring.graphql.websocket
属性列表。您也可以查看 GraphQlWebMvcAutoConfiguration
或 GraphQlWebFluxAutoConfiguration
获取实际的 Boot 自动配置详情。
此仓库的 1.0.x 分支包含一个 WebFlux WebSocket 示例应用程序。
RSocket
GraphQlRSocketHandler
处理基于 RSocket 的 GraphQL 请求。查询和变更操作期望并被作为 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
HTTP 和 WebSocket 传输方式调用一个包含 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 传输。这些拦截器有助于从 GraphQL over WebSocket 消息的 "connection_init"
有效载荷中提取认证详情,进行认证,然后将 SecurityContext
传播到 WebSocket 连接上的后续请求。
在 spring-graphql-examples 中有一个 websocket-authentication 示例。 |
RSocketQlInterceptor
类似于WebGraphQlInterceptor
,RSocketQlInterceptor
允许在 GraphQL Java 引擎执行之前和之后拦截 GraphQL over RSocket 请求。您可以使用它来自定义 graphql.ExecutionInput
和 graphql.ExecutionResult
。