Skip to content

Commit

Permalink
Alternative ResponseInterceptor attempt (#1634)
Browse files Browse the repository at this point in the history
* add ResponseInterceptor support #1126

* Add the license header.

Add the license header.

Co-authored-by: Dewald de Jager <DewaldDeJager@users.noreply.github.com>

* small fix for license header

* fix format issue

* combine before and after method to one aroundDecode method

* Change ResponseInterceptor to use InvocationContext

Co-authored-by: Fei,Yanke <yanke.fei@mosi-tech.com>
Co-authored-by: feiyanke <feiyanke@126.com>
Co-authored-by: Dewald de Jager <DewaldDeJager@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 23, 2022
1 parent 75a3c1c commit a259a72
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 100 deletions.
3 changes: 2 additions & 1 deletion core/src/main/java/feign/AsyncFeign.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public AsyncFeign<C> build() {
decoder,
errorDecoder,
dismiss404,
closeAfterDecode),
closeAfterDecode, responseInterceptor),
AsyncResponseHandler.class,
capabilities);

Expand All @@ -126,6 +126,7 @@ public AsyncFeign<C> build() {
.queryMapEncoder(queryMapEncoder)
.options(options)
.requestInterceptors(requestInterceptors)
.responseInterceptor(responseInterceptor)
.invocationHandlerFactory(invocationHandlerFactory)
.build(), defaultContextSupplier, activeContextHolder);
}
Expand Down
20 changes: 8 additions & 12 deletions core/src/main/java/feign/AsyncResponseHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@

import static feign.FeignException.errorReading;
import static feign.Util.ensureClosed;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.concurrent.CompletableFuture;
import feign.Logger.Level;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.concurrent.CompletableFuture;

/**
* The response handler that is used to provide asynchronous support on top of standard response
Expand All @@ -40,15 +39,18 @@ class AsyncResponseHandler {
private final boolean dismiss404;
private final boolean closeAfterDecode;

private final ResponseInterceptor responseInterceptor;

AsyncResponseHandler(Level logLevel, Logger logger, Decoder decoder, ErrorDecoder errorDecoder,
boolean dismiss404, boolean closeAfterDecode) {
boolean dismiss404, boolean closeAfterDecode, ResponseInterceptor responseInterceptor) {
super();
this.logLevel = logLevel;
this.logger = logger;
this.decoder = decoder;
this.errorDecoder = errorDecoder;
this.dismiss404 = dismiss404;
this.closeAfterDecode = closeAfterDecode;
this.responseInterceptor = responseInterceptor;
}

boolean isVoidType(Type returnType) {
Expand Down Expand Up @@ -111,12 +113,6 @@ void handleResponse(CompletableFuture<Object> resultFuture,
}

Object decode(Response response, Type type) throws IOException {
try {
return decoder.decode(response, type);
} catch (final FeignException e) {
throw e;
} catch (final RuntimeException e) {
throw new DecodeException(response.status(), e.getMessage(), response.request(), e);
}
return responseInterceptor.aroundDecode(new InvocationContext(decoder, type, response));
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/feign/BaseBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public abstract class BaseBuilder<B extends BaseBuilder<B>> {

protected final List<RequestInterceptor> requestInterceptors =
new ArrayList<>();
protected ResponseInterceptor responseInterceptor = ResponseInterceptor.DEFAULT;
protected Logger.Level logLevel = Logger.Level.NONE;
protected Contract contract = new Contract.Default();
protected Retryer retryer = new Retryer.Default();
Expand Down Expand Up @@ -196,6 +197,15 @@ public B requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
return thisB;
}

/**
* Adds a single response interceptor to the builder.
*/
public B responseInterceptor(ResponseInterceptor responseInterceptor) {
this.responseInterceptor = responseInterceptor;
return thisB;
}


/**
* Allows you to override how reflective dispatch works inside of Feign.
*/
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/feign/Capability.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ default RequestInterceptor enrich(RequestInterceptor requestInterceptor) {
return requestInterceptor;
}

default ResponseInterceptor enrich(ResponseInterceptor responseInterceptor) {
return responseInterceptor;
}

default Logger enrich(Logger logger) {
return logger;
}
Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/feign/Feign.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public static class Builder extends BaseBuilder<Builder> {

public Builder client(Client client) {
this.client = client;

return this;
}

Expand All @@ -119,8 +120,9 @@ public Feign build() {
super.enrich();

SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding);
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
responseInterceptor, logger, logLevel, dismiss404, closeAfterDecode,
propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
Expand Down
58 changes: 58 additions & 0 deletions core/src/main/java/feign/InvocationContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 static feign.FeignException.errorReading;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import java.io.IOException;
import java.lang.reflect.Type;

public class InvocationContext {

private final Decoder decoder;
private final Type returnType;
private final Response response;

InvocationContext(Decoder decoder, Type returnType, Response response) {
this.decoder = decoder;
this.returnType = returnType;
this.response = response;
}

public Object proceed() {
try {
return decoder.decode(response, returnType);
} catch (final FeignException e) {
throw e;
} catch (final RuntimeException e) {
throw new DecodeException(response.status(), e.getMessage(), response.request(), e);
} catch (IOException e) {
throw errorReading(response.request(), response, e);
}
}

public Decoder decoder() {
return decoder;
}

public Type returnType() {
return returnType;
}

public Response response() {
return response;
}

}
38 changes: 38 additions & 0 deletions core/src/main/java/feign/ResponseInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 java.io.IOException;
import java.util.function.Function;

/**
* 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 {

ResponseInterceptor DEFAULT = InvocationContext::proceed;

/**
* Called for response around decode, must either manually invoke
* {@link InvocationContext#proceed} or manually create a new response object
*
* @param invocationContext information surrounding the response been decoded
* @return decoded response
*/
Object aroundDecode(InvocationContext invocationContext) throws IOException;

}
40 changes: 22 additions & 18 deletions core/src/main/java/feign/SynchronousMethodHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
*/
package feign;

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.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 {

Expand All @@ -36,6 +36,7 @@ final class SynchronousMethodHandler implements MethodHandler {
private final Client client;
private final Retryer retryer;
private final List<RequestInterceptor> requestInterceptors;
private final ResponseInterceptor responseInterceptor;
private final Logger logger;
private final Logger.Level logLevel;
private final RequestTemplate.Factory buildTemplateFromArgs;
Expand All @@ -48,8 +49,8 @@ final class SynchronousMethodHandler implements MethodHandler {


private SynchronousMethodHandler(Target<?> target, Client client, Retryer retryer,
List<RequestInterceptor> requestInterceptors, Logger logger,
Logger.Level logLevel, MethodMetadata metadata,
List<RequestInterceptor> requestInterceptors, ResponseInterceptor responseInterceptor,
Logger logger, Logger.Level logLevel, MethodMetadata metadata,
RequestTemplate.Factory buildTemplateFromArgs, Options options,
Decoder decoder, ErrorDecoder errorDecoder, boolean dismiss404,
boolean closeAfterDecode, ExceptionPropagationPolicy propagationPolicy,
Expand All @@ -66,6 +67,7 @@ private SynchronousMethodHandler(Target<?> target, Client client, Retryer retrye
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
Expand All @@ -75,7 +77,7 @@ private SynchronousMethodHandler(Target<?> target, Client client, Retryer retrye
} else {
this.decoder = null;
this.asyncResponseHandler = new AsyncResponseHandler(logLevel, logger, decoder, errorDecoder,
dismiss404, closeAfterDecode);
dismiss404, closeAfterDecode, responseInterceptor);
}
}

Expand Down Expand Up @@ -130,19 +132,18 @@ Object executeAndDecode(RequestTemplate template, Options options) throws Throwa
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);


if (decoder != null)
return decoder.decode(response, metadata.returnType());
if (decoder != null) {
return responseInterceptor
.aroundDecode(new InvocationContext(decoder, metadata.returnType(), response));
}

CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
metadata.returnType(), elapsedTime);

try {
if (!resultFuture.isDone())
throw new IllegalStateException("Response handling not done");

return resultFuture.join();
} catch (CompletionException e) {
Throwable cause = e.getCause();
Expand Down Expand Up @@ -179,6 +180,7 @@ static class Factory {
private final Client client;
private final Retryer retryer;
private final List<RequestInterceptor> requestInterceptors;
private final ResponseInterceptor responseInterceptor;
private final Logger logger;
private final Logger.Level logLevel;
private final boolean dismiss404;
Expand All @@ -187,11 +189,13 @@ static class Factory {
private final boolean forceDecoding;

Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors,
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.responseInterceptor = responseInterceptor;
this.logger = checkNotNull(logger, "logger");
this.logLevel = checkNotNull(logLevel, "logLevel");
this.dismiss404 = dismiss404;
Expand All @@ -206,8 +210,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,
responseInterceptor, logger, logLevel, md, buildTemplateFromArgs, options, decoder,
errorDecoder, dismiss404, closeAfterDecode, propagationPolicy, forceDecoding);
}
}
Expand Down

0 comments on commit a259a72

Please sign in to comment.