フィルター

次の例に示すように、リクエストをインターセプトおよび変更するために、WebClient.Builder を介してクライアントフィルター(ExchangeFilterFunction)を登録できます。

  • Java

  • Kotlin

WebClient client = WebClient.builder()
		.filter((request, next) -> {

			ClientRequest filtered = ClientRequest.from(request)
					.header("foo", "bar")
					.build();

			return next.exchange(filtered);
		})
		.build();
val client = WebClient.builder()
		.filter { request, next ->

			val filtered = ClientRequest.from(request)
					.header("foo", "bar")
					.build()

			next.exchange(filtered)
		}
		.build()

これは、認証などの横断的な関心事に使用できます。次の例では、静的ファクトリ方式による基本認証にフィルターを使用しています。

  • Java

  • Kotlin

import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = WebClient.builder()
		.filter(basicAuthentication("user", "password"))
		.build();
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication

val client = WebClient.builder()
		.filter(basicAuthentication("user", "password"))
		.build()

フィルターは、既存の WebClient インスタンスを変更することで追加または削除でき、元のインスタンスに影響を与えない新しい WebClient インスタンスになります。例:

  • Java

  • Kotlin

import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = webClient.mutate()
		.filters(filterList -> {
			filterList.add(0, basicAuthentication("user", "password"));
		})
		.build();
val client = webClient.mutate()
		.filters { it.add(0, basicAuthentication("user", "password")) }
		.build()

WebClient は、フィルターのチェーンの周囲の薄いファサードであり、その後に ExchangeFunction が続きます。リクエストを作成したり、上位レベルのオブジェクトとの間でエンコードしたりするためのワークフローを提供し、レスポンスコンテンツが常に消費されるようにできます。フィルターが何らかの方法でレスポンスを処理する場合、常にそのコンテンツを消費するか、そうでなければ同じことを保証する WebClient にダウンストリームで伝播するように特別な注意を払う必要があります。以下は、UNAUTHORIZED ステータスコードを処理するが、予期されるかどうかに関係なく、すべてのレスポンスコンテンツが確実にリリースされるようにするフィルターです。

  • Java

  • Kotlin

public ExchangeFilterFunction renewTokenFilter() {
	return (request, next) -> next.exchange(request).flatMap(response -> {
		if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
			return response.releaseBody()
					.then(renewToken())
					.flatMap(token -> {
						ClientRequest newRequest = ClientRequest.from(request).build();
						return next.exchange(newRequest);
					});
		} else {
			return Mono.just(response);
		}
	});
}
fun renewTokenFilter(): ExchangeFilterFunction? {
	return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction ->
		next.exchange(request!!).flatMap { response: ClientResponse ->
			if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
				return@flatMap response.releaseBody()
						.then(renewToken())
						.flatMap { token: String? ->
							val newRequest = ClientRequest.from(request).build()
							next.exchange(newRequest)
						}
			} else {
				return@flatMap Mono.just(response)
			}
		}
	}
}

以下の例は、ExchangeFilterFunction インターフェースを使用して、バッファリングを使用した PUT および POSTmultipart/form-data リクエストの Content-Length ヘッダーの計算に役立つカスタムフィルタークラスを作成する方法を示しています。

  • Java

  • Kotlin

public class MultipartExchangeFilterFunction implements ExchangeFilterFunction {

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
        if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
                && (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
            return next.exchange(ClientRequest.from(request).body((outputMessage, context) ->
                request.body().insert(new BufferingDecorator(outputMessage), context)).build()
            );
        } else {
            return next.exchange(request);
        }
    }

    private static final class BufferingDecorator extends ClientHttpRequestDecorator {

        private BufferingDecorator(ClientHttpRequest delegate) {
            super(delegate);
        }

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            return DataBufferUtils.join(body).flatMap(buffer -> {
                getHeaders().setContentLength(buffer.readableByteCount());
                return super.writeWith(Mono.just(buffer));
            });
        }
    }
}
class MultipartExchangeFilterFunction : ExchangeFilterFunction {

    override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
        return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType())
            && (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) {
            next.exchange(ClientRequest.from(request)
                .body { message, context -> request.body().insert(BufferingDecorator(message), context) }
                .build())
        }
        else {
            next.exchange(request)
        }

    }

    private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) {
        override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
            return DataBufferUtils.join(body)
                .flatMap {
                    headers.contentLength = it.readableByteCount().toLong()
                    super.writeWith(Mono.just(it))
                }
        }
    }
}