SpringCloud OpenFeign Post請求400錯誤解決方案
在微服務(wù)開發(fā)中SpringCloud全家桶集成了OpenFeign用于服務(wù)調(diào)用,SpringCloud的OpenFeign使用SpringMVCContract來解析OpenFeign的接口定義。 但是SpringMVCContract的Post接口解析實現(xiàn)有個巨坑,就是如果使用的是@RequestParam傳參的Post請求,參數(shù)是直接掛在URL上的。
問題發(fā)現(xiàn)與分析
最近線上服務(wù)器突然經(jīng)常性出現(xiàn)CPU高負(fù)載的預(yù)警,經(jīng)過排查發(fā)現(xiàn)日志出來了大量的OpenFeign跨服務(wù)調(diào)用出現(xiàn)400的錯誤(HTTP Status 400)。
一般有兩種情況:
- nginx 返回400
- java應(yīng)用返回400
通過分析發(fā)現(xiàn)400是java應(yīng)用返回的,那么可以確定是OpenFeign客戶端發(fā)起跨服務(wù)請求時出現(xiàn)異常了。 但是查看源碼發(fā)現(xiàn)出現(xiàn)這個問題的服務(wù)接口非常簡單,就是一個只有三個參數(shù)的POST請求接口,這個錯誤并不是必現(xiàn)的錯誤,而是當(dāng)參數(shù)值比較長(String)的時候才會出現(xiàn)。 所以可以初步確認(rèn)可能是參數(shù)太長導(dǎo)致請求400,對于POST請求因參數(shù)太長導(dǎo)致400錯誤非常疑惑,POST請求除非把參數(shù)掛在URL上,否則不應(yīng)該出現(xiàn)400才對。
問題排查
為了驗證上面的猜測,手寫了一個簡單的示例,在驗證過程中特意開啟了OpenFeign的debug日志。
首先編寫服務(wù)接口
這是一個簡單的Post接口,僅有一個參數(shù)(這里的代碼僅用于驗證,非正式代碼)
@FeignClient(name = "user-service-provider")
public interface HelloService {
@PostMapping("/hello")
public String hello(@RequestParam("name") String name);
}
編寫服務(wù)
服務(wù)這里隨便寫一個Http接口即可(同上,代碼僅用于驗證)
@SpringBootApplication
@RestController
public class Starter {
@RequestMapping("/hello")
public String hello(String name) {
return "User服務(wù)返回值:Hello " + String.valueOf(name);
}
public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}
服務(wù)注冊并調(diào)用
將服務(wù)注冊到注冊中心上,通過調(diào)用hello服務(wù)
@Autowired
public HelloService helloService;
@RequestMapping("/hello")
public String hello(String name) {
return helloService.hello(name);
}
通過日志可以發(fā)現(xiàn),SpringCloud集成OpenFeign的POST請求確實是直接將參數(shù)掛在URL上,如下圖:

正是因為這個巨坑,導(dǎo)致了線上服務(wù)器經(jīng)常性高CPU負(fù)載預(yù)警。
問題解決
問題知道了,那么就好解決了,用SpringCloud官方的說法是可以使用@RequestBody來解決這個問題,但是@RequestBody的使用是有限制的,也就是參數(shù)只能有一個,而且需要整個調(diào)用鏈路都做相應(yīng)的調(diào)整,這個代價有點高,這里不采用這種方式,而是采用RequestInterceptor來處理。
編寫RequestInterceptor
這里將request的queryLine取下來放在body中,需要注意的是,只有body沒有值的時候才能這么做。
public class PostRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
if ("post".equalsIgnoreCase(template.method()) && template.body() == null) {
String query = template.queryLine();
template.queries(new HashMap<>());
if (StringUtils.hasText(query) && query.startsWith("?")) {
template.body(query.substring(1));
}
template.header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
}
}
}
配置RequestInterceptor
feign:
client:
config:
default:
requestInterceptors: cn.com.ava.yaolin.springcloud.demo.PostRequestInterceptor
在下圖可以看出請求參數(shù)不再掛在URL上了

@RequestBody的解決方案
問題雖然解決了,但采用的不是官方推薦的方案,這里將官方推薦的這種@RequestBody的解決方法也貼出來。 使用@RequestBody解決,需要4個步驟:
編寫請求參數(shù)Bean
public class HelloReqForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
調(diào)整接口聲明
@PostMapping("/hello")
public String hello(@RequestBody HelloReqForm form);
調(diào)整服務(wù)調(diào)用
HelloReqForm form = new HelloReqForm();
form.setName(name);
return helloService.hello(form);
調(diào)整服務(wù)實現(xiàn)
@RequestMapping("/hello")
public String hello(@RequestBody HelloReqForm form) {
}
最終調(diào)用日志

涉及的Java類
最后列出一些涉及的java類:
- SpringMVCContract 服務(wù)接口
- RequestParamParameterProcessor 參數(shù)
- feign.RequestTemplate Rest請求構(gòu)造器
- feign.Request 處理http請求
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot Entity中枚舉類型詳細(xì)使用介紹
本文介紹SpringBoot如何在Entity(DAO)中使用枚舉類型。(本文使用MyBatis-Plus)。在實際開發(fā)中,經(jīng)常會遇到表示類型或者狀態(tài)的情況,比如:有三種支付方式:微信、支付寶、銀聯(lián)。本文介紹如何這種場景的方案對比,并用實例來介紹如何用枚舉這種最優(yōu)雅的來表示2022-10-10
如何使用Spring boot的@Transactional進(jìn)行事務(wù)管理
這篇文章介紹了SpringBoot中使用@Transactional注解進(jìn)行聲明式事務(wù)管理的詳細(xì)信息,包括基本用法、核心配置參數(shù)、關(guān)鍵注意事項、調(diào)試技巧、最佳實踐以及完整示例,感興趣的朋友一起看看吧2025-02-02
RabbitMq報錯reply-code=406 reply-text=PRECONDITION_FAILED
這篇文章主要為大家介紹了RabbitMq報錯reply-code=406 reply-text=PRECONDITION_FAILED分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Spring ApplicationContext上下文核心容器深入探究
ApplicationContext是Spring應(yīng)用程序中的中央接口,由于繼承了多個組件,使得ApplicationContext擁有了許多Spring的核心功能,如獲取bean組件,注冊監(jiān)聽事件,加載資源文件等2023-01-01

