伺服器傳輸
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
。