服务器传输
Spring for GraphQL 支持通过 HTTP、WebSocket 和 RSocket 处理 GraphQL 请求。
HTTP
GraphQlHttpHandler
通过 HTTP 请求处理 GraphQL,并委托给 拦截 链执行请求。有两种变体,一种用于 Spring MVC,另一种用于 Spring WebFlux。两者都异步处理请求,并具有等效的功能,但分别依赖于阻塞 I/O 与非阻塞 I/O 来编写 HTTP 响应。
请求必须使用 HTTP POST,内容类型为 "application/json"
,并且请求正文中包含 JSON 中的 GraphQL 请求详细信息,如建议的 GraphQL over HTTP 规范中所定义。成功解码 JSON 正文后,HTTP 响应状态始终为 200(正常),并且 GraphQL 请求执行中的任何错误都将显示在 GraphQL 响应的“errors”部分中。默认且首选的媒体类型是 "application/graphql-response+json"
,但 "application/json"
也受支持,如规范中所述。
GraphQlHttpHandler
可以通过声明 RouterFunction
bean 并使用 Spring MVC 或 WebFlux 中的 RouterFunctions
来创建路由,从而公开为 HTTP 端点。 Boot Starter 会这样做,有关详细信息,请参阅 Web 端点 部分,或查看其中包含的 GraphQlWebMvcAutoConfiguration
或 GraphQlWebFluxAutoConfiguration
,以了解实际配置。
默认情况下,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 传输 的替代方案,接收订阅操作响应的一系列项目。此处不支持其他类型的操作,如查询和突变,并且应该使用通过 HTTP 传输变体的纯 JSON。
文件上传
作为一种协议,GraphQL 专注于文本数据的交换。这不包括二进制数据,如图像,但有一个单独的非正式 graphql-multipart-request-spec,允许通过 HTTP 上传 GraphQL 文件。
Spring for GraphQL 不直接支持 graphql-multipart-request-spec
。虽然该规范确实提供了统一 GraphQL API 的好处,但实际体验导致了许多问题,并且最佳实践建议已经发展,请参阅 Apollo Server File Upload Best Practices 以进行更详细的讨论。
如果您想在应用程序中使用 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
属性 列表。你还可以查看 GraphQlWebMvcAutoConfiguration
或 `GraphQlWebFluxAutoConfiguration` 以了解实际的 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
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 传输。这些拦截器有助于从 WebSocket 消息上的 GraphQL“connection_init”有效负载中提取身份验证详细信息、进行身份验证,然后将 SecurityContext
传播到 WebSocket 连接上的后续请求。
RSocketQlInterceptor
类似于 WebGraphQlInterceptor
,RSocketQlInterceptor
允许在 GraphQL Java 引擎执行之前和之后拦截 RSocket 上的 GraphQL 请求。您可以使用它来自定义 graphql.ExecutionInput
和 graphql.ExecutionResult
。