Feign調(diào)用服務(wù)時(shí)丟失Cookie和Header信息的解決方案
Feign調(diào)用服務(wù)丟失Cookie和Header信息
今天在使用Feign調(diào)用其他微服務(wù)的接口時(shí),發(fā)現(xiàn)了一個(gè)問(wèn)題:因?yàn)槲业捻?xiàng)目采用了無(wú)狀態(tài)登錄,token信息是存放在cookie中的,所以調(diào)用接口時(shí),因?yàn)閏ookie中沒(méi)有token信息,我的請(qǐng)求被攔截器攔截了。
參考幾篇文章,靠譜的解決方法是:將cookie信息放到請(qǐng)求頭中,再進(jìn)行調(diào)用接口時(shí),攔截器中可以對(duì)請(qǐng)求頭進(jìn)行解析,獲取cookie信息
服務(wù)調(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
*
* 自定義的請(qǐng)求頭處理類,處理服務(wù)發(fā)送時(shí)的請(qǐng)求頭;
* 將服務(wù)接收到的請(qǐng)求頭中的uniqueId和token字段取出來(lái),并設(shè)置到新的請(qǐng)求頭里面去轉(zhuǎn)發(fā)給下游服務(wù)
* 比如A服務(wù)收到一個(gè)請(qǐng)求,請(qǐng)求頭里面包含uniqueId和token字段,A處理時(shí)會(huì)使用Feign客戶端調(diào)用B服務(wù)
* 那么uniqueId和token這兩個(gè)字段就會(huì)添加到請(qǐng)求頭中一并發(fā)給B服務(wù);
*/
@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)通過(guò)如下方式取
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)通過(guò)如下方式取
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
/**
* 遍歷請(qǐng)求頭里面的屬性字段,將jsessionid添加到新的請(qǐng)求頭中轉(zhuǎn)發(fā)到下游服務(wù)
* */
if ("jsessionid".equalsIgnoreCase(name)) {
log.debug("添加自定義請(qǐng)求頭key:" + name + ",value:" + value);
requestTemplate.header(name, value);
} else {
log.debug("FeignHeadConfiguration", "非自定義請(qǐng)求頭key:" + name + ",value:" + value + "不需要添加!");
}
}
} else {
log.warn("FeignHeadConfiguration", "獲取請(qǐng)求頭失??!");
}
}
};
}
}
服務(wù)接受方
//有些請(qǐng)求時(shí)從通過(guò)feign進(jìn)行請(qǐng)求的,這一部分請(qǐng)求時(shí)不包含cookie信息的,因此我們要從請(qǐng)求頭中獲取
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;
}
}
}
運(yùn)行的時(shí)候,我發(fā)現(xiàn)請(qǐng)求還是被攔截了,看了下打印信息,發(fā)現(xiàn)我的MC_TOKEN變成了小寫,所以在字符串進(jìn)行比較的時(shí)候要忽略大小寫。

以下為擴(kuò)展,僅僅記錄一下
這樣仍然有個(gè)問(wèn)題:
在開(kāi)啟熔斷器之后,方法里的attrs是null,因?yàn)槿蹟嗥髂J(rèn)的隔離策略是thread,也就是線程隔離,實(shí)際上接收到的對(duì)象和這個(gè)在發(fā)送給B不是一個(gè)線程,怎么辦?
有一個(gè)辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為信號(hào)量的隔離模式,但是不推薦,因?yàn)閠hread是默認(rèn)的,而且要命的是信號(hào)量模式,熔斷器不生效,比如設(shè)置了熔斷時(shí)間。
另一個(gè)辦法:重寫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的隔離策略;
* 在轉(zhuǎn)發(fā)Feign的請(qǐng)求頭的時(shí)候,如果開(kāi)啟了Hystrix,Hystrix的默認(rèn)隔離策略是Thread(線程隔離策略),因此轉(zhuǎn)發(fā)攔截器內(nèi)是無(wú)法獲取到請(qǐng)求的請(qǐng)求頭信息的,可以修改默認(rèn)隔離策略為信號(hào)量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,這樣的話轉(zhuǎn)發(fā)線程和請(qǐng)求線程實(shí)際上是一個(gè)線程,這并不是最好的解決方法,信號(hào)量模式也不是官方最為推薦的隔離策略;另一個(gè)解決方法就是自定義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();
}
}
}
}
然后使用默認(rèn)的熔斷器隔離策略,也可以在攔截器內(nèi)獲取到上游服務(wù)的請(qǐng)求頭信息了;
Feign調(diào)用存在的問(wèn)題
① feign遠(yuǎn)程調(diào)用丟失請(qǐng)求頭
問(wèn)題描述:
當(dāng)遠(yuǎn)程調(diào)用其他服務(wù)時(shí),設(shè)置了攔截器判斷用戶是否登錄,但是結(jié)果是即使用戶登錄了,也會(huì)顯示用戶沒(méi)登錄,原因在于遠(yuǎn)程調(diào)用時(shí),發(fā)送的請(qǐng)求是一個(gè)新的情求,請(qǐng)求中并不存在cookie,而原始請(qǐng)求中是攜帶cookie的。

解決方案如下:
@Configuration
public class MallFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
RequestInterceptor requestInterceptor = template -> {
//1、使用RequestContextHolder拿到剛進(jìn)來(lái)的請(qǐng)求數(shù)據(jù)
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
//老請(qǐng)求
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2、同步請(qǐng)求頭的數(shù)據(jù)(主要是cookie)
//把老請(qǐng)求的cookie值放到新請(qǐng)求上來(lái),進(jìn)行一個(gè)同步
String cookie = request.getHeader("Cookie");
template.header("Cookie", cookie);
}
}
};
return requestInterceptor;
}
}
② 異步調(diào)用Feign丟失上下文問(wèn)題
問(wèn)題描述:
由于feign請(qǐng)求攔截器為新的request設(shè)置請(qǐng)求頭底層是使用ThreadLocal保存剛進(jìn)來(lái)的請(qǐng)求,所以在異步情況下,其他線程并不能獲取到主線程的ThreadLocal,所以也拿不到請(qǐng)求。
解決:
先獲取主線程的requestAttributes,再分別向其他線程中設(shè)置
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture.runAsync(() ->{
RequestContextHolder.setRequestAttributes(requestAttributes);
});
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot2配置Swagger2生成API接口文檔詳情
這篇文章主要介紹了Spring Boot2配置Swagger2生成API接口文檔詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
創(chuàng)建Jersey REST 服務(wù),基于Maven的實(shí)現(xiàn)
下面小編就為大家?guī)?lái)一篇?jiǎng)?chuàng)建Jersey REST 服務(wù),基于Maven的實(shí)現(xiàn)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
SpringBoot中整合Minio文件存儲(chǔ)的安裝部署過(guò)程
這篇文章主要介紹了SpringBoot整合Minio文件存儲(chǔ)的相關(guān)知識(shí),詳細(xì)介紹了Minio安裝部署過(guò)程,需要的朋友可以參考下2022-04-04
Java項(xiàng)目中如何訪問(wèn)WEB-INF下jsp頁(yè)面
這篇文章主要介紹了Java項(xiàng)目中如何訪問(wèn)WEB-INF下jsp頁(yè)面,文章通過(guò)示例代碼和圖文解析介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
java Person,Student,GoodStudent 三個(gè)類的繼承、構(gòu)造函數(shù)的執(zhí)行
這篇文章主要介紹了java Person,Student,GoodStudent 三個(gè)類的繼承、構(gòu)造函數(shù)的執(zhí)行,需要的朋友可以參考下2017-02-02
SpringBoot @Cacheable自定義KeyGenerator方式
這篇文章主要介紹了SpringBoot @Cacheable自定義KeyGenerator方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java web項(xiàng)目啟動(dòng)Tomcat報(bào)錯(cuò)解決方案
這篇文章主要介紹了Java web項(xiàng)目啟動(dòng)Tomcat報(bào)錯(cuò)解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07

