Filters

你可以通过 WebClient.Builder 注册一个客户端过滤器(ExchangeFilterFunction)以拦截和修改请求,如下示例所示:

You can register a client filter (ExchangeFilterFunction) through the WebClient.Builder in order to intercept and modify requests, as the following example shows:

  • 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()

这可用于横切关注点,如认证。以下示例使用了一个通过静态工厂方法的简单认证过滤器:

This can be used for cross-cutting concerns, such as authentication. The following example uses a filter for basic authentication through a static factory method:

  • 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 实例。例如:

Filters can be added or removed by mutating an existing WebClient instance, resulting in a new WebClient instance that does not affect the original one. For example:

  • 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 状态码的过滤器,但确保发出任何响应内容(无论预期与否):

WebClient is a thin facade around the chain of filters followed by an ExchangeFunction. It provides a workflow to make requests, to encode to and from higher level objects, and it helps to ensure that response content is always consumed. When filters handle the response in some way, extra care must be taken to always consume its content or to otherwise propagate it downstream to the WebClient which will ensure the same. Below is a filter that handles the UNAUTHORIZED status code but ensures that any response content, whether expected or not, is released:

  • 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 接口创建一个自定义过滤器类,该类使用缓冲区帮助计算 PUTPOST multipart/form-data 请求的内容长度头。

The example below demonstrates how to use the ExchangeFilterFunction interface to create a custom filter class that helps with computing a Content-Length header for PUT and POST multipart/form-data requests using buffering.

  • 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))
                }
        }
    }
}