サーバートランスポート
Spring for GraphQL は、HTTP、WebSocket、RSocket を介した GraphQL リクエストのサーバー処理をサポートします。
HTTP
GraphQlHttpHandler
は HTTP リクエストを介して GraphQL を処理し、リクエストの実行のためにインターセプトチェーンに委譲します。Spring MVC 用と Spring WebFlux 用の 2 つのバリエーションがあります。どちらもリクエストを非同期に処理し、同等の機能を備えていますが、HTTP レスポンスを書き込むために、それぞれブロッキング I/O とノンブロッキング I/O に依存しています。
リクエストは、コンテンツ型として "application/json"
を指定した HTTP POST を使用し、リクエスト本文に JSON として含まれる GraphQL リクエストの詳細を、提案された HTTP 経由の GraphQL [GitHub] (英語) 仕様で定義されているように使用する必要があります。JSON ボディが正常にデコードされると、HTTP レスポンスのステータスは常に 200 (OK) になり、GraphQL リクエストの実行によるエラーは GraphQL レスポンスの「エラー」セクションに表示されます。メディア型のデフォルトで推奨される選択肢は "application/graphql-response+json"
ですが、仕様に従って "application/json"
もサポートされています。
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
をインスタンス化できます。
このリポジトリの 1.0.x ブランチには、Spring MVC HTTP サンプル [GitHub] (英語) アプリケーションが含まれています。
サーバー送信イベント
GraphQlSseHandler
は上記の HTTP ハンドラーとよく似ていますが、今回は Server-Sent Events プロトコルを使用して HTTP 経由で GraphQL リクエストを処理します。このトランスポートを使用する場合、クライアントは、コンテンツ型として "application/json"
を使用し、GraphQL リクエストの詳細をリクエスト本文に JSON として含めて、HTTP POST リクエストをエンドポイントに送信する必要があります。バニラの HTTP バリアントとの唯一の違いは、クライアントが "text/event-stream"
を "Accept"
リクエストヘッダーとして送信する必要があることです。レスポンスは 1 つ以上のサーバー送信イベントとして送信されます。
これは、提案されている HTTP 経由の GraphQL [GitHub] (英語) 仕様でも定義されています。Spring for GraphQL は「個別接続モード」のみを実装しているため、アプリケーションはスケーラビリティの問題と、基礎となるトランスポートとして HTTP/2 を採用することが役立つかどうかを考慮する必要があります。
GraphQlSseHandler
の主な使用例は、WebSocket 輸送の代替であり、サブスクリプション操作へのレスポンスとして項目のストリームを受信します。クエリやミューテーションなどの他の種類の操作はここではサポートされていないため、プレーンな JSON over HTTP トランスポートバリアントを使用する必要があります。
ファイルアップロード
プロトコルとしての 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] (英語) アプリケーションが含まれています。
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
をカスタマイズするもっと
例: インターセプターは、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 | 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
をカスタマイズできます。