サーバートランスポート

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");
		...
	}
}
1GraphQLContext に付加価値を与えるコントローラー
2Interceptor は値を使用して 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)
		});
	}
}
1ExecutionResult に null 以外の値を持つ「データ」キーがある場合は、同じものを返します。
2GraphQL エラーを確認して変換する
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 をカスタマイズできます。