Feign客戶端呼叫服務時丟失Header引數的解決方案
前言
在SpringCloud微服務架構的專案中,服務之間的呼叫是通過Feign客戶端實現。預設情況下在使用Feign客戶端時,Feign 呼叫遠端服務存在Header請求頭引數丟失問題,例如一個訂單服務Order和一個商品服務Product,呼叫關係為: 使用者下單呼叫訂單服務,訂單庫建立一筆訂單,同時商品服務扣減庫存數量;在訂單服務通過Feign呼叫商品服務中扣減庫存的介面時,由於Feign是一個偽HTTP客戶端,這時相當於重新生成一個HTTP請求,會出現請求頭Header引數丟失問題,那麼下面給大家介紹一下如何解決這種不足。
一、解決方案
1. 實現Feign提供的RequestInterceptor
首先需要新建一個類FeignRequestInterceptor,用來做攔截器,FeignRequestInterceptor實現Feign中提供的RequestInterceptor類,重寫apply方法,在apply方法方法中通過邏輯程式碼實現獲取header請求頭引數資訊,然後傳遞下去。
具體做法吐下:
- FeignRequestInterceptor攔截器
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* @desc: 微服務之間使用Feign呼叫介面時, 透傳header引數資訊
* @author: cao_wencao
* @date: 2020-09-25 16:36
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
requestTemplate.header(name, values);
}
}
}
}
2. 配置使用
FeignRequestInterceptor實現Feign提供的RequestInterceptor後,看了網上大部分文件提到需要做一下額外的配置,才能夠使FeignRequestInterceptor生效,我所使用的SpringCloud版本是Greenwich.SR2,在這個版本中我發現無需額外的配置也可以正常生效,查了下文件,說是在Spring-Cloud Brixton之前的版本需要做一下額外的配置,方可生效。
這裡我兼顧到在使用SpringCloud比較老的版本,擴充套件一下需要如何配置才能生效自定義的攔截器實現無縫傳遞Header請求頭引數的目的。
二、配置生效
上述提到,這部分配置生效是為了兼顧老版本的SpringCloud所需要做的,新版本加上因為無妨,這裡給大家介紹兩種配置生效方式,分別是在@FeignClient註解屬性的configuration欄位配置攔截器類和application.yml中配置攔截器類。
1. @FeignClient註解屬性
@FeignClient註解中有個configuration配置屬性,通過configuration可指定自定義的攔截器FeignRequestInterceptor
- IProductService
import com.example.common.product.entity.Product;
import com.example.order.config.FeignRequestInterceptor;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @desc:
* @author: cao_wencao
* @date: 2020-09-22 23:43
*/
@FeignClient(value = "product-service",configuration = FeignRequestInterceptor.class)
public interface ProductService {
//@FeignClient的value + @RequestMapping的value值 其實就是完成的請求地址 "http://product-service/product/" + pid
//指定請求的URI部分
@RequestMapping("/product/product/{pid}")
Product findByPid(@PathVariable Integer pid);
//扣減庫存,模擬全域性事務提交
//引數一: 商品標識
//引數二:扣減數量
@RequestMapping("/product/reduceInventory/commit")
void reduceInventoryCommit(@RequestParam("pid") Integer pid,
@RequestParam("number") Integer number);
//扣減庫存,模擬全域性事務回滾
//引數一: 商品標識
//引數二:扣減數量
@RequestMapping("/product/reduceInventory/rollback")
void reduceInventoryRollback(@RequestParam("pid") Integer pid,
@RequestParam("number") Integer number);
}
- ProductController獲取Header傳遞的Token
/**
* @desc:
* @author: cao_wencao
* @date: 2020-09-22 23:16
*/
@RestController
@Slf4j
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 扣減庫存,正常->模擬全域性事務提交
* @param pid
* @param number
*/
@RequestMapping("/reduceInventory/commit")
public void reduceInventoryCommit(Integer pid, Integer number) {
String token = ServletUtils.getRequest().getHeader("token");
log.info("從head請求頭透傳過來的值為token:"+ token);
productService.reduceInventoryCommit(pid, number);
}
}
- ServletUtils工具類
public class ServletUtils {
/**
* 獲取String引數
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 獲取String引數
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 獲取Integer引數
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 獲取Integer引數
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 獲取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 獲取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 獲取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 將字串渲染到客戶端
*
* @param response 渲染物件
* @param string 待渲染的字串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 是否是Ajax非同步請求
*
* @param request
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.indexOf("application/json") != -1) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
return true;
}
String uri = request.getRequestURI();
if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) {
return true;
}
String ajax = request.getParameter("__ajax");
return StringUtils.inStringIgnoreCase(ajax, "json", "xml");
}
}
2. application.yml配置攔截器類
配置 讓所有 FeignClient,使用 FeignRequestInterceptor,具體如下:
- application.yml
#配置讓所有 FeignClient,使用FeignRequestInterceptor
feign:
hystrix:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
requestInterceptors: com.example.order.config.FeignRequestInterceptor
#修改預設隔離策略為訊號量模式
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
也可以配置讓 某個 FeignClient 使用這個 FeignRequestInterceptor,具體如下:
#配置讓所有 FeignClient,使用FeignRequestInterceptor
feign:
hystrix:
enabled: true
client:
config:
xxxx: ##表示遠端服務名
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
requestInterceptors: com.example.order.config.FeignRequestInterceptor
#修改預設隔離策略為訊號量模式
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
解釋一下下面這行配置的含義: 在轉發Feign的請求頭的時候, 如果開啟了Hystrix, Hystrix的預設隔離策略是Thread(執行緒隔離策略), 因此轉發攔截器內是無法獲取到請求的請求頭資訊的。
可以修改預設隔離策略為訊號量模式:
總結
通過上述為大家總結的幾點方式,我們可完美的解決Feign客戶端在呼叫服務時丟失了Header引數的問題,有什麼疑問,見下方評論,一起交流。
相關文章
- Feign 呼叫丟失Header的解決方案Header
- SpringCloud解決feign呼叫token丟失問題SpringGCCloud
- spring cloud微服務快速教程之(十四)spring cloud feign使用okhttp3--以及feign呼叫引數丟失的說明SpringCloud微服務HTTP
- OSSEC服務端配置客戶端批次部署方案服務端客戶端
- 服務端,客戶端服務端客戶端
- 客戶端,服務端客戶端服務端
- 解決spring cloud Feign遠端呼叫服務,新增headers解決攔截器攔截問題SpringCloudHeader
- Spring Cloud實戰系列(三) - 宣告式客戶端呼叫FeignSpringCloud客戶端
- 使用Golang搭建gRPC服務提供給.NetCore客戶端呼叫GolangRPCNetCore客戶端
- vue.js頁面重新整理及後退引數丟失的解決方案Vue.js
- RocketMQ訊息丟失解決方案:事務訊息MQ
- feign服務端出異常客戶端處理的方法服務端客戶端
- Android so庫防客戶端破解的解決方案Android客戶端
- 客戶端影片渲染目前最理想的解決方案客戶端
- nuxt反向代理,解決客戶端服務端兩者之間衝突UX客戶端服務端
- HTML轉PDF的純客戶端和純服務端實現方案HTML客戶端服務端
- 服務端渲染和客戶端渲染服務端客戶端
- MQTT協議從服務端到客戶端詳解MQQT協議服務端客戶端
- JavaScript精度丟失原因以及解決方案JavaScript
- Dubbo原始碼解析之客戶端初始化及服務呼叫原始碼客戶端
- Java與WCF互動(一):Java客戶端呼叫WCF服務 (轉)Java客戶端
- 高併發下丟失更新的解決方案
- React 服務端渲染方案完美的解決方案React服務端
- 初識Spring Cloud Eureka(三)(Eureka客戶端之間 服務的相互呼叫)SpringCloud客戶端
- Thrift 客戶端-服務端 零XML配置 註解式配置客戶端服務端XML
- GET請求的引數丟失
- python建立tcp服務端和客戶端PythonTCP服務端客戶端
- Spring Cloud Feign 宣告式服務呼叫SpringCloud
- SpringCloud-使用Feign呼叫服務介面SpringGCCloud
- Spring Cloud Alibaba(8)---Feign服務呼叫SpringCloud
- 宣告式服務呼叫 Spring Cloud FeignSpringCloud
- macOS 自帶的ftp服務端&vnc客戶端MacFTP服務端VNC客戶端
- React 服務端渲染完美的解決方案React服務端
- spring cloud feign實現遠端呼叫服務傳輸檔案SpringCloud
- golang實現tcp客戶端服務端程式GolangTCP客戶端服務端
- 09.redis 哨兵主備切換時資料丟失的解決方案Redis
- 使用Apollo Server搭建GraphQL的服務端和客戶端Server服務端客戶端
- 實現客戶端與服務端的HTTP通訊客戶端服務端HTTP