このバージョンはまだ開発中であり、まだ安定しているとは見なされていません。最新の安定バージョンについては、Spring GraphQL 2.0.5 を使用してください! |
サーバートランスポート
Spring for GraphQL は HTTP、WebSocket、RSocket を介した GraphQL リクエストの処理をサポートしています。
HTTP
GraphQlHttpHandler は HTTP リクエストを介して GraphQL を処理し、リクエストの実行のためにインターセプトチェーンに委譲します。Spring MVC 用と Spring WebFlux 用の 2 つのバリエーションがあります。どちらもリクエストを非同期に処理し、同等の機能を備えていますが、HTTP レスポンスを書き込むために、それぞれブロッキング I/O とノンブロッキング I/O に依存しています。
By default, GraphQlHttpHandler only accepts HTTP POST requests, with "application/json" as content type and GraphQL request details included as JSON in the request body. Clients can request the "application/graphql-response+json" media type to get the behavior defined in the official HTTP 経由の GraphQL [GitHub] (英語) specification. If the client doesn’t express any preference, this will be the content type of choice. Clients can also request the legacy "application/json" media type to get the legacy HTTP behavior.
実際には、サーバーが利用できない場合、セキュリティ資格情報が見つからない場合、リクエスト本文が有効な JSON でない場合、GraphQL HTTP クライアントは 4xx/5xx HTTP レスポンスを期待する必要があります。クライアントから送信された GraphQL ドキュメントを解析できない場合、または GraphQL エンジンによって無効と見なされた場合、"application/graphql-response+json" レスポンスでも 4xx ステータスが使用されます。この場合、"application/json" レスポンスでは引き続き 200 (OK) が使用されます。GraphQL リクエストが正常に検証されると、HTTP レスポンスステータスは常に 200 (OK) になり、GraphQL リクエスト実行によるエラーは GraphQL レスポンスの「エラー」セクションに表示されます。
RouterFunction Bean を宣言し、Spring MVC または WebFlux からの RouterFunctions を使用してルートを作成することにより、GraphQlHttpHandler を HTTP エンドポイントとして公開できます。Boot スターターはこれを行います。詳細については Web エンドポイントセクションを参照するか、実際の構成については含まれている GraphQlWebMvcAutoConfiguration または GraphQlWebFluxAutoConfiguration を確認してください。
デフォルトでは、GraphQlHttpHandler は、Web フレームワークで構成された HttpMessageConverter (Spring MVC) と DecoderHttpMessageReader/EncoderHttpMessageWriter (WebFlux) を使用して JSON ペイロードを直列化およびデ直列化します。場合によっては、アプリケーションが HTTP エンドポイントの JSON コーデックを GraphQL ペイロードと互換性のない方法で構成することがあります。アプリケーションは、GraphQL ペイロードに使用されるカスタム JSON コーデックを使用して GraphQlHttpHandler をインスタンス化できます。
HTTP GET
GraphQlHttpHandler can optionally also accept HTTP GET requests, as defined in the GraphQL over HTTP 仕様 (英語) . This is useful for clients that can only issue GET requests, such as a browser EventSource, or for responses that a CDN or intermediate cache can store. GET support is disabled by default and must be enabled explicitly, on both the handler and the matching RequestPredicate:
GraphQlHttpHandler httpHandler = GraphQlHttpHandler.builder(webGraphQlHandler)
.httpMethods(HttpMethod.GET, HttpMethod.POST)
.build();
RequestPredicate predicate = GraphQlRequestPredicates.graphQlHttp("/graphql",
Set.of(HttpMethod.GET, HttpMethod.POST));On a GET request, there is no request body: query and operationName are read as plain query string parameters, while variables and extensions, when present, must each be a JSON string. An empty variables or extensions parameter is treated the same as if it were absent. If the request URI would become too large to encode a request this way, clients should fall back to HTTP POST.
Because GET is a "safe" HTTP method, it must not be used to execute mutations. A GET request whose operation is a mutation is rejected with a 405 (Method Not Allowed) response, with an Allow header listing the HTTP methods the endpoint accepts; this applies regardless of the requested media type.
Before enabling GET support, consider the following:
The query string, including
variables, typically ends up in access logs, browser history,Refererheaders, and intermediate proxies or caches. Do not enable GET ifvariablesmay carry sensitive data, unless those exposure paths are otherwise mitigated.A GET request without a
Content-Typeheader is a CORS "simple request": it is sent cross-origin with credentials and without a preflight check. The response itself is not readable by the calling script cross-origin, but the request is still executed on the server, including any side effects a query might have.Spring Security’s
CsrfFiltertreats GET as a safe method and does not require a CSRF token for it.
Mutations remain blocked either way, but read-side side effects, such as rate limiting, cost, or audit logging, still apply and should be considered as part of enabling this transport.
HTTP QUERY
GraphQlHttpHandler can optionally also accept the HTTP QUERY method, defined in RFC 10008 (英語) . Unlike GET, QUERY carries the GraphQL request in the request body, using the same JSON encoding as POST, so a Content-Type header is required. QUERY support is disabled by default and must be enabled explicitly, on both the handler and the matching RequestPredicate:
GraphQlHttpHandler httpHandler = GraphQlHttpHandler.builder(webGraphQlHandler)
.httpMethods(HttpMethod.QUERY, HttpMethod.POST)
.build();
RequestPredicate predicate = GraphQlRequestPredicates.graphQlHttp("/graphql",
Set.of(HttpMethod.QUERY, HttpMethod.POST));Like GET, QUERY is a "safe" HTTP method and must not be used to execute mutations. However, since QUERY is a method the resource does support, 405 (Method Not Allowed) would not be accurate here: a QUERY request whose operation is a mutation is rejected instead with a 422 (Unprocessable Content) response, without an Allow header, regardless of the requested media type.
Unlike GET, QUERY is not a CORS "simple request": browsers always send a preflight request for it, and Spring Security’s CsrfFilter requires a CSRF token for it, since QUERY is not in the safe-method allowlist. This may be worth noting when migrating clients from POST, where CSRF may have been disabled.
サーバー送信イベント
GraphQlSseHandler is very similar to the HTTP handler listed above, but this time handling GraphQL requests over HTTP using the Server-Sent Events protocol. With this transport, clients send HTTP POST requests to the endpoint by default, with "application/json" as content type and GraphQL request details included as JSON in the request body; the only difference with the vanilla HTTP variant is that the client must send "text/event-stream" as the "Accept" request header. The response will be sent as one or more Server-Sent Event(s).
これは、提案されている HTTP 経由の GraphQL [GitHub] (英語) 仕様でも定義されています。Spring for GraphQL は「個別接続モード」のみを実装しているため、アプリケーションはスケーラビリティの問題と、基礎となるトランスポートとして HTTP/2 を採用することが役立つかどうかを考慮する必要があります。
GraphQlSseHandler の主な使用例は、WebSocket 輸送の代替であり、サブスクリプション操作へのレスポンスとして項目のストリームを受信します。クエリやミューテーションなどの他の種類の操作はここではサポートされていないため、プレーンな JSON over HTTP トランスポートバリアントを使用する必要があります。
Like GraphQlHttpHandler, GraphQlSseHandler only accepts HTTP POST requests by default. HTTP GET can be enabled the same way, on both the handler and the matching RequestPredicate, which is useful since a browser EventSource, a common client for this transport, can only issue GET requests:
GraphQlSseHandler sseHandler = GraphQlSseHandler.builder(webGraphQlHandler)
.httpMethods(HttpMethod.GET, HttpMethod.POST)
.build();
RequestPredicate ssePredicate = GraphQlRequestPredicates.graphQlSse("/graphql",
Set.of(HttpMethod.GET, HttpMethod.POST));See the HTTP GET section above for the query string encoding rules and the security considerations that also apply here.
ファイルアップロード
プロトコルとしての GraphQL はテキストデータの交換に重点を置いています。これにはイメージなどのバイナリデータは含まれませんが、HTTP 経由で GraphQL を使用してファイルをアップロードできるようにする別の非公式の graphql-multipart-request-spec [GitHub] (英語) があります。
Spring for GraphQL は graphql-multipart-request-spec を直接サポートしていません。仕様は統合 GraphQL API の利点を提供しますが、実際の使用では多くの問題が発生し、ベストプラクティスの推奨事項も変化しています。詳細については、Apollo サーバーのファイルアップロードのベストプラクティス (英語) を参照してください。
アプリケーションで graphql-multipart-request-spec を使用したい場合は、ライブラリ multipart-spring-graphql [GitHub] (英語) を通じて使用できます。
WebSocket
GraphQlWebSocketHandler は、graphql-ws [GitHub] (英語) ライブラリで定義されたプロトコル [GitHub] (英語) に基づいて、WebSocket リクエストを介した GraphQL を処理します。WebSocket で GraphQL を使用する主な理由は、GraphQL レスポンスのストリームを送信できるサブスクリプションですが、単一のレスポンスを持つ通常のクエリにも使用できます。ハンドラーは、さらにリクエストを実行するために、すべてのリクエストをインターセプトチェーンに委譲します。
WebSocket プロトコルを介した GraphQL このようなプロトコルは 2 つあります。1 つは subscriptions-transport-ws [GitHub] (英語) ライブラリにあり、もう 1 つは graphql-ws [GitHub] (英語) ライブラリにあります。前者は活動せず、後者に引き継がれています。歴史については、このブログ記事 (英語) を参照してください。 |
GraphQlWebSocketHandler には、Spring MVC 用と Spring WebFlux 用の 2 つのバリアントがあります。どちらもリクエストを非同期に処理し、同等の機能を備えています。WebFlux ハンドラーは、ノンブロッキング I/O とバックプレッシャーも使用してメッセージをストリーミングします。これは、GraphQL Java ではサブスクリプションレスポンスが Reactive Streams Publisher であるため、うまく機能します。
graphql-ws プロジェクトには、クライアントが使用する多数のレシピ [GitHub] (英語) がリストされています。
GraphQlWebSocketHandler は、SimpleUrlHandlerMapping Bean を宣言し、それを使用してハンドラーを URL パスにマップすることで、WebSocket エンドポイントとして公開できます。デフォルトでは、Boot スターターは WebSocket エンドポイント上の GraphQL を公開しませんが、エンドポイントパスのプロパティを追加して有効にすることができます。Boot リファレンスドキュメントの Web エンドポイントと、サポートされている spring.graphql.websocket プロパティのリストを確認してください。実際の Boot 自動構成の詳細については、GraphQlWebMvcAutoConfiguration または GraphQlWebFluxAutoConfiguration を確認することもできます。
このリポジトリの 1.0.x ブランチには、WebFlux WebSocket サンプル [GitHub] (英語) アプリケーションが含まれています。
WebSocket Resource Management
HTTP リクエストとは異なり、WebSocket 接続はクライアントが接続を維持している限り開いたままになり、1 つのセッションで複数の同時サブスクリプションを同時に処理できます。アプリケーションは、実行時に使用される CPU とメモリの量が想定範囲内に収まるように、デプロイのサイズを適切に設定する必要があります。これは、単一のクライアントがデプロイの残りの部分に与える影響を制限するため、有効な安全策にもなります。以下の項目を設定することをお勧めします。
The maximum number of open server connections, which is a property of the underlying server or connector rather than something Spring for GraphQL configures directly.
The maximum size of a buffered WebSocket message. For the Spring MVC variant, this is configured through the Jakarta WebSocket
ServerContainer, for example withsetDefaultMaxTextMessageBufferSize. For the Spring WebFlux variant, the equivalent setting depends on the underlying reactive server.The maximum number of concurrent subscriptions a single WebSocket session is allowed to have, configured with the
maxSubscriptionsPerSessionbuilder option onGraphQlWebSocketHandler. By default, there is no limit; once it is reached, the session is closed.
RSocket
GraphQlRSocketHandler は、GraphQL over RSocket リクエストを処理します。サブスクリプションは request-stream として処理されますが、クエリとミューテーションは RSocket request-response インタラクションとして予期され、処理されます。
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をカスタマイズするもっと
Spring for GraphQL は、リクエストから GraphQL コンテキストに HTTP ヘッダーをコピーする組み込みの HttpRequestHeaderInterceptor を提供します。これにより、アノテーション付きコントローラーなどのデータフェッチャーがヘッダーを利用できるようになります。たとえば、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 | Interceptor は値を使用して 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 以外の値を持つ「データ」キーがある場合は、同じものを返します。 |
| 2 | GraphQL エラーを確認して変換する |
| 3 | 変更されたエラーで ExecutionResult を更新します |
WebGraphQlHandler を使用して、WebGraphQlInterceptor チェーンを構成します。これは Boot スターターでサポートされています。Web エンドポイントを参照してください。
WebSocketGraphQlInterceptor
WebSocketGraphQlInterceptor は、クライアント側のサブスクリプションのキャンセルに加えて、WebSocket 接続の開始と終了を処理するための追加のコールバックで WebGraphQlInterceptor を継承します。また、WebSocket 接続上のすべての GraphQL リクエストもインターセプトします。
WebGraphQlHandler を使用して WebGraphQlInterceptor チェーンを構成します。これは Boot スターターでサポートされています。Web エンドポイントを参照してください。インターセプターの チェーンには最大 1 つの WebSocketGraphQlInterceptor を含めることができます。
AuthenticationWebSocketInterceptor と呼ばれる 2 つの組み込み WebSocket インターセプターがあり、1 つは WebMVC 用、もう 1 つは WebFlux トランスポート用です。これらは、"connection_init" GraphQL over WebSocket メッセージのペイロードから認証の詳細を抽出し、認証してから、SecurityContext を WebSocket 接続の後続のリクエストに伝播できます。
| spring-graphql-examples [GitHub] (英語) には websocket-authentication のサンプルがあります。 |
RSocketQlInterceptor
WebGraphQlInterceptor と同様に、RSocketQlInterceptor を使用すると、GraphQL Java エンジンの実行の前後に、RSocket リクエストを介して GraphQL をインターセプトできます。これを使用して、graphql.ExecutionInput および graphql.ExecutionResult をカスタマイズできます。