伺服器傳輸

Spring for GraphQL 支援透過 HTTP、WebSocket 和 RSocket 處理伺服器端的 GraphQL 請求。

HTTP

GraphQlHttpHandler 處理透過 HTTP 的 GraphQL 請求,並委派給攔截鏈以執行請求。有兩種變體,一種用於 Spring MVC,另一種用於 Spring WebFlux。兩者都以非同步方式處理請求,並具有等效的功能,但分別依賴於阻塞式與非阻塞式 I/O 來寫入 HTTP 回應。

請求必須使用 HTTP POST,內容類型為 "application/json",並在請求 body 中包含 GraphQL 請求詳細資訊作為 JSON,如提議的 GraphQL over HTTP 規範中所定義。一旦 JSON body 成功解碼,HTTP 回應狀態始終為 200 (OK),並且來自 GraphQL 請求執行的任何錯誤都會出現在 GraphQL 回應的 "errors" 區段中。預設和首選的媒體類型是 "application/graphql-response+json",但也支援 "application/json",如規範中所述。

可以透過宣告 RouterFunction bean 並使用 Spring MVC 或 WebFlux 中的 RouterFunctions 來建立路由,從而將 GraphQlHttpHandler 作為 HTTP 端點公開。Boot Starter 會執行此操作,詳細資訊請參閱網路端點章節,或查看 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 以取得實際設定。

預設情況下,GraphQlHttpHandler 將使用 HttpMessageConverter (Spring MVC) 和 DecoderHttpMessageReader/EncoderHttpMessageWriter (WebFlux)(在網路框架中設定)序列化和反序列化 JSON payload。在某些情況下,應用程式將以與 GraphQL payload 不相容的方式設定 HTTP 端點的 JSON 編碼解碼器。應用程式可以使用自訂 JSON 編碼解碼器來實例化 GraphQlHttpHandler,該編碼解碼器將用於 GraphQL payload。

此儲存庫的 1.0.x 分支包含 Spring MVC HTTP 範例 應用程式。

伺服器發送事件

GraphQlSseHandler 與上面列出的 HTTP 處理器非常相似,但這次是使用伺服器發送事件協定處理透過 HTTP 的 GraphQL 請求。使用此傳輸方式,用戶端必須將 HTTP POST 請求傳送到端點,內容類型為 "application/json",並在請求 body 中包含 GraphQL 請求詳細資訊作為 JSON;與原始 HTTP 變體的唯一區別是用戶端必須傳送 "text/event-stream" 作為 "Accept" 請求標頭。回應將以一個或多個伺服器發送事件的形式傳送。

這也在提議的 GraphQL over HTTP 規範中定義。Spring for GraphQL 僅實作「獨立連線模式」,因此應用程式必須考慮擴充性問題,以及採用 HTTP/2 作為底層傳輸是否有幫助。

GraphQlSseHandler 的主要用例是 WebSocket 傳輸的替代方案,接收項目串流作為訂閱操作的回應。其他類型的操作,如查詢和 mutation,在此處不受支援,應使用純 JSON over HTTP 傳輸變體。

檔案上傳

作為一個協定,GraphQL 專注於文字資料的交換。這不包括二進位資料,例如圖像,但有一個獨立的、非正式的 graphql-multipart-request-spec,允許使用 GraphQL over HTTP 進行檔案上傳。

Spring for GraphQL 不直接支援 graphql-multipart-request-spec。雖然規範確實提供了統一 GraphQL API 的優勢,但實際體驗已導致許多問題,並且最佳實務建議已經發展,請參閱 Apollo Server 檔案上傳最佳實務 以取得更詳細的討論。

如果您想在應用程式中使用 graphql-multipart-request-spec,您可以透過程式庫 multipart-spring-graphql 來實現。

WebSocket

GraphQlWebSocketHandler 根據 protocol 處理透過 WebSocket 的 GraphQL 請求,該協定在 graphql-ws 程式庫中定義。透過 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 專案列出了許多用於用戶端使用的 recipes

可以透過宣告 SimpleUrlHandlerMapping bean 並使用它將處理器對應到 URL 路徑,從而將 GraphQlWebSocketHandler 作為 WebSocket 端點公開。預設情況下,Boot Starter 不會公開 GraphQL over WebSocket 端點,但您可以新增端點路徑的屬性來啟用它。請查看 Boot 參考文件中的 網路端點,以及支援的 spring.graphql.websocket 屬性列表。您也可以查看 GraphQlWebMvcAutoConfigurationGraphQlWebFluxAutoConfiguration 以取得實際的 Boot 自動設定詳細資訊。

此儲存庫的 1.0.x 分支包含 WebFlux WebSocket 範例 應用程式。

RSocket

GraphQlRSocketHandler 處理透過 RSocket 的 GraphQL 請求。查詢和 mutation 預期並作為 RSocket request-response 互動處理,而訂閱則作為 request-stream 處理。

GraphQlRSocketHandler 可以用作 @Controller 的委派,該 @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

HTTPWebSocket 傳輸調用 0 個或多個 WebGraphQlInterceptor 的鏈,然後調用 ExecutionGraphQlService,後者調用 GraphQL Java 引擎。攔截器允許應用程式攔截傳入的請求,以便

  • 檢查 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 具有帶有非 null 值的 "data" 金鑰,則傳回相同的值
2 檢查和轉換 GraphQL 錯誤
3 使用修改後的錯誤更新 ExecutionResult

使用 WebGraphQlHandler 來設定 WebGraphQlInterceptor 鏈。Boot Starter 支援此功能,請參閱 網路端點

WebSocketGraphQlInterceptor

WebSocketGraphQlInterceptor 擴充了 WebGraphQlInterceptor,新增了額外的回呼以處理 WebSocket 連線的開始和結束,以及用戶端取消訂閱。它還攔截 WebSocket 連線上的每個 GraphQL 請求。

使用 WebGraphQlHandler 來設定 WebGraphQlInterceptor 鏈。Boot Starter 支援此功能,請參閱 網路端點。攔截器鏈中最多只能有一個 WebSocketGraphQlInterceptor

有兩個內建的 WebSocket 攔截器,稱為 AuthenticationWebSocketInterceptor,一個用於 WebMVC 傳輸,另一個用於 WebFlux 傳輸。這些攔截器有助於從 "connection_init" GraphQL over WebSocket 訊息的 payload 中提取驗證詳細資訊、進行驗證,然後將 SecurityContext 傳播到 WebSocket 連線上的後續請求。

spring-graphql-examples 中有一個 websocket-authentication 範例。

RSocketQlInterceptor

WebGraphQlInterceptor 類似,RSocketQlInterceptor 允許在 GraphQL Java 引擎執行之前和之後攔截透過 RSocket 的 GraphQL 請求。您可以使用它來自訂 graphql.ExecutionInputgraphql.ExecutionResult