Feign調(diào)用服務時丟失Cookie和Header信息的解決方案
Feign調(diào)用服務丟失Cookie和Header信息
今天在使用Feign調(diào)用其他微服務的接口時,發(fā)現(xiàn)了一個問題:因為我的項目采用了無狀態(tài)登錄,token信息是存放在cookie中的,所以調(diào)用接口時,因為cookie中沒有token信息,我的請求被攔截器攔截了。
參考幾篇文章,靠譜的解決方法是:將cookie信息放到請求頭中,再進行調(diào)用接口時,攔截器中可以對請求頭進行解析,獲取cookie信息
服務調(diào)用方
package top.codekiller.manager.upload.config; import feign.RequestInterceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; /** * @author codekiller * @date 2020/5/26 14:22 * * 自定義的請求頭處理類,處理服務發(fā)送時的請求頭; * 將服務接收到的請求頭中的uniqueId和token字段取出來,并設置到新的請求頭里面去轉發(fā)給下游服務 * 比如A服務收到一個請求,請求頭里面包含uniqueId和token字段,A處理時會使用Feign客戶端調(diào)用B服務 * 那么uniqueId和token這兩個字段就會添加到請求頭中一并發(fā)給B服務; */ @Configuration @Slf4j public class FeignHeaderConfiguration { @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs != null) { HttpServletRequest request = attrs.getRequest(); // 如果在Cookie內(nèi)通過如下方式取 Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { requestTemplate.header(cookie.getName(), cookie.getValue()); System.out.println("信息"+cookie.getName()+cookie.getValue()); } } else { log.warn("FeignHeadConfiguration", "獲取Cookie失??!"); } // 如果放在header內(nèi)通過如下方式取 Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); /** * 遍歷請求頭里面的屬性字段,將jsessionid添加到新的請求頭中轉發(fā)到下游服務 * */ if ("jsessionid".equalsIgnoreCase(name)) { log.debug("添加自定義請求頭key:" + name + ",value:" + value); requestTemplate.header(name, value); } else { log.debug("FeignHeadConfiguration", "非自定義請求頭key:" + name + ",value:" + value + "不需要添加!"); } } } else { log.warn("FeignHeadConfiguration", "獲取請求頭失??!"); } } }; } }
服務接受方
//有些請求時從通過feign進行請求的,這一部分請求時不包含cookie信息的,因此我們要從請求頭中獲取 Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); System.out.println("header的信息"+name+"::::"+value); if(name.equalsIgnoreCase("MC_TOKEN")){ //注意這里變成了小寫 token=value; } } }
運行的時候,我發(fā)現(xiàn)請求還是被攔截了,看了下打印信息,發(fā)現(xiàn)我的MC_TOKEN變成了小寫,所以在字符串進行比較的時候要忽略大小寫。
以下為擴展,僅僅記錄一下
這樣仍然有個問題:
在開啟熔斷器之后,方法里的attrs是null,因為熔斷器默認的隔離策略是thread,也就是線程隔離,實際上接收到的對象和這個在發(fā)送給B不是一個線程,怎么辦?
有一個辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為信號量的隔離模式,但是不推薦,因為thread是默認的,而且要命的是信號量模式,熔斷器不生效,比如設置了熔斷時間。
另一個辦法:重寫Feign的隔離策略
import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 自定義Feign的隔離策略; * 在轉發(fā)Feign的請求頭的時候,如果開啟了Hystrix,Hystrix的默認隔離策略是Thread(線程隔離策略),因此轉發(fā)攔截器內(nèi)是無法獲取到請求的請求頭信息的,可以修改默認隔離策略為信號量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,這樣的話轉發(fā)線程和請求線程實際上是一個線程,這并不是最好的解決方法,信號量模式也不是官方最為推薦的隔離策略;另一個解決方法就是自定義Hystrix的隔離策略,思路是將現(xiàn)有的并發(fā)策略作為新并發(fā)策略的成員變量,在新并發(fā)策略中,返回現(xiàn)有并發(fā)策略的線程池、Queue;將策略加到Spring容器即可; * */ @Component public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy { private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class); private HystrixConcurrencyStrategy delegate; public FeignHystrixConcurrencyStrategyIntellif() { try { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) { // Welcome to singleton hell... return; } HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); } catch (Exception e) { log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e); } } private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier, HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { if (log.isDebugEnabled()) { log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]"); log.debug("Registering Sleuth Hystrix Concurrency Strategy."); } } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return new WrappedCallable<>(callable, requestAttributes); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties); } @Override public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { return this.delegate.getBlockingQueue(maxQueueSize); } @Override public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) { return this.delegate.getRequestVariable(rv); } static class WrappedCallable<T> implements Callable<T> { private final Callable<T> target; private final RequestAttributes requestAttributes; public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) { this.target = target; this.requestAttributes = requestAttributes; } @Override public T call() throws Exception { try { RequestContextHolder.setRequestAttributes(requestAttributes); return target.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } } }
然后使用默認的熔斷器隔離策略,也可以在攔截器內(nèi)獲取到上游服務的請求頭信息了;
Feign調(diào)用存在的問題
① feign遠程調(diào)用丟失請求頭
問題描述:
當遠程調(diào)用其他服務時,設置了攔截器判斷用戶是否登錄,但是結果是即使用戶登錄了,也會顯示用戶沒登錄,原因在于遠程調(diào)用時,發(fā)送的請求是一個新的情求,請求中并不存在cookie,而原始請求中是攜帶cookie的。
解決方案如下:
@Configuration public class MallFeignConfig { @Bean("requestInterceptor") public RequestInterceptor requestInterceptor() { RequestInterceptor requestInterceptor = template -> { //1、使用RequestContextHolder拿到剛進來的請求數(shù)據(jù) ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) { //老請求 HttpServletRequest request = requestAttributes.getRequest(); if (request != null) { //2、同步請求頭的數(shù)據(jù)(主要是cookie) //把老請求的cookie值放到新請求上來,進行一個同步 String cookie = request.getHeader("Cookie"); template.header("Cookie", cookie); } } }; return requestInterceptor; } }
② 異步調(diào)用Feign丟失上下文問題
問題描述:
由于feign請求攔截器為新的request設置請求頭底層是使用ThreadLocal保存剛進來的請求,所以在異步情況下,其他線程并不能獲取到主線程的ThreadLocal,所以也拿不到請求。
解決:
先獲取主線程的requestAttributes,再分別向其他線程中設置
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuture.runAsync(() ->{ RequestContextHolder.setRequestAttributes(requestAttributes); });
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Spring Boot2配置Swagger2生成API接口文檔詳情
這篇文章主要介紹了Spring Boot2配置Swagger2生成API接口文檔詳情,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09創(chuàng)建Jersey REST 服務,基于Maven的實現(xiàn)
下面小編就為大家?guī)硪黄獎?chuàng)建Jersey REST 服務,基于Maven的實現(xiàn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06java Person,Student,GoodStudent 三個類的繼承、構造函數(shù)的執(zhí)行
這篇文章主要介紹了java Person,Student,GoodStudent 三個類的繼承、構造函數(shù)的執(zhí)行,需要的朋友可以參考下2017-02-02SpringBoot @Cacheable自定義KeyGenerator方式
這篇文章主要介紹了SpringBoot @Cacheable自定義KeyGenerator方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12