feign.AsyncFeign Maven / Gradle / Ivy
/**
* Copyright 2012-2020 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.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;
/**
* Enhances {@link Feign} to provide support for asynchronous clients. Context (for example for
* session cookies or tokens) is explicit, as calls for the same session may be done across several
* threads.
*
* {@link Retryer} is not supported in this model, as that is a blocking API.
* {@link ExceptionPropagationPolicy} is made redundant as {@link RetryableException} is never
* thrown.
* Alternative approaches to retrying can be handled through {@link AsyncClient clients}.
*
* Target interface methods must return {@link CompletableFuture} with a non-wildcard type. As the
* completion is done by the {@link AsyncClient}, it is important that any subsequent processing on
* the thread be short - generally, this should involve notifying some other thread of the work to
* be done (for example, creating and submitting a task to an {@link ExecutorService}).
*
*/
@Experimental
public abstract class AsyncFeign extends Feign {
public static AsyncBuilder asyncBuilder() {
return new AsyncBuilder<>();
}
private static class LazyInitializedExecutorService {
private static final ExecutorService instance = Executors.newCachedThreadPool(r -> {
final Thread result = new Thread(r);
result.setDaemon(true);
return result;
});
}
public static class AsyncBuilder {
private final Builder builder;
private Supplier defaultContextSupplier = () -> null;
private AsyncClient client;
private final Logger.Level logLevel = Logger.Level.NONE;
private final Logger logger = new NoOpLogger();
private Decoder decoder = new Decoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
public AsyncBuilder() {
super();
this.builder = Feign.builder();
}
public AsyncBuilder defaultContextSupplier(Supplier supplier) {
this.defaultContextSupplier = supplier;
return this;
}
public AsyncBuilder client(AsyncClient client) {
this.client = client;
return this;
}
/**
* @see Builder#mapAndDecode(ResponseMapper, Decoder)
*/
public AsyncBuilder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
this.decoder = (response, type) -> decoder.decode(mapper.map(response, type), type);
return this;
}
/**
* @see Builder#decoder(Decoder)
*/
public AsyncBuilder decoder(Decoder decoder) {
this.decoder = decoder;
return this;
}
/**
* @see Builder#decode404()
*/
public AsyncBuilder decode404() {
this.decode404 = true;
return this;
}
/**
* @see Builder#errorDecoder(ErrorDecoder)
*/
public AsyncBuilder errorDecoder(ErrorDecoder errorDecoder) {
this.errorDecoder = errorDecoder;
return this;
}
public AsyncBuilder doNotCloseAfterDecode() {
this.closeAfterDecode = false;
return this;
}
public T target(Class apiType, String url) {
return target(new HardCodedTarget<>(apiType, url));
}
public T target(Class apiType, String url, C context) {
return target(new HardCodedTarget<>(apiType, url), context);
}
public T target(Target target) {
return build().newInstance(target);
}
public T target(Target target, C context) {
return build().newInstance(target, context);
}
private AsyncBuilder lazyInits() {
if (client == null) {
client = new AsyncClient.Default<>(new Client.Default(null, null),
LazyInitializedExecutorService.instance);
}
return this;
}
public AsyncFeign build() {
return new ReflectiveAsyncFeign<>(lazyInits());
}
// start of builder delgates
/**
* @see Builder#logLevel(Logger.Level)
*/
public AsyncBuilder logLevel(Logger.Level logLevel) {
builder.logLevel(logLevel);
return this;
}
/**
* @see Builder#contract(Contract)
*/
public AsyncBuilder contract(Contract contract) {
builder.contract(contract);
return this;
}
/**
* @see Builder#logLevel(Logger.Level)
*/
public AsyncBuilder logger(Logger logger) {
builder.logger(logger);
return this;
}
/**
* @see Builder#encoder(Encoder)
*/
public AsyncBuilder encoder(Encoder encoder) {
builder.encoder(encoder);
return this;
}
/**
* @see Builder#queryMapEncoder(QueryMapEncoder)
*/
public AsyncBuilder queryMapEncoder(QueryMapEncoder queryMapEncoder) {
builder.queryMapEncoder(queryMapEncoder);
return this;
}
/**
* @see Builder#options(Options)
*/
public AsyncBuilder options(Options options) {
builder.options(options);
return this;
}
/**
* @see Builder#requestInterceptor(RequestInterceptor)
*/
public AsyncBuilder requestInterceptor(RequestInterceptor requestInterceptor) {
builder.requestInterceptor(requestInterceptor);
return this;
}
/**
* @see Builder#requestInterceptors(Iterable)
*/
public AsyncBuilder requestInterceptors(Iterable requestInterceptors) {
builder.requestInterceptors(requestInterceptors);
return this;
}
/**
* @see Builder#invocationHandlerFactory(InvocationHandlerFactory)
*/
public AsyncBuilder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
builder.invocationHandlerFactory(invocationHandlerFactory);
return this;
}
}
private final ThreadLocal> activeContext;
private final Feign feign;
private final Supplier defaultContextSupplier;
private final AsyncClient client;
private final Logger.Level logLevel;
private final Logger logger;
private final AsyncResponseHandler responseHandler;
protected AsyncFeign(AsyncBuilder asyncBuilder) {
this.activeContext = new ThreadLocal<>();
this.defaultContextSupplier = asyncBuilder.defaultContextSupplier;
this.client = asyncBuilder.client;
this.logLevel = asyncBuilder.logLevel;
this.logger = asyncBuilder.logger;
this.responseHandler = new AsyncResponseHandler(
asyncBuilder.logLevel,
asyncBuilder.logger,
asyncBuilder.decoder,
asyncBuilder.errorDecoder,
asyncBuilder.decode404,
asyncBuilder.closeAfterDecode);
asyncBuilder.builder.client(this::stageExecution);
asyncBuilder.builder.decoder(this::stageDecode);
asyncBuilder.builder.forceDecoding(); // force all handling through stageDecode
this.feign = asyncBuilder.builder.build();
}
private Response stageExecution(Request request, Options options) {
final Response result = Response.builder()
.status(200)
.request(request)
.build();
final AsyncInvocation invocationContext = activeContext.get();
invocationContext.setResponseFuture(
client.execute(request, options, Optional.ofNullable(invocationContext.context())));
return result;
}
// from SynchronousMethodHandler
long elapsedTime(long start) {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
}
private Object stageDecode(Response response, Type type) {
final AsyncInvocation invocationContext = activeContext.get();
final CompletableFuture
© 2015 - 2025 Weber Informatics LLC | Privacy Policy