From 3f7d3cbbd38a3d63e3f1c781bfe6924b630cb4ee Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 7 Jan 2025 15:16:34 +0000 Subject: [PATCH] Add chaining methods to ClientHttpRequestInterceptor See gh-34169 --- .../client/ClientHttpRequestInterceptor.java | 31 +++++++++++++++- .../client/InterceptingClientHttpRequest.java | 37 +++++-------------- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java b/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java index 9c22b283fad..2751b4b25db 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java +++ b/spring-web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.springframework.http.client; import java.io.IOException; import org.springframework.http.HttpRequest; +import org.springframework.util.Assert; /** * Contract to intercept client-side HTTP requests. Implementations can be @@ -60,4 +61,32 @@ public interface ClientHttpRequestInterceptor { ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException; + /** + * Return a new interceptor that invokes {@code this} interceptor first, and + * then the one that's passed in. + * @param interceptor the next interceptor + * @return a new interceptor that chains the two + * @since 7.0 + */ + default ClientHttpRequestInterceptor andThen(ClientHttpRequestInterceptor interceptor) { + Assert.notNull(interceptor, "ClientHttpRequestInterceptor must not be null"); + return (request, body, execution) -> { + ClientHttpRequestExecution nextExecution = + (nextRequest, nextBody) -> interceptor.intercept(nextRequest, nextBody, execution); + return intercept(request, body, nextExecution); + }; + } + + /** + * Return a new execution that invokes {@code this} interceptor, and then + * delegates to the given execution. + * @param execution the execution to delegate to + * @return a new execution instance + * @since 7.0 + */ + default ClientHttpRequestExecution apply(ClientHttpRequestExecution execution) { + Assert.notNull(execution, "ClientHttpRequestExecution must not be null"); + return (request, body) -> intercept(request, body, execution); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java index 9a7181ce834..fd47cdc39f5 100644 --- a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.List; -import java.util.ListIterator; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -69,39 +68,23 @@ class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { @Override protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { - ClientHttpRequestExecution requestExecution = new DelegatingRequestExecution(this.requestFactory); - ListIterator iterator = this.interceptors.listIterator(this.interceptors.size()); - while (iterator.hasPrevious()) { - ClientHttpRequestInterceptor interceptor = iterator.previous(); - requestExecution = new InterceptingRequestExecution(interceptor, requestExecution); - } - return requestExecution.execute(this, bufferedOutput); + return getExecution().execute(this, bufferedOutput); } - - private static class InterceptingRequestExecution implements ClientHttpRequestExecution { - - private final ClientHttpRequestInterceptor interceptor; - - private final ClientHttpRequestExecution nextExecution; - - public InterceptingRequestExecution(ClientHttpRequestInterceptor interceptor, ClientHttpRequestExecution nextExecution) { - this.interceptor = interceptor; - this.nextExecution = nextExecution; - } - - @Override - public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { - return this.interceptor.intercept(request, body, this.nextExecution); - } - + private ClientHttpRequestExecution getExecution() { + ClientHttpRequestExecution execution = new EndOfChainRequestExecution(this.requestFactory); + return this.interceptors.stream() + .reduce(ClientHttpRequestInterceptor::andThen) + .map(interceptor -> interceptor.apply(execution)) + .orElse(execution); } - private static class DelegatingRequestExecution implements ClientHttpRequestExecution { + + private static class EndOfChainRequestExecution implements ClientHttpRequestExecution { private final ClientHttpRequestFactory requestFactory; - public DelegatingRequestExecution(ClientHttpRequestFactory requestFactory) { + public EndOfChainRequestExecution(ClientHttpRequestFactory requestFactory) { this.requestFactory = requestFactory; }