Feign遠(yuǎn)程調(diào)用丟失請求頭問題
前言
我們在寫服務(wù)端項(xiàng)目的時(shí)候,總會(huì)限制對某些資源的訪問,最常見的就是要求用戶先登錄才能訪問資源,當(dāng)用戶登錄后就會(huì)將此次會(huì)話信息保存進(jìn)session,同時(shí)返回給瀏覽器指定的cookie鍵值,下次瀏覽器再次訪問,請求頭中就會(huì)攜帶這個(gè)cookie,我們也以次來識別用戶的登錄狀態(tài),做出正確響應(yīng)。
問題
有時(shí)候,我們先行登錄,然后訪問服務(wù)A的某個(gè)方法,請求頭中攜帶cookie,標(biāo)識我們已經(jīng)登錄。
但若是我們訪問的目標(biāo)方法在執(zhí)行過程中使用feign進(jìn)行遠(yuǎn)程調(diào)用服務(wù)B,而服務(wù)B也要先判斷登錄狀態(tài),我們可能發(fā)現(xiàn)服務(wù)B會(huì)調(diào)用失敗,或者說拿不到數(shù)據(jù),理由是服務(wù)B認(rèn)為我們并未登錄。
而這時(shí),如果我們直接從瀏覽器訪問服務(wù)B的這個(gè)方法卻能得到一個(gè)成功的響應(yīng)。

查看源碼
1、使用feign進(jìn)行遠(yuǎn)程調(diào)用時(shí),首先判斷目標(biāo)方法類型,如果是 toString(),hashCode(),equals()這幾個(gè)方法,那就是本地直接完成了

2、執(zhí)行真正的遠(yuǎn)程調(diào)用的方法

3、根據(jù)模板template來創(chuàng)建請求request(默認(rèn)的情況feign是不會(huì)幫我們把原請求頭參數(shù)復(fù)制到新請求的請求頭上的),然后進(jìn)行客戶端client調(diào)用

4、但是創(chuàng)建請求時(shí),會(huì)先調(diào)用所有的攔截器RequestInterceptors

5、所以我們可以自己向容器中注冊一個(gè)攔截器RequestInterceptor,在這個(gè)攔截器中重寫apply方法,在apply方法中把老請求的cookie復(fù)制到新request的請求頭中,完成請求頭的同步。(然后targetRequest 方法就會(huì)調(diào)用攔截器的apply方法,進(jìn)行返回)
總結(jié):
feign遠(yuǎn)程調(diào)用,自己創(chuàng)建一個(gè)新的request對象,按照指定的路徑和參數(shù)發(fā)起新的請求,并得到響應(yīng)結(jié)果。但是這個(gè)新的request對象請求頭為空,所以丟失了原先請求中的數(shù)據(jù)。
feign在創(chuàng)建新的request對象時(shí),會(huì)調(diào)用一系列容器中的RequestInterceptor對象,執(zhí)行其apply方法,對這個(gè)創(chuàng)建好的request進(jìn)行增強(qiáng),再去真正執(zhí)行請求。但是默認(rèn)情況下容器中不存在這類攔截器對象。
我們可以自己向容器中注冊一個(gè)RequestInterceptor,在其apply方法體內(nèi),獲取到原始request,將其數(shù)據(jù)取出,賦值到新的request中,完成請求頭的同步。RequestContextHolder借助ThreadLocal將每一個(gè)原始請求與tomcat為其分配的線程綁定,之后,只要在同個(gè)線程內(nèi),隨時(shí)隨地都可輕易獲取到原始request。而我們是在apply方法體內(nèi),通過 RequestContextHolder.getRequestAttributes() 獲取的。RequestContextHolder是借助ThreadLocal將每一個(gè)原始請求與tomcat為其分配的線程綁定,之后,只要在同個(gè)線程內(nèi),隨時(shí)隨地都可輕易獲取到原始request。
解決

1、同步時(shí)
直接向spring容器注入RequestInterceptor攔截器即可
/**
* Feign
* @return
*/
@Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
//1、從RequestContextHolder獲取原始請求的請求數(shù)據(jù)(請求參數(shù)、請求頭等)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//2、同步請求頭數(shù)據(jù)
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
// 跳過 content-length,沒有跳過會(huì)導(dǎo)致請求參數(shù)無法接收
if (HttpHeaderConsts.CONTENT_LENGTH.equalsIgnoreCase(name)) {
continue;
}
requestTemplate.header(name, values);
}
}
};
}2、異步時(shí)
如果請求中使用了異步,也就是多線程,就算配置了上面的配置也會(huì)導(dǎo)致feign請求頭丟失,因?yàn)檎埱箢^信息是通過threadLocal保存的,也就是只有在同一個(gè)線程中才能使用請求中的請求頭并且同步初始請求頭信息到feign請求中
解決辦法
在異步調(diào)用時(shí)主動(dòng)將請求頭覆蓋到異步線程的請求上下文中
// 通過請求上下文獲取請求信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
// 遠(yuǎn)程查詢所有的收貨地址列表
// 在該線程中添加請求信息
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
orderConfirmVo.setAdress(address);
}, executor);
?
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
// 遠(yuǎn)程查詢購物車所有選中的購物想
// 在該線程中添加請求信息
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> currentUserItems = cartFeignService.getCurrentUserItems();
orderConfirmVo.setItems(currentUserItems);
}, executor);總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
從零開始使用IDEA創(chuàng)建SpringBoot項(xiàng)目(圖文)
這篇文章主要介紹了從零開始使用IDEA創(chuàng)建SpringBoot項(xiàng)目(圖文),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
java int轉(zhuǎn)byte和long轉(zhuǎn)byte的方法
下面小編就為大家?guī)硪黄猨ava int轉(zhuǎn)byte和long轉(zhuǎn)byte的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10
java String、Json對象與byte數(shù)組轉(zhuǎn)換方式
這篇文章主要介紹了java String、Json對象與byte數(shù)組轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Mybatis分頁插件PageHelper的配置和簡單使用方法(推薦)
在使用Java Spring開發(fā)的時(shí)候,Mybatis算是對數(shù)據(jù)庫操作的利器了。這篇文章主要介紹了Mybatis分頁插件PageHelper的配置和使用方法,需要的朋友可以參考下2017-12-12
java多線程學(xué)習(xí)之死鎖的模擬和避免(實(shí)例講解)
下面小編就為大家?guī)硪黄猨ava多線程學(xué)習(xí)之死鎖的模擬和避免(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06

