Feign遠(yuǎn)程調(diào)用丟失請求頭問題
前言
我們在寫服務(wù)端項目的時候,總會限制對某些資源的訪問,最常見的就是要求用戶先登錄才能訪問資源,當(dāng)用戶登錄后就會將此次會話信息保存進(jìn)session,同時返回給瀏覽器指定的cookie鍵值,下次瀏覽器再次訪問,請求頭中就會攜帶這個cookie,我們也以次來識別用戶的登錄狀態(tài),做出正確響應(yīng)。
問題
有時候,我們先行登錄,然后訪問服務(wù)A的某個方法,請求頭中攜帶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會調(diào)用失敗,或者說拿不到數(shù)據(jù),理由是服務(wù)B認(rèn)為我們并未登錄。
而這時,如果我們直接從瀏覽器訪問服務(wù)B的這個方法卻能得到一個成功的響應(yīng)。
查看源碼
1、使用feign進(jìn)行遠(yuǎn)程調(diào)用時,首先判斷目標(biāo)方法類型,如果是 toString(),hashCode(),equals()這幾個方法,那就是本地直接完成了
2、執(zhí)行真正的遠(yuǎn)程調(diào)用的方法
3、根據(jù)模板template來創(chuàng)建請求request(默認(rèn)的情況feign是不會幫我們把原請求頭參數(shù)復(fù)制到新請求的請求頭上的),然后進(jìn)行客戶端client調(diào)用
4、但是創(chuàng)建請求時,會先調(diào)用所有的攔截器RequestInterceptors
5、所以我們可以自己向容器中注冊一個攔截器RequestInterceptor,在這個攔截器中重寫apply方法,在apply方法中把老請求的cookie復(fù)制到新request的請求頭中,完成請求頭的同步。(然后targetRequest 方法就會調(diào)用攔截器的apply方法,進(jìn)行返回)
總結(jié):
feign遠(yuǎn)程調(diào)用,自己創(chuàng)建一個新的request對象,按照指定的路徑和參數(shù)發(fā)起新的請求,并得到響應(yīng)結(jié)果。但是這個新的request對象請求頭為空,所以丟失了原先請求中的數(shù)據(jù)。
feign在創(chuàng)建新的request對象時,會調(diào)用一系列容器中的RequestInterceptor對象,執(zhí)行其apply方法,對這個創(chuàng)建好的request進(jìn)行增強,再去真正執(zhí)行請求。但是默認(rèn)情況下容器中不存在這類攔截器對象。
我們可以自己向容器中注冊一個RequestInterceptor,在其apply方法體內(nèi),獲取到原始request,將其數(shù)據(jù)取出,賦值到新的request中,完成請求頭的同步。RequestContextHolder借助ThreadLocal將每一個原始請求與tomcat為其分配的線程綁定,之后,只要在同個線程內(nèi),隨時隨地都可輕易獲取到原始request。而我們是在apply方法體內(nèi),通過 RequestContextHolder.getRequestAttributes() 獲取的。RequestContextHolder是借助ThreadLocal將每一個原始請求與tomcat為其分配的線程綁定,之后,只要在同個線程內(nèi),隨時隨地都可輕易獲取到原始request。
解決
1、同步時
直接向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,沒有跳過會導(dǎo)致請求參數(shù)無法接收 if (HttpHeaderConsts.CONTENT_LENGTH.equalsIgnoreCase(name)) { continue; } requestTemplate.header(name, values); } } }; }
2、異步時
如果請求中使用了異步,也就是多線程,就算配置了上面的配置也會導(dǎo)致feign請求頭丟失,因為請求頭信息是通過threadLocal保存的,也就是只有在同一個線程中才能使用請求中的請求頭并且同步初始請求頭信息到feign請求中
解決辦法
在異步調(diào)用時主動將請求頭覆蓋到異步線程的請求上下文中
// 通過請求上下文獲取請求信息 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é)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis處理mysql主鍵自動增長出現(xiàn)的不連續(xù)問題解決
本文主要介紹了MyBatis處理mysql主鍵自動增長出現(xiàn)的不連續(xù)問題解決,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09SpringCloud?GateWay網(wǎng)關(guān)示例代碼詳解
這篇文章主要介紹了SpringCloud?GateWay網(wǎng)關(guān),Spring?cloud?Gateway的功能很多很強大,文中提到了Spring?Cloud?Gateway中幾個重要的概念,結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-04-04springboot @ConfigurationProperties和@PropertySource的區(qū)別
這篇文章主要介紹了springboot @ConfigurationProperties和@PropertySource的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Netty網(wǎng)絡(luò)編程實戰(zhàn)之開發(fā)聊天室功能
這篇文章主要為大家詳細(xì)介紹了如何利用Netty實現(xiàn)聊天室功能,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Netty網(wǎng)絡(luò)編程有一定幫助,需要的可以參考一下2022-10-10MyBatis異常-Property ''configLocation'' not specified, using d
今天小編就為大家分享一篇關(guān)于MyBatis異常-Property 'configLocation' not specified, using default MyBatis Configuration,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03