Feign遠(yuǎn)程調(diào)用丟失請(qǐng)求頭問題
前言
我們?cè)趯懛?wù)端項(xiàng)目的時(shí)候,總會(huì)限制對(duì)某些資源的訪問,最常見的就是要求用戶先登錄才能訪問資源,當(dāng)用戶登錄后就會(huì)將此次會(huì)話信息保存進(jìn)session,同時(shí)返回給瀏覽器指定的cookie鍵值,下次瀏覽器再次訪問,請(qǐng)求頭中就會(huì)攜帶這個(gè)cookie,我們也以次來識(shí)別用戶的登錄狀態(tài),做出正確響應(yīng)。
問題
有時(shí)候,我們先行登錄,然后訪問服務(wù)A的某個(gè)方法,請(qǐng)求頭中攜帶cookie,標(biāo)識(shí)我們已經(jīng)登錄。
但若是我們?cè)L問的目標(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)建請(qǐng)求request(默認(rèn)的情況feign是不會(huì)幫我們把原請(qǐng)求頭參數(shù)復(fù)制到新請(qǐng)求的請(qǐng)求頭上的),然后進(jìn)行客戶端client調(diào)用

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

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

1、同步時(shí)
直接向spring容器注入RequestInterceptor攔截器即可
/**
* Feign
* @return
*/
@Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
//1、從RequestContextHolder獲取原始請(qǐng)求的請(qǐng)求數(shù)據(jù)(請(qǐng)求參數(shù)、請(qǐng)求頭等)
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//2、同步請(qǐng)求頭數(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)致請(qǐng)求參數(shù)無法接收
if (HttpHeaderConsts.CONTENT_LENGTH.equalsIgnoreCase(name)) {
continue;
}
requestTemplate.header(name, values);
}
}
};
}2、異步時(shí)
如果請(qǐng)求中使用了異步,也就是多線程,就算配置了上面的配置也會(huì)導(dǎo)致feign請(qǐng)求頭丟失,因?yàn)檎?qǐng)求頭信息是通過threadLocal保存的,也就是只有在同一個(gè)線程中才能使用請(qǐng)求中的請(qǐng)求頭并且同步初始請(qǐng)求頭信息到feign請(qǐng)求中
解決辦法
在異步調(diào)用時(shí)主動(dòng)將請(qǐng)求頭覆蓋到異步線程的請(qǐng)求上下文中
// 通過請(qǐng)求上下文獲取請(qǐng)求信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
// 遠(yuǎn)程查詢所有的收貨地址列表
// 在該線程中添加請(qǐng)求信息
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
orderConfirmVo.setAdress(address);
}, executor);
?
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
// 遠(yuǎn)程查詢購(gòu)物車所有選中的購(gòu)物想
// 在該線程中添加請(qǐng)求信息
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> currentUserItems = cartFeignService.getCurrentUserItems();
orderConfirmVo.setItems(currentUserItems);
}, executor);總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis處理mysql主鍵自動(dòng)增長(zhǎng)出現(xiàn)的不連續(xù)問題解決
本文主要介紹了MyBatis處理mysql主鍵自動(dòng)增長(zhǎng)出現(xiàn)的不連續(xù)問題解決,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
SpringCloud?GateWay網(wǎng)關(guān)示例代碼詳解
這篇文章主要介紹了SpringCloud?GateWay網(wǎng)關(guān),Spring?cloud?Gateway的功能很多很強(qiáng)大,文中提到了Spring?Cloud?Gateway中幾個(gè)重要的概念,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-04-04
springboot @ConfigurationProperties和@PropertySource的區(qū)別
這篇文章主要介紹了springboot @ConfigurationProperties和@PropertySource的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Netty網(wǎng)絡(luò)編程實(shí)戰(zhàn)之開發(fā)聊天室功能
這篇文章主要為大家詳細(xì)介紹了如何利用Netty實(shí)現(xiàn)聊天室功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Netty網(wǎng)絡(luò)編程有一定幫助,需要的可以參考一下2022-10-10
MyBatis異常-Property ''configLocation'' not specified, using d
今天小編就為大家分享一篇關(guān)于MyBatis異常-Property 'configLocation' not specified, using default MyBatis Configuration,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
Java+Swing實(shí)現(xiàn)中國(guó)象棋游戲
這篇文章將通過Java+Swing實(shí)現(xiàn)經(jīng)典的中國(guó)象棋游戲。文中可以實(shí)現(xiàn)開始游戲,悔棋,退出等功能。感興趣的小伙伴可以跟隨小編一起動(dòng)手試一試2022-02-02

