2. 服务器传输

Spring for GraphQL 支持通过 HTTP、WebSocket 和 RSocket 的spring-doc.cadn.net.cn

2.1. HTTP协议

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

请求必须使用 HTTP POST 和"application/json"作为内容类型和 GraphQL 请求详细信息 作为 JSON 包含在请求正文中,如建议的 GraphQL over HTTP 规范中所定义。 成功解码 JSON 正文后,HTTP 响应状态始终为 200 (OK), GraphQL 请求执行中的任何错误都会显示在 GraphQL 响应的“错误”部分中。 媒体类型的默认和首选选择是"application/graphql-response+json""application/json"也受支持,如规范中所述。spring-doc.cadn.net.cn

GraphQlHttpHandler可以通过声明RouterFunctionbean 并使用RouterFunctions从 Spring MVC 或 WebFlux 创建路由。Boot Starter 执行此作,请参阅 Web Endpoints 部分 details 或检查GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration它包含实际配置。spring-doc.cadn.net.cn

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

2.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

2.2. WebSocket 浏览器

GraphQlWebSocketHandler根据 graphql-ws 库中定义的协议处理 WebSocket 上的 GraphQL 请求。使用的主要原因 基于 WebSocket 的 GraphQL 是允许发送 GraphQL 流的订阅 responses,但它也可以用于具有单个响应的常规查询。 处理程序将每个请求委托给 Interception 链以进一步 请求执行。spring-doc.cadn.net.cn

基于 WebSocket 协议的 GraphQL

有两种这样的协议,一种在 subscriptions-transport-ws 库中,另一种在 graphql-ws 库中。前者不活跃且 由后者接替。阅读这篇博文了解历史。spring-doc.cadn.net.cn

有两种变体GraphQlWebSocketHandler,一个用于 Spring MVC,一个用于 Spring WebFlux 的 Web Flux 中。两者都异步处理请求,并且具有等效的功能。 WebFlux 处理程序还使用非阻塞 I/O 和背压来流式传输消息, 这很好用,因为在 GraphQL Java 中,订阅响应是反应式流Publisher.spring-doc.cadn.net.cn

graphql-wsproject 列出了许多供客户端使用的配方spring-doc.cadn.net.cn

GraphQlWebSocketHandler可以通过声明SimpleUrlHandlerMappingbean 并使用它将处理程序映射到 URL 路径。默认情况下, Boot Starter 不会通过 WebSocket 端点公开 GraphQL,但很容易 通过为 Endpoint path 添加属性来启用它。有关详细信息,请参阅 Web 端点部分,或查看GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration以获取实际的 Boot Starter 配置。spring-doc.cadn.net.cn

此存储库的 1.0.x 分支包含一个 WebFlux WebSocket 示例应用程序。spring-doc.cadn.net.cn

2.3. RSocket

GraphQlRSocketHandler处理 GraphQL over RSocket 请求。查询和更改是 expected 并作为 RSocket 处理request-response订阅 处理为request-stream.spring-doc.cadn.net.cn

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

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

2.4. Interception

Server transports allow intercepting requests before and after the GraphQL Java engine is called to process a request.spring-doc.cadn.net.cn

2.4.1. WebGraphQlInterceptor

HTTP and WebSocket transports invoke a chain of 0 or more WebGraphQlInterceptor, followed by an ExecutionGraphQlService that calls the GraphQL Java engine. WebGraphQlInterceptor allows an application to intercept incoming requests and do one of the following:spring-doc.cadn.net.cn

For example, an interceptor can pass an HTTP request header to a DataFetcher:spring-doc.cadn.net.cn

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 Interceptor adds HTTP request header value into GraphQLContext
2 Data controller method accesses the value

Reversely, an interceptor can access values added to the GraphQLContext by a controller:spring-doc.cadn.net.cn

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 Controller adds value to the GraphQLContext
2 Interceptor uses the value to add an HTTP response header

WebGraphQlHandler can modify the ExecutionResult, for example, to inspect and modify request validation errors that are raised before execution begins and which cannot be handled with a DataFetcherExceptionResolver:spring-doc.cadn.net.cn

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 Return the same if ExecutionResult has a "data" key with non-null value
2 Check and transform the GraphQL errors
3 Update the ExecutionResult with the modified errors

Use WebGraphQlHandler to configure the WebGraphQlInterceptor chain. This is supported by the Boot Starter, see Web Endpoints.spring-doc.cadn.net.cn

2.4.2. RSocketQlInterceptor

Similar to WebGraphQlInterceptor, an RSocketQlInterceptor allows intercepting GraphQL over RSocket requests before and after GraphQL Java engine execution. You can use this to customize the graphql.ExecutionInput and the graphql.ExecutionResult.spring-doc.cadn.net.cn