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.
Hi, I solved it with changing the strategy of isolation of the hystrix to SEMAPHORE in application.yml
Hey, that will definitely solve the issue but bring in others. Semaphore isolation means that the same thread will be reused and Hystrix will use a semaphore to separate contexts which implies that in a system which needs high throughput, most probably it will not be as effective as it should be.
Thank You very much 🙂
Tanks
Hi Arnold, I read your another post that you explain how to propagate the request but in that post you not tell me the trick in this current post and I lost a lot of time to understand why my request was returning null. I change to Semaphore and I didn’t like that approach. Thanks I gonna use this approach now and do my tests. I need propagate my language because of another services needs return correct message by language code.
Hi Rogerio. Awesome. That’s a quite frequent use-case. Let me know how the implementation went.
Hi Arnold, Thanks for this article. I was wondering whether Hystrix can create problem if I am injecting the original HttpServletRequest into my Interceptor component using spring boot’s Autowiring like:
“`
@Autowired
private HttpServletRequest currentRequest;
“`
and then using `currentRequest.getHeader(“Accept-Language”);` to get the header value of original request?
Sorry, it seems this comment section doesn’t really recognize the code block markdown.