Copying current request information into Feign interceptor with Hystrix enabled

Not so long ago I came across a problem which was related to localization in a microservices architecture which was using Feign for internal service communication.

The idea was that if an incoming request has an Accept-Language  header, I should pass it over with every Feign request in order to have the locale information in every single service throughout the system. For this, I wanted to use RequestInterceptor , but luckily I was also using Hystrix enabled in the services.

The code I had in my interceptor is similar to the following:

public class LanguageRequestInterceptor implements RequestInterceptor {
    private static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        if (request == null) {
            return;
        }
        String language = request.getHeader(ACCEPT_LANGUAGE_HEADER);
        if (language == null) {
            return;
        }
        requestTemplate.header(ACCEPT_LANGUAGE_HEADER, language);
    }
}

Now what I experienced is that localization was simply not working. When I debugged this piece of code, it turned out that the I cannot access the current request bound to the thread. After a little bit of investigation I realized that Hystrix was playing me. By default, Hystrix is executing the Feign requests in a separate thread, meaning that the original request information will not be available as it’s stored in a ThreadLocal  variable. Unless you have some custom code to copy it.

So this is what I did:

@Component
public class RequestContextHolderHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    private HystrixConcurrencyStrategy delegate;

    public RequestContextHolderHystrixConcurrencyStrategy() {
        this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
        HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
        HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
        HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
        HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
        HystrixPlugins.reset();
        HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
        HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
        HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
        HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
        HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return delegate != null ? delegate.getBlockingQueue(maxQueueSize) : super.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return delegate != null ? delegate.getRequestVariable(rv) : super.getRequestVariable(rv);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue) {
        return delegate != null ? delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)
                : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
        return delegate != null ? delegate.getThreadPool(threadPoolKey, threadPoolProperties) : super.getThreadPool(threadPoolKey, threadPoolProperties);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        RequestContextHolderCallable<T> delegatingCallable = new RequestContextHolderCallable<>(callable, requestAttributes);
        return delegate != null ? delegate.wrapCallable(delegatingCallable) : super.wrapCallable(delegatingCallable);
    }
}

The important method is the wrapCallable which is basically overridden to propagate the original request attributes to the new thread. This is how the RequestContextHolderCallable  looks like:

public class RequestContextHolderCallable<V> implements Callable<V> {
    private final Callable<V> delegate;
    private final RequestAttributes originalRequestAttributes;

    public RequestContextHolderCallable(Callable<V> delegate, RequestAttributes originalRequestAttributes) {
        this.delegate = delegate;
        this.originalRequestAttributes = originalRequestAttributes;
    }

    @Override
    public V call() throws Exception {
        RequestAttributes currentRequestAttributes = RequestContextHolder.getRequestAttributes();
        try {
            RequestContextHolder.setRequestAttributes(originalRequestAttributes);
            return delegate.call();
        } finally {
            RequestContextHolder.setRequestAttributes(currentRequestAttributes);
        }
    }
}

After starting the application, the locale will be propagated correctly.

The example project can be found on my GitHub page.

6 Replies to “Copying current request information into Feign interceptor with Hystrix enabled”

    1. Arnold Galovics says:
  1. Rogerio Esteves says:

Leave a Reply

Your email address will not be published. Required fields are marked *