伺服器傳輸
Spring for GraphQL 支援透過 HTTP、WebSocket 和 RSocket 處理 GraphQL 請求。
HTTP
GraphQlHttpHandler 處理透過 HTTP 的 GraphQL 請求,並委託給 攔截 鏈執行請求。有兩種變體,一種用於 Spring MVC,另一種用於 Spring WebFlux。兩者都非同步處理請求,並具有等效的功能,但分別依賴於阻塞或非阻塞 I/O 來寫入 HTTP 響應。
請求必須使用 HTTP POST,內容型別為 "application/json",並在請求正文中以 JSON 形式包含 GraphQL 請求詳細資訊。客戶端可以請求 "application/graphql-response+json" 媒體型別以獲取官方 GraphQL over HTTP 規範中定義的行為。如果客戶端未明確表達任何偏好,這將是首選內容型別。客戶端也可以請求傳統的 "application/json" 媒體型別以獲取傳統的 HTTP 行為。
實際上,如果伺服器不可用、缺少安全憑據或請求正文不是有效 JSON,GraphQL HTTP 客戶端應預期收到 4xx/5xx HTTP 響應。如果客戶端傳送的 GraphQL 文件無法解析或被 GraphQL 引擎認為是無效的,"application/graphql-response+json" 響應也將使用 4xx 狀態。在這種情況下,"application/json" 響應仍將使用 200 (OK)。一旦 GraphQL 請求成功驗證,HTTP 響應狀態始終為 200 (OK),並且 GraphQL 請求執行中的任何錯誤都會出現在 GraphQL 響應的“errors”部分。
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 負載。
伺服器傳送事件 (Server-Sent Events)
GraphQlSseHandler 與上面列出的 HTTP 處理程式非常相似,但這次使用伺服器傳送事件協議處理透過 HTTP 的 GraphQL 請求。使用此傳輸方式,客戶端必須向端點發送 HTTP POST 請求,內容型別為 "application/json",並在請求正文中以 JSON 形式包含 GraphQL 請求詳細資訊;與普通 HTTP 變體的唯一區別是客戶端必須傳送 "text/event-stream" 作為 "Accept" 請求頭。響應將以一個或多個伺服器傳送事件的形式傳送。
這也在擬議的 GraphQL over HTTP 規範中定義。Spring for GraphQL 只實現了“獨立連線模式”,因此應用程式必須考慮可伸縮性問題以及採用 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 根據 graphql-ws 庫中定義的 協議 處理透過 WebSocket 的 GraphQL 請求。使用 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 處理透過 RSocket 的 GraphQL 請求。查詢和變更被預期並作為 RSocket request-response 互動處理,而訂閱則作為 request-stream 處理。
GraphQlRSocketHandler 可以作為 @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
HTTP 和 WebSocket 傳輸會呼叫一個包含 0 個或多個 WebGraphQlInterceptor 的鏈,然後是呼叫 GraphQL Java 引擎的 ExecutionGraphQlService。攔截器允許應用程式攔截傳入請求,以便
-
檢查 HTTP 請求詳細資訊
-
定製
graphql.ExecutionInput -
新增 HTTP 響應頭
-
定製
graphql.ExecutionResult -
等等
Spring for GraphQL 提供了一個內建的 HttpRequestHeaderInterceptor,它將 HTTP 頭從請求複製到 GraphQL 上下文,然後使其可供資料獲取器(例如帶註解的控制器)使用。例如,在 Spring Boot 應用程式中,這可以透過以下方式完成:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.data.method.annotation.ContextValue;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.server.support.HttpRequestHeaderInterceptor;
import org.springframework.stereotype.Controller;
@Configuration
class RequestHeaderInterceptorConfig {
@Bean
public HttpRequestHeaderInterceptor headerInterceptor() { (1)
return HttpRequestHeaderInterceptor.builder().mapHeader("myHeader").build();
}
}
@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。