Loading...
Spring Framework Reference Documentation 7.0.2의 Filters의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
WebClient.Builder를 통해 client filter (ExchangeFilterFunction)를 등록하여 요청을 가로채고 수정할 수 있으며, 다음 예제와 같습니다:
1WebClient client = WebClient.builder() 2 .filter((request, next) -> { 3 4 ClientRequest filtered = ClientRequest.from(request) 5 .header("foo", "bar") 6 .build(); 7 8 return next.exchange(filtered); 9 }) 10 .build();
1val client = WebClient.builder() 2 .filter { request, next -> 3 4 val filtered = ClientRequest.from(request) 5 .header("foo", "bar") 6 .build() 7 8 next.exchange(filtered) 9 } 10 .build()
이는 authentication과 같은 cross-cutting concern에 사용할 수 있습니다. 다음 예제는 static factory method를 통해 basic authentication을 위한 filter를 사용합니다:
1import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; 2 3WebClient client = WebClient.builder() 4 .filter(basicAuthentication("user", "password")) 5 .build();
1import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication 2 3val client = WebClient.builder() 4 .filter(basicAuthentication("user", "password")) 5 .build()
Filter는 기존 WebClient 인스턴스를 mutate하여 추가하거나 제거할 수 있으며, 원래의 인스턴스에 영향을 주지 않는 새로운 WebClient 인스턴스를 생성합니다. 예를 들면:
1import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; 2 3WebClient client = webClient.mutate() 4 .filters(filterList -> { 5 filterList.add(0, basicAuthentication("user", "password")); 6 }) 7 .build();
1val client = webClient.mutate() 2 .filters { it.add(0, basicAuthentication("user", "password")) } 3 .build()
WebClient는 filter 체인 다음에 이어지는 ExchangeFunction 주위의 얇은 facade입니다. 이는 요청을 만들고, 상위 수준의 객체로부터 인코딩하고 인코딩하는 워크플로를 제공하며, response content가 항상 소비되도록 돕습니다.
Filter가 어떤 방식으로든 response를 처리할 때에는 그 content를 항상 소비하거나 그렇지 않으면 downstream으로 WebClient에 전달하여 동일한 동작을 보장하도록 특별한 주의를 기울여야 합니다. 아래는 UNAUTHORIZED status code를 처리하지만 예상했든 하지 않았든 모든 response content가 해제되도록 보장하는 filter입니다:
1public ExchangeFilterFunction renewTokenFilter() { 2 return (request, next) -> next.exchange(request).flatMap(response -> { 3 if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) { 4 return response.releaseBody() 5 .then(renewToken()) 6 .flatMap(token -> { 7 ClientRequest newRequest = ClientRequest.from(request).build(); 8 return next.exchange(newRequest); 9 }); 10 } else { 11 return Mono.just(response); 12 } 13 }); 14}
1fun renewTokenFilter(): ExchangeFilterFunction? { 2 return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction -> 3 next.exchange(request!!).flatMap { response: ClientResponse -> 4 if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) { 5 return@flatMap response.releaseBody() 6 .then(renewToken()) 7 .flatMap { token: String? -> 8 val newRequest = ClientRequest.from(request).build() 9 next.exchange(newRequest) 10 } 11 } else { 12 return@flatMap Mono.just(response) 13 } 14 } 15 } 16}
아래 예제는 ExchangeFilterFunction 인터페이스를 사용하여 buffering을 통해 PUT 및 POST multipart/form-data 요청에 대한 Content-Length 헤더를 계산하는 데 도움이 되는 custom filter class를 생성하는 방법을 보여줍니다.
1public class MultipartExchangeFilterFunction implements ExchangeFilterFunction { 2 3 @Override 4 public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { 5 if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType()) 6 && (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) { 7 return next.exchange(ClientRequest.from(request).body((outputMessage, context) -> 8 request.body().insert(new BufferingDecorator(outputMessage), context)).build() 9 ); 10 } else { 11 return next.exchange(request); 12 } 13 } 14 15 private static final class BufferingDecorator extends ClientHttpRequestDecorator { 16 17 private BufferingDecorator(ClientHttpRequest delegate) { 18 super(delegate); 19 } 20 21 @Override 22 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { 23 return DataBufferUtils.join(body).flatMap(buffer -> { 24 getHeaders().setContentLength(buffer.readableByteCount()); 25 return super.writeWith(Mono.just(buffer)); 26 }); 27 } 28 } 29}
1class MultipartExchangeFilterFunction : ExchangeFilterFunction { 2 3 override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> { 4 return if (MediaType.MULTIPART_FORM_DATA.includes(request.headers().getContentType()) 5 && (request.method() == HttpMethod.PUT || request.method() == HttpMethod.POST)) { 6 next.exchange(ClientRequest.from(request) 7 .body { message, context -> request.body().insert(BufferingDecorator(message), context) } 8 .build()) 9 } 10 else { 11 next.exchange(request) 12 } 13 14 } 15 16 private class BufferingDecorator(delegate: ClientHttpRequest) : ClientHttpRequestDecorator(delegate) { 17 override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> { 18 return DataBufferUtils.join(body) 19 .flatMap { 20 headers.contentLength = it.readableByteCount().toLong() 21 super.writeWith(Mono.just(it)) 22 } 23 } 24 } 25}
Request Body
Attributes