3. 服务器传输

Spring for GraphQL 支持通过HTTP、WebSocket和RSocket处理GraphQL请求的服务器端处理。spring-doc.cadn.net.cn

3.1. HTTP

GraphQlHttpHandler 处理 GraphQL over HTTP 请求,并将请求委托给 拦截链 执行。有两个变体,一个用于 Spring MVC,另一个用于 Spring WebFlux。两者都异步处理请求并且功能等价,但分别依赖于阻塞和非阻塞 I/O 来写入 HTTP 响应。spring-doc.cadn.net.cn

请求必须使用HTTP POST,并将GraphQL请求细节以JSON格式包含在请求主体中,如GraphQL over HTTP提议的规范所述。一旦JSON主体被成功解码,HTTP响应状态始终为200(OK),并且从GraphQL请求执行中生成的任何错误将在GraphQL响应的"errors"部分出现。默认且首选的媒体类型是"application/graphql+json",但根据规范,"application/json"也受支持。spring-doc.cadn.net.cn

GraphQlHttpHandler 可以通过声明一个 RouterFunction bean 并使用 Spring MVC 或 WebFlux 的 RouterFunctions 来暴露为 HTTP 端点。Boot 起步器会执行此操作,详见 Web端点 部分以获取详细信息,或者查看 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 中的内容,以获得实际的配置。spring-doc.cadn.net.cn

The Spring for GraphQL仓库包含一个Spring MVC HTTP示例 应用程序。spring-doc.cadn.net.cn

3.1.1. 文件上传

作为协议,GraphQL 关注的是文本数据的交换。这不包括如图像这样的二进制数据,但有一个单独的、非正式的 graphql-multipart-request-spec 允许通过 HTTP 使用 GraphQL 进行文件上传。spring-doc.cadn.net.cn

Spring for GraphQL 不直接支持 graphql-multipart-request-spec。 尽管规范提供了统一的 GraphQL API 的好处,实际体验却带来了一些问题,并且最佳实践推荐也随之演变,请参阅 Apollo Server 文件上传最佳实践 以获得更详细的讨论。spring-doc.cadn.net.cn

如果您希望在应用中使用graphql-multipart-request-spec,可以通过库 multipart-spring-graphql 实现。spring-doc.cadn.net.cn

3.2. WebSocket

GraphQlWebSocketHandler 基于 graphql-ws 库中定义的协议 处理 GraphQL over WebSocket 请求。使用 GraphQL over WebSocket 的主要原因在于它可以进行订阅,允许发送 GraphQL 响应流,但也可以用于常规查询并返回单个响应。处理器将每个请求委托给 拦截链 以进一步执行请求。spring-doc.cadn.net.cn

GraphQL Over WebSocket 协议

存在两个这样的协议,一个在 subscriptions-transport-ws 库中,另一个在 graphql-ws 库中。前者已经不再使用,并被后者所取代。您可以阅读这篇 博客文章 了解历史。spring-doc.cadn.net.cn

Spring 框架中有两种 GraphQlWebSocketHandler 的变体,一种用于 Spring MVC,另一种用于 Spring WebFlux。这两种方式都异步处理请求,并具有同等的功能。WebFlux 处理程序还使用非阻塞 I/O 和回压来流式传输消息,这在 GraphQL Java 中表现良好,因为订阅响应是一个 Reactive Streams Publisherspring-doc.cadn.net.cn

The graphql-ws项目列出了若干 Recipes以供客户端使用。spring-doc.cadn.net.cn

GraphQlWebSocketHandler 可以通过声明一个SimpleUrlHandlerMapping Bean 并使用它将处理器映射到一个 URL 路径来暴露为 WebSocket 端点。默认情况下,Boot 起步器不会暴露 GraphQL over WebSocket 端点,但可以通过添加端点路径的属性轻松启用它。请参阅Web 端点部分以获取详细信息,或查看GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 了解实际的 Boot 起步器配置。spring-doc.cadn.net.cn

The Spring for GraphQL仓库包含一个基于WebFlux的 WebSocket示例应用spring-doc.cadn.net.cn

3.3. RSocket

GraphQlRSocketHandler 处理通过 RSocket 的 GraphQL 请求。查询和突变被期望并以 RSocket request-response 交互的方式处理,而订阅则作为 request-stream 来处理。spring-doc.cadn.net.cn

GraphQlRSocketHandler 可以用作映射到GraphQL请求路由的 @Controller 的代理。例如:spring-doc.cadn.net.cn

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

3.4. 拦截

服务器传输允许在GraphQL Java引擎调用来处理请求之前和之后拦截请求。spring-doc.cadn.net.cn

3.4.1. WebGraphQlInterceptor

HTTPWebSocket 运输会调用一个或多个 WebGraphQlInterceptor 链,然后调用一个 ExecutionGraphQlService,其会调用 GraphQL Java 引擎。WebGraphQlInterceptor 允许应用程序拦截传入请求,并执行以下操作之一:spring-doc.cadn.net.cn

例如,一个拦截器可以将HTTP请求头传递给DataFetcherspring-doc.cadn.net.cn

class HeaderInterceptor 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 MyController { (2)

    @QueryMapping
    Person person(@ContextValue String myHeader) {
        // ...
    }
}
1 拦截器将HTTP请求头值添加到GraphQLContext中
2 数据控制器方法访问值

相反,一个拦截器可以访问控制器添加到GraphQLContext中的值:spring-doc.cadn.net.cn

@Controller
class MyController {

    @QueryMapping
    Person person(GraphQLContext context) { (1)
        context.put("cookieName", "123");
    }
}

// Subsequent access from a WebGraphQlInterceptor

class HeaderInterceptor 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());
        });
    }
}
1 控制器向GraphQLContext添加值
2 拦截器使用该值向HTTP响应头中添加一个头部。

WebGraphQlHandler 可以修改 ExecutionResult,例如,用于检查和修改在执行开始前抛出的请求验证错误,并且这些错误无法用 DataFetcherExceptionResolver 来处理:spring-doc.cadn.net.cn

static 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();
                    })
                    .collect(Collectors.toList());

            return response.transform(builder -> builder.errors(errors).build()); (3)
        });
    }
}
1 如果 `0` 具有非空的
2 检查并转换GraphQL错误
3 更新ExecutionResult为修改后的错误信息

使用WebGraphQlHandler来配置WebGraphQlInterceptor链。这受到BootStarters的支持,详见 Web端点spring-doc.cadn.net.cn

3.4.2. RSocketQlInterceptor

类似于 WebGraphQlInterceptorRSocketQlInterceptor 允许在 GraphQL Java 引擎执行前后拦截基于 RSocket 的 GraphQL 请求。您可以使用它来自定义 graphql.ExecutionInputgraphql.ExecutionResultspring-doc.cadn.net.cn