From 40db4bae9b5f7457f9a0b51e4a013f3cc7f911d3 Mon Sep 17 00:00:00 2001 From: "Fei,Yanke" Date: Thu, 28 Apr 2022 10:48:22 +0800 Subject: [PATCH 1/6] add ResponseInterceptor support #1126 --- core/src/main/java/feign/Feign.java | 18 ++++++++-- .../main/java/feign/ResponseInterceptor.java | 29 ++++++++++++++++ .../java/feign/SynchronousMethodHandler.java | 34 +++++++++++++++---- 3 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 core/src/main/java/feign/ResponseInterceptor.java diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index 4f38a0b96..2c192606b 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -98,6 +98,8 @@ public static class Builder { private final List requestInterceptors = new ArrayList(); + private final List responseInterceptors = + new ArrayList(); private Logger.Level logLevel = Logger.Level.NONE; private Contract contract = new Contract.Default(); private Client client = new Client.Default(null, null); @@ -227,6 +229,14 @@ public Builder requestInterceptor(RequestInterceptor requestInterceptor) { return this; } + /** + * Adds a single response interceptor to the builder. + */ + public Builder responseInterceptor(ResponseInterceptor responseInterceptor) { + this.responseInterceptors.add(responseInterceptor); + return this; + } + /** * Sets the full set of request interceptors for the builder, overwriting any previous * interceptors. @@ -297,6 +307,9 @@ public Feign build() { List requestInterceptors = this.requestInterceptors.stream() .map(ri -> Capability.enrich(ri, capabilities)) .collect(Collectors.toList()); + List responseInterceptors = this.responseInterceptors.stream() + .map(ri -> Capability.enrich(ri, capabilities)) + .collect(Collectors.toList()); Logger logger = Capability.enrich(this.logger, capabilities); Contract contract = Capability.enrich(this.contract, capabilities); Options options = Capability.enrich(this.options, capabilities); @@ -307,8 +320,9 @@ public Feign build() { QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities); SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = - new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, - logLevel, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding); + new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, + responseInterceptors,logger, logLevel, dismiss404, closeAfterDecode, + propagationPolicy, forceDecoding); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java new file mode 100644 index 000000000..adb20d698 --- /dev/null +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -0,0 +1,29 @@ +package feign; + +/** + * Zero or more {@code ResponseInterceptor} may be configured for purposes + * such as verify or modify headers of response, verify the business status of decoded object. + * No guarantees are given with regards to the order that interceptors are applied. + * Once interceptors are applied, {@link ResponseInterceptor#beforeDecode(Response)} is called + * before decode method called, {@link ResponseInterceptor#afterDecode(Object)} is called + * after decode method called. + */ +public interface ResponseInterceptor { + + /** + * Called for response before decode, add data on the supplied {@link Response} or doing + * customized logic + * + * @param response + * @return + */ + void beforeDecode(Response response); + + /** + * Called for response after decode, add data to decoded object or doing customized logic + * + * @param response + * @return + */ + void afterDecode(Object response); +} diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 1d8f39a0d..ae89bf276 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -36,6 +36,7 @@ final class SynchronousMethodHandler implements MethodHandler { private final Client client; private final Retryer retryer; private final List requestInterceptors; + private final List responseInterceptors; private final Logger logger; private final Logger.Level logLevel; private final RequestTemplate.Factory buildTemplateFromArgs; @@ -48,8 +49,8 @@ final class SynchronousMethodHandler implements MethodHandler { private SynchronousMethodHandler(Target target, Client client, Retryer retryer, - List requestInterceptors, Logger logger, - Logger.Level logLevel, MethodMetadata metadata, + List requestInterceptors, List responseInterceptors, + Logger logger, Logger.Level logLevel, MethodMetadata metadata, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder, boolean dismiss404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy, @@ -60,6 +61,8 @@ private SynchronousMethodHandler(Target target, Client client, Retryer retrye this.retryer = checkNotNull(retryer, "retryer for %s", target); this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors for %s", target); + this.responseInterceptors = + checkNotNull(responseInterceptors, "responseInterceptors for %s", target); this.logger = checkNotNull(logger, "logger for %s", target); this.logLevel = checkNotNull(logLevel, "logLevel for %s", target); this.metadata = checkNotNull(metadata, "metadata for %s", target); @@ -130,9 +133,19 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); + for (ResponseInterceptor interceptor: responseInterceptors) { + interceptor.beforeDecode(response); + } + + if (decoder != null) { + Object object = decoder.decode(response, metadata.returnType()); + + for (ResponseInterceptor interceptor: responseInterceptors) { + interceptor.afterDecode(object); + } - if (decoder != null) - return decoder.decode(response, metadata.returnType()); + return object; + } CompletableFuture resultFuture = new CompletableFuture<>(); asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response, @@ -143,7 +156,11 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa if (!resultFuture.isDone()) throw new IllegalStateException("Response handling not done"); - return resultFuture.join(); + Object object = resultFuture.join(); + for (ResponseInterceptor interceptor: responseInterceptors) { + interceptor.afterDecode(object); + } + return object; } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause != null) @@ -179,6 +196,7 @@ static class Factory { private final Client client; private final Retryer retryer; private final List requestInterceptors; + private final List responseInterceptors; private final Logger logger; private final Logger.Level logLevel; private final boolean dismiss404; @@ -187,11 +205,13 @@ static class Factory { private final boolean forceDecoding; Factory(Client client, Retryer retryer, List requestInterceptors, + List responseInterceptors, Logger logger, Logger.Level logLevel, boolean dismiss404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy, boolean forceDecoding) { this.client = checkNotNull(client, "client"); this.retryer = checkNotNull(retryer, "retryer"); this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors"); + this.responseInterceptors = checkNotNull(responseInterceptors, "responseInterceptors"); this.logger = checkNotNull(logger, "logger"); this.logLevel = checkNotNull(logLevel, "logLevel"); this.dismiss404 = dismiss404; @@ -206,8 +226,8 @@ public MethodHandler create(Target target, Options options, Decoder decoder, ErrorDecoder errorDecoder) { - return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger, - logLevel, md, buildTemplateFromArgs, options, decoder, + return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, + responseInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding); } } From 62d8c0bcfdc8e5c97906b733b97e1a96cde156bb Mon Sep 17 00:00:00 2001 From: feiyanke Date: Mon, 2 May 2022 00:01:59 +0800 Subject: [PATCH 2/6] Add the license header. Add the license header. Co-authored-by: Dewald de Jager --- core/src/main/java/feign/ResponseInterceptor.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java index adb20d698..2c01b205b 100644 --- a/core/src/main/java/feign/ResponseInterceptor.java +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -1,3 +1,16 @@ +/* + * Copyright 2012-2022 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. +*/ package feign; /** From f44697d1d539806530f5b544f87a5493dac30b71 Mon Sep 17 00:00:00 2001 From: feiyanke Date: Mon, 2 May 2022 00:29:53 +0800 Subject: [PATCH 3/6] small fix for license header --- core/src/main/java/feign/ResponseInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java index 2c01b205b..1aa065d38 100644 --- a/core/src/main/java/feign/ResponseInterceptor.java +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -10,7 +10,7 @@ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. -*/ + */ package feign; /** From 1aeb437a07e0f364c31f420ec5222c27b780ef68 Mon Sep 17 00:00:00 2001 From: feiyanke Date: Mon, 2 May 2022 00:36:23 +0800 Subject: [PATCH 4/6] fix format issue --- core/src/main/java/feign/Feign.java | 2 +- .../main/java/feign/ResponseInterceptor.java | 41 +++++++++---------- .../java/feign/SynchronousMethodHandler.java | 12 +++--- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index 2c192606b..371b7fa4c 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -321,7 +321,7 @@ public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, - responseInterceptors,logger, logLevel, dismiss404, closeAfterDecode, + responseInterceptors, logger, logLevel, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java index 1aa065d38..e1057967a 100644 --- a/core/src/main/java/feign/ResponseInterceptor.java +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -14,29 +14,28 @@ package feign; /** - * Zero or more {@code ResponseInterceptor} may be configured for purposes - * such as verify or modify headers of response, verify the business status of decoded object. - * No guarantees are given with regards to the order that interceptors are applied. - * Once interceptors are applied, {@link ResponseInterceptor#beforeDecode(Response)} is called - * before decode method called, {@link ResponseInterceptor#afterDecode(Object)} is called - * after decode method called. + * Zero or more {@code ResponseInterceptor} may be configured for purposes such as verify or modify + * headers of response, verify the business status of decoded object. No guarantees are given with + * regards to the order that interceptors are applied. Once interceptors are applied, + * {@link ResponseInterceptor#beforeDecode(Response)} is called before decode method called, + * {@link ResponseInterceptor#afterDecode(Object)} is called after decode method called. */ public interface ResponseInterceptor { - /** - * Called for response before decode, add data on the supplied {@link Response} or doing - * customized logic - * - * @param response - * @return - */ - void beforeDecode(Response response); + /** + * Called for response before decode, add data on the supplied {@link Response} or doing + * customized logic + * + * @param response + * @return + */ + void beforeDecode(Response response); - /** - * Called for response after decode, add data to decoded object or doing customized logic - * - * @param response - * @return - */ - void afterDecode(Object response); + /** + * Called for response after decode, add data to decoded object or doing customized logic + * + * @param response + * @return + */ + void afterDecode(Object response); } diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index ae89bf276..c82ba000b 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -133,15 +133,15 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); - for (ResponseInterceptor interceptor: responseInterceptors) { - interceptor.beforeDecode(response); + for (ResponseInterceptor interceptor : responseInterceptors) { + interceptor.beforeDecode(response); } if (decoder != null) { - Object object = decoder.decode(response, metadata.returnType()); + Object object = decoder.decode(response, metadata.returnType()); - for (ResponseInterceptor interceptor: responseInterceptors) { - interceptor.afterDecode(object); + for (ResponseInterceptor interceptor : responseInterceptors) { + interceptor.afterDecode(object); } return object; @@ -157,7 +157,7 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa throw new IllegalStateException("Response handling not done"); Object object = resultFuture.join(); - for (ResponseInterceptor interceptor: responseInterceptors) { + for (ResponseInterceptor interceptor : responseInterceptors) { interceptor.afterDecode(object); } return object; From 21d31bec98aad93a9f44a69d2ea42a2ad63fdb2b Mon Sep 17 00:00:00 2001 From: "Fei,Yanke" Date: Thu, 5 May 2022 12:20:16 +0800 Subject: [PATCH 5/6] combine before and after method to one aroundDecode method --- core/src/main/java/feign/Feign.java | 14 +++--- .../main/java/feign/ResponseInterceptor.java | 25 ++++------ .../java/feign/SynchronousMethodHandler.java | 50 +++++++++---------- 3 files changed, 40 insertions(+), 49 deletions(-) diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index 371b7fa4c..f63b796a5 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -98,8 +98,7 @@ public static class Builder { private final List requestInterceptors = new ArrayList(); - private final List responseInterceptors = - new ArrayList(); + private ResponseInterceptor responseInterceptor = null; private Logger.Level logLevel = Logger.Level.NONE; private Contract contract = new Contract.Default(); private Client client = new Client.Default(null, null); @@ -233,7 +232,7 @@ public Builder requestInterceptor(RequestInterceptor requestInterceptor) { * Adds a single response interceptor to the builder. */ public Builder responseInterceptor(ResponseInterceptor responseInterceptor) { - this.responseInterceptors.add(responseInterceptor); + this.responseInterceptor = responseInterceptor; return this; } @@ -307,9 +306,6 @@ public Feign build() { List requestInterceptors = this.requestInterceptors.stream() .map(ri -> Capability.enrich(ri, capabilities)) .collect(Collectors.toList()); - List responseInterceptors = this.responseInterceptors.stream() - .map(ri -> Capability.enrich(ri, capabilities)) - .collect(Collectors.toList()); Logger logger = Capability.enrich(this.logger, capabilities); Contract contract = Capability.enrich(this.contract, capabilities); Options options = Capability.enrich(this.options, capabilities); @@ -318,10 +314,14 @@ public Feign build() { InvocationHandlerFactory invocationHandlerFactory = Capability.enrich(this.invocationHandlerFactory, capabilities); QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities); + ResponseInterceptor responseInterceptor = null; + if (this.responseInterceptor != null) { + responseInterceptor = Capability.enrich(this.responseInterceptor, capabilities); + } SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, - responseInterceptors, logger, logLevel, dismiss404, closeAfterDecode, + responseInterceptor, logger, logLevel, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java index e1057967a..75ce6e0f9 100644 --- a/core/src/main/java/feign/ResponseInterceptor.java +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -13,29 +13,24 @@ */ package feign; +import java.util.function.Function; + /** - * Zero or more {@code ResponseInterceptor} may be configured for purposes such as verify or modify - * headers of response, verify the business status of decoded object. No guarantees are given with - * regards to the order that interceptors are applied. Once interceptors are applied, - * {@link ResponseInterceptor#beforeDecode(Response)} is called before decode method called, - * {@link ResponseInterceptor#afterDecode(Object)} is called after decode method called. + * Zero or One {@code ResponseInterceptor} may be configured for purposes such as verify or modify + * headers of response, verify the business status of decoded object. Once interceptors are applied, + * {@link ResponseInterceptor#aroundDecode(Response, Function)} is called around decode method + * called */ public interface ResponseInterceptor { /** - * Called for response before decode, add data on the supplied {@link Response} or doing - * customized logic + * Called for response around decode, user can use supplied {@link Function} decoder function to + * decode response data. * * @param response + * @param decoder * @return */ - void beforeDecode(Response response); + void aroundDecode(Response response, Function decoder); - /** - * Called for response after decode, add data to decoded object or doing customized logic - * - * @param response - * @return - */ - void afterDecode(Object response); } diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index c82ba000b..713db33b0 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -13,6 +13,9 @@ */ package feign; +import static feign.ExceptionPropagationPolicy.UNWRAP; +import static feign.FeignException.errorExecuting; +import static feign.Util.checkNotNull; import java.io.IOException; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -21,11 +24,9 @@ import java.util.stream.Stream; import feign.InvocationHandlerFactory.MethodHandler; import feign.Request.Options; +import feign.codec.DecodeException; import feign.codec.Decoder; import feign.codec.ErrorDecoder; -import static feign.ExceptionPropagationPolicy.UNWRAP; -import static feign.FeignException.errorExecuting; -import static feign.Util.checkNotNull; final class SynchronousMethodHandler implements MethodHandler { @@ -36,7 +37,7 @@ final class SynchronousMethodHandler implements MethodHandler { private final Client client; private final Retryer retryer; private final List requestInterceptors; - private final List responseInterceptors; + private final ResponseInterceptor responseInterceptor; private final Logger logger; private final Logger.Level logLevel; private final RequestTemplate.Factory buildTemplateFromArgs; @@ -49,7 +50,7 @@ final class SynchronousMethodHandler implements MethodHandler { private SynchronousMethodHandler(Target target, Client client, Retryer retryer, - List requestInterceptors, List responseInterceptors, + List requestInterceptors, ResponseInterceptor responseInterceptor, Logger logger, Logger.Level logLevel, MethodMetadata metadata, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder, boolean dismiss404, @@ -61,14 +62,13 @@ private SynchronousMethodHandler(Target target, Client client, Retryer retrye this.retryer = checkNotNull(retryer, "retryer for %s", target); this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors for %s", target); - this.responseInterceptors = - checkNotNull(responseInterceptors, "responseInterceptors for %s", target); this.logger = checkNotNull(logger, "logger for %s", target); this.logLevel = checkNotNull(logLevel, "logLevel for %s", target); this.metadata = checkNotNull(metadata, "metadata for %s", target); this.buildTemplateFromArgs = checkNotNull(buildTemplateFromArgs, "metadata for %s", target); this.options = checkNotNull(options, "options for %s", target); this.propagationPolicy = propagationPolicy; + this.responseInterceptor = responseInterceptor; if (forceDecoding) { // internal only: usual handling will be short-circuited, and all responses will be passed to @@ -133,18 +133,19 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); - for (ResponseInterceptor interceptor : responseInterceptors) { - interceptor.beforeDecode(response); - } - if (decoder != null) { - Object object = decoder.decode(response, metadata.returnType()); - - for (ResponseInterceptor interceptor : responseInterceptors) { - interceptor.afterDecode(object); + if (responseInterceptor != null) { + responseInterceptor.aroundDecode(response, (res) -> { + try { + return decoder.decode(res, metadata.returnType()); + } catch (IOException e) { + throw new DecodeException(res.status(), "decode error cause of io exception", + res.request(), e); + } + }); + } else { + return decoder.decode(response, metadata.returnType()); } - - return object; } CompletableFuture resultFuture = new CompletableFuture<>(); @@ -155,12 +156,7 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa try { if (!resultFuture.isDone()) throw new IllegalStateException("Response handling not done"); - - Object object = resultFuture.join(); - for (ResponseInterceptor interceptor : responseInterceptors) { - interceptor.afterDecode(object); - } - return object; + return resultFuture.join(); } catch (CompletionException e) { Throwable cause = e.getCause(); if (cause != null) @@ -196,7 +192,7 @@ static class Factory { private final Client client; private final Retryer retryer; private final List requestInterceptors; - private final List responseInterceptors; + private final ResponseInterceptor responseInterceptor; private final Logger logger; private final Logger.Level logLevel; private final boolean dismiss404; @@ -205,13 +201,13 @@ static class Factory { private final boolean forceDecoding; Factory(Client client, Retryer retryer, List requestInterceptors, - List responseInterceptors, + ResponseInterceptor responseInterceptor, Logger logger, Logger.Level logLevel, boolean dismiss404, boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy, boolean forceDecoding) { this.client = checkNotNull(client, "client"); this.retryer = checkNotNull(retryer, "retryer"); this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors"); - this.responseInterceptors = checkNotNull(responseInterceptors, "responseInterceptors"); + this.responseInterceptor = responseInterceptor; this.logger = checkNotNull(logger, "logger"); this.logLevel = checkNotNull(logLevel, "logLevel"); this.dismiss404 = dismiss404; @@ -227,7 +223,7 @@ public MethodHandler create(Target target, Decoder decoder, ErrorDecoder errorDecoder) { return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, - responseInterceptors, logger, logLevel, md, buildTemplateFromArgs, options, decoder, + responseInterceptor, logger, logLevel, md, buildTemplateFromArgs, options, decoder, errorDecoder, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding); } } From 072711b7c0d169cb49adfadeb20a85d5b4261be7 Mon Sep 17 00:00:00 2001 From: Marvin Froeder Date: Wed, 22 Jun 2022 09:25:23 +1200 Subject: [PATCH 6/6] Change ResponseInterceptor to use InvocationContext --- core/src/main/java/feign/AsyncFeign.java | 20 +++++-- core/src/main/java/feign/Feign.java | 15 ++++-- .../main/java/feign/InvocationContext.java | 53 +++++++++++++++++++ .../main/java/feign/ResponseInterceptor.java | 13 ++--- .../java/feign/SynchronousMethodHandler.java | 22 ++------ 5 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 core/src/main/java/feign/InvocationContext.java diff --git a/core/src/main/java/feign/AsyncFeign.java b/core/src/main/java/feign/AsyncFeign.java index 5235f5884..117c065d4 100644 --- a/core/src/main/java/feign/AsyncFeign.java +++ b/core/src/main/java/feign/AsyncFeign.java @@ -13,17 +13,21 @@ */ package feign; -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.Optional; -import java.util.concurrent.*; -import java.util.function.Supplier; import feign.Logger.NoOpLogger; import feign.Request.Options; import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; /** * Enhances {@link Feign} to provide support for asynchronous clients. Context (for example for @@ -106,6 +110,7 @@ public AsyncBuilder decoder(Decoder decoder) { * @see Builder#decode404() * @deprecated */ + @Deprecated public AsyncBuilder decode404() { this.dismiss404 = true; return this; @@ -222,6 +227,11 @@ public AsyncBuilder requestInterceptor(RequestInterceptor requestInterceptor) return this; } + public AsyncBuilder responseInterceptor(ResponseInterceptor responseInterceptor) { + builder.responseInterceptor(responseInterceptor); + return this; + } + /** * @see Builder#requestInterceptors(Iterable) */ diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index f63b796a5..2b8386fa1 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -13,6 +13,7 @@ */ package feign; +import static feign.ExceptionPropagationPolicy.NONE; import feign.Logger.NoOpLogger; import feign.ReflectiveFeign.ParseHandlersByName; import feign.Request.Options; @@ -27,7 +28,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import static feign.ExceptionPropagationPolicy.NONE; /** * Feign's purpose is to ease development against http apis that feign restfulness.
@@ -51,7 +51,11 @@ public static Builder builder() { * *
    * 
    - *
  • {@code Route53}: would match a class {@code route53.Route53}
  • + *
  • {@code + * Route53 + * }: would match a class {@code + * route53.Route53 + * }
  • *
  • {@code Route53#list()}: would match a method {@code route53.Route53#list()}
  • *
  • {@code Route53#listAt(Marker)}: would match a method {@code * route53.Route53#listAt(Marker)}
  • @@ -97,8 +101,8 @@ public static String configKey(Method method) { public static class Builder { private final List requestInterceptors = - new ArrayList(); - private ResponseInterceptor responseInterceptor = null; + new ArrayList<>(); + private ResponseInterceptor responseInterceptor = ResponseInterceptor.DEFAULT; private Logger.Level logLevel = Logger.Level.NONE; private Contract contract = new Contract.Default(); private Client client = new Client.Default(null, null); @@ -183,6 +187,7 @@ public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) { * @since 8.12 * @deprecated */ + @Deprecated public Builder decode404() { this.dismiss404 = true; return this; @@ -293,7 +298,7 @@ Builder forceDecoding() { } public T target(Class apiType, String url) { - return target(new HardCodedTarget(apiType, url)); + return target(new HardCodedTarget<>(apiType, url)); } public T target(Target target) { diff --git a/core/src/main/java/feign/InvocationContext.java b/core/src/main/java/feign/InvocationContext.java new file mode 100644 index 000000000..138b8c431 --- /dev/null +++ b/core/src/main/java/feign/InvocationContext.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2022 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign; + +import feign.codec.DecodeException; +import feign.codec.Decoder; +import java.io.IOException; + +public class InvocationContext { + + private final Decoder decoder; + private final MethodMetadata metadata; + private final Response response; + + public InvocationContext(Decoder decoder, MethodMetadata metadata, Response response) { + this.decoder = decoder; + this.metadata = metadata; + this.response = response; + } + + public Object proceed() { + try { + return decoder.decode(response, metadata.returnType()); + } catch (IOException e) { + throw new DecodeException(response.status(), "decode error cause of io exception", + response.request(), e); + } + } + + public Decoder getDecoder() { + return decoder; + } + + public MethodMetadata getMetadata() { + return metadata; + } + + public Response getResponse() { + return response; + } + +} diff --git a/core/src/main/java/feign/ResponseInterceptor.java b/core/src/main/java/feign/ResponseInterceptor.java index 75ce6e0f9..9f238128a 100644 --- a/core/src/main/java/feign/ResponseInterceptor.java +++ b/core/src/main/java/feign/ResponseInterceptor.java @@ -23,14 +23,15 @@ */ public interface ResponseInterceptor { + ResponseInterceptor DEFAULT = InvocationContext::proceed; + /** - * Called for response around decode, user can use supplied {@link Function} decoder function to - * decode response data. + * Called for response around decode, must either manually invoke + * {@link InvocationContext#proceed} or manually create a new response object * - * @param response - * @param decoder - * @return + * @param invocationContext information surrounding the response been decoded + * @return decoded response */ - void aroundDecode(Response response, Function decoder); + Object aroundDecode(InvocationContext invocationContext); } diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 713db33b0..9951d1277 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -16,17 +16,16 @@ import static feign.ExceptionPropagationPolicy.UNWRAP; import static feign.FeignException.errorExecuting; import static feign.Util.checkNotNull; +import feign.InvocationHandlerFactory.MethodHandler; +import feign.Request.Options; +import feign.codec.Decoder; +import feign.codec.ErrorDecoder; import java.io.IOException; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import feign.InvocationHandlerFactory.MethodHandler; -import feign.Request.Options; -import feign.codec.DecodeException; -import feign.codec.Decoder; -import feign.codec.ErrorDecoder; final class SynchronousMethodHandler implements MethodHandler { @@ -134,18 +133,7 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); if (decoder != null) { - if (responseInterceptor != null) { - responseInterceptor.aroundDecode(response, (res) -> { - try { - return decoder.decode(res, metadata.returnType()); - } catch (IOException e) { - throw new DecodeException(res.status(), "decode error cause of io exception", - res.request(), e); - } - }); - } else { - return decoder.decode(response, metadata.returnType()); - } + return responseInterceptor.aroundDecode(new InvocationContext(decoder, metadata, response)); } CompletableFuture resultFuture = new CompletableFuture<>();