Skip to content

Commit

Permalink
Converted to using around
Browse files Browse the repository at this point in the history
  • Loading branch information
marcingrzejszczak committed Sep 20, 2022
1 parent 9372799 commit 41e6a5b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 97 deletions.
37 changes: 11 additions & 26 deletions core/src/main/java/feign/ClientInterceptor.java
Expand Up @@ -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<ClientInterceptor> iterator)
throws FeignException;

}
9 changes: 0 additions & 9 deletions core/src/main/java/feign/ClientInvocationContext.java
Expand Up @@ -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<Object, Object> holder = new HashMap<>();

public ClientInvocationContext(RequestTemplate requestTemplate, Request.Options options) {
this.requestTemplate = requestTemplate;
this.options = options;
Expand All @@ -36,8 +31,4 @@ public RequestTemplate getRequestTemplate() {
public Request.Options getOptions() {
return options;
}

public Map<Object, Object> getHolder() {
return holder;
}
}
133 changes: 92 additions & 41 deletions core/src/main/java/feign/SynchronousMethodHandler.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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<RequestInterceptor> requestInterceptors;
private final Logger logger;
private final Logger.Level logLevel;
private final Client client;

private final long start;

HttpCall(MethodMetadata metadata, Target<?> target,
List<RequestInterceptor> 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<ClientInterceptor> iterator) {
return httpCall.call(context.getRequestTemplate(), context.getOptions());
}
}

private static class InterceptorChain {
private final List<ClientInterceptor> interceptors;

InterceptorChain(List<ClientInterceptor> interceptors, HttpCall httpCall) {
this.interceptors = new ArrayList<>(interceptors);
this.interceptors.add(new HttpCallingInterceptor(httpCall));
}

Response call(ClientInvocationContext context) {
Iterator<ClientInterceptor> iterator = this.interceptors.iterator();
ClientInterceptor next = iterator.next();
return next.around(context, iterator);
}
}

static class Factory {

private final Client client;
Expand Down
Expand Up @@ -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;
Expand All @@ -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<ClientInterceptor> 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();
}
}

0 comments on commit 41e6a5b

Please sign in to comment.