diff --git a/core/src/main/java/feign/ClientInterceptor.java b/core/src/main/java/feign/ClientInterceptor.java index 6087ade00..5d1218799 100644 --- a/core/src/main/java/feign/ClientInterceptor.java +++ b/core/src/main/java/feign/ClientInterceptor.java @@ -13,41 +13,26 @@ */ package feign; +import java.util.Iterator; + /** * Zero or One {@code ClientInterceptor} may be configured for purposes such as tracing - mutate * headers, execute the call, parse the response and close any previously created objects. */ public interface ClientInterceptor { - ClientInterceptor DEFAULT = new ClientInterceptor() { - @Override - public void beforeExecute(ClientInvocationContext context) {} - - @Override - public void afterExecute(ClientInvocationContext context, - Response response, - Throwable exception) { - - } - }; - - /** - * Called before the request was made. Can be used to mutate the request and create objects that - * later will be passed to {@link #afterExecute(ClientInvocationContext, Response, Throwable)}. - * - * @param clientInvocationContext information surrounding the request - */ - void beforeExecute(ClientInvocationContext clientInvocationContext); + ClientInterceptor DEFAULT = (context, iterator) -> null; /** + * Allows to make an around instrumentation. Remember to call the next interceptor by calling + * {@code ClientInterceptor interceptor = iterator.next(); return interceptor.around(context, iterator)}. * - * @param clientInvocationContext information surrounding the request - * @param response received undecoded response (can be null when an exception occurred) - * @param exception exception that occurred while trying to send the request or receive the - * response (can be null when there was no exception) + * @param context input context to send a request + * @param iterator iterator to the next {@link ClientInterceptor} + * @return response or an exception - remember to rethrow an exception if it occurrs + * @throws FeignException exception while trying to send a request */ - void afterExecute(ClientInvocationContext clientInvocationContext, - Response response, - Throwable exception); + Response around(ClientInvocationContext context, Iterator iterator) + throws FeignException; } diff --git a/core/src/main/java/feign/ClientInvocationContext.java b/core/src/main/java/feign/ClientInvocationContext.java index 850626693..b340d7ff0 100644 --- a/core/src/main/java/feign/ClientInvocationContext.java +++ b/core/src/main/java/feign/ClientInvocationContext.java @@ -13,17 +13,12 @@ */ package feign; -import java.util.HashMap; -import java.util.Map; - public class ClientInvocationContext { private final RequestTemplate requestTemplate; private final Request.Options options; - private final Map holder = new HashMap<>(); - public ClientInvocationContext(RequestTemplate requestTemplate, Request.Options options) { this.requestTemplate = requestTemplate; this.options = options; @@ -36,8 +31,4 @@ public RequestTemplate getRequestTemplate() { public Request.Options getOptions() { return options; } - - public Map getHolder() { - return holder; - } } diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 4208a3417..155fcf9e8 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -21,6 +21,8 @@ import feign.codec.Decoder; import feign.codec.ErrorDecoder; import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -115,37 +117,11 @@ public Object invoke(Object[] argv) throws Throwable { Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { ClientInvocationContext context = new ClientInvocationContext(template, options); - for (ClientInterceptor interceptor : this.clientInterceptors) { - interceptor.beforeExecute(context); - } - - Request request = targetRequest(template); - - if (logLevel != Logger.Level.NONE) { - logger.logRequest(metadata.configKey(), logLevel, request); - } - - Response response = null; - Throwable throwable = null; long start = System.nanoTime(); - try { - response = client.execute(request, options); - // ensure the request is set. TODO: remove in Feign 12 - response = response.toBuilder() - .request(request) - .requestTemplate(template) - .build(); - } catch (IOException e) { - throwable = e; - if (logLevel != Logger.Level.NONE) { - logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); - } - throw errorExecuting(request, e); - } finally { - for (ClientInterceptor interceptor : this.clientInterceptors) { - interceptor.afterExecute(context, response, throwable); - } - } + InterceptorChain interceptorChain = + new InterceptorChain(this.clientInterceptors, new HttpCall(this.metadata, this.target, + this.requestInterceptors, this.logger, this.logLevel, this.client, start)); + Response response = interceptorChain.call(context); long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); if (decoder != null) { @@ -169,17 +145,6 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa } } - long elapsedTime(long start) { - return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); - } - - Request targetRequest(RequestTemplate template) { - for (RequestInterceptor interceptor : requestInterceptors) { - interceptor.apply(template); - } - return target.apply(template); - } - Options findOptions(Object[] argv) { if (argv == null || argv.length == 0) { return this.options; @@ -191,6 +156,92 @@ Options findOptions(Object[] argv) { .orElse(this.options); } + static class HttpCall { + private final MethodMetadata metadata; + private final Target target; + private final List requestInterceptors; + private final Logger logger; + private final Logger.Level logLevel; + private final Client client; + + private final long start; + + HttpCall(MethodMetadata metadata, Target target, + List requestInterceptors, Logger logger, Logger.Level logLevel, + Client client, long start) { + this.metadata = metadata; + this.target = target; + this.requestInterceptors = requestInterceptors; + this.logger = logger; + this.logLevel = logLevel; + this.client = client; + this.start = start; + } + + Response call(RequestTemplate template, Request.Options options) { + Request request = targetRequest(template); + + if (logLevel != Logger.Level.NONE) { + logger.logRequest(metadata.configKey(), logLevel, request); + } + + Response response; + try { + response = client.execute(request, options); + // ensure the request is set. TODO: remove in Feign 12 + return response.toBuilder() + .request(request) + .requestTemplate(template) + .build(); + } catch (IOException e) { + if (logLevel != Logger.Level.NONE) { + logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); + } + throw errorExecuting(request, e); + } + } + + Request targetRequest(RequestTemplate template) { + for (RequestInterceptor interceptor : requestInterceptors) { + interceptor.apply(template); + } + return target.apply(template); + } + + long elapsedTime(long start) { + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + } + } + + private static class HttpCallingInterceptor implements ClientInterceptor { + + private final HttpCall httpCall; + + HttpCallingInterceptor(HttpCall httpCall) { + this.httpCall = httpCall; + } + + @Override + public Response around(ClientInvocationContext context, Iterator iterator) { + return httpCall.call(context.getRequestTemplate(), context.getOptions()); + } + } + + private static class InterceptorChain { + private final List interceptors; + + InterceptorChain(List interceptors, HttpCall httpCall) { + this.interceptors = new ArrayList<>(interceptors); + this.interceptors.add(new HttpCallingInterceptor(httpCall)); + } + + Response call(ClientInvocationContext context) { + Iterator iterator = this.interceptors.iterator(); + ClientInterceptor next = iterator.next(); + return next.around(context, iterator); + } + } + static class Factory { private final Client client; diff --git a/micrometer/src/main/java/feign/micrometer/ObservedClientInterceptor.java b/micrometer/src/main/java/feign/micrometer/ObservedClientInterceptor.java index 1fbe6e714..a84b0b1cf 100644 --- a/micrometer/src/main/java/feign/micrometer/ObservedClientInterceptor.java +++ b/micrometer/src/main/java/feign/micrometer/ObservedClientInterceptor.java @@ -13,9 +13,11 @@ */ package feign.micrometer; +import java.util.Iterator; import feign.Client; import feign.ClientInterceptor; import feign.ClientInvocationContext; +import feign.FeignException; import feign.Response; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; @@ -38,31 +40,28 @@ public ObservedClientInterceptor(ObservationRegistry observationRegistry) { } @Override - public void beforeExecute(ClientInvocationContext clientInvocationContext) { - FeignContext feignContext = new FeignContext(clientInvocationContext.getRequestTemplate()); + public Response around(ClientInvocationContext context, Iterator iterator) + throws FeignException { + FeignContext feignContext = new FeignContext(context.getRequestTemplate()); Observation observation = FeignDocumentedObservation.DEFAULT .observation(this.customFeignObservationConvention, DefaultFeignObservationConvention.INSTANCE, feignContext, this.observationRegistry) .start(); - clientInvocationContext.getHolder().put(Observation.class, observation); - clientInvocationContext.getHolder().put(FeignContext.class, feignContext); - } - - @Override - public void afterExecute(ClientInvocationContext clientInvocationContext, - Response response, - Throwable exception) { - FeignContext feignContext = - (FeignContext) clientInvocationContext.getHolder().get(FeignContext.class); - Observation observation = - (Observation) clientInvocationContext.getHolder().get(Observation.class); - if (feignContext == null) { - return; - } - feignContext.setResponse(response); - if (exception != null) { - observation.error(exception); + Exception ex = null; + Response response = null; + try { + ClientInterceptor interceptor = iterator.next(); + response = interceptor.around(context, iterator); + return response; + } catch (FeignException exception) { + ex = exception; + throw exception; + } finally { + feignContext.setResponse(response); + if (ex != null) { + observation.error(ex); + } + observation.stop(); } - observation.stop(); } }