All Downloads are FREE. Search and download functionalities are using the official Maven repository.

feign.AsyncFeign Maven / Gradle / Ivy

There is a newer version: 13.5
Show newest version
/**
 * Copyright 2012-2021 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 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); this.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 result = new CompletableFuture<>(); invocationContext.responseFuture().whenComplete((r, t) -> { final long elapsedTime = elapsedTime(invocationContext.startNanos()); if (t != null) { if (logLevel != Logger.Level.NONE && t instanceof IOException) { final IOException e = (IOException) t; logger.logIOException(invocationContext.configKey(), logLevel, e, elapsedTime); } result.completeExceptionally(t); } else { responseHandler.handleResponse(result, invocationContext.configKey(), r, invocationContext.underlyingType(), elapsedTime); } }); result.whenComplete((r, t) -> { if (result.isCancelled()) { invocationContext.responseFuture().cancel(true); } }); if (invocationContext.isAsyncReturnType()) { return result; } try { return result.join(); } catch (final CompletionException e) { final Response r = invocationContext.responseFuture().join(); Throwable cause = e.getCause(); if (cause == null) { cause = e; } throw new AsyncJoinException(r.status(), cause.getMessage(), r.request(), cause); } } protected void setInvocationContext(AsyncInvocation invocationContext) { activeContext.set(invocationContext); } protected void clearInvocationContext() { activeContext.remove(); } @Override public T newInstance(Target target) { return newInstance(target, defaultContextSupplier.get()); } public T newInstance(Target target, C context) { return wrap(target.type(), feign.newInstance(target), context); } protected abstract T wrap(Class type, T instance, C context); }