SpringCloud OpenFeign Post請求400錯(cuò)誤解決方案
在微服務(wù)開發(fā)中SpringCloud全家桶集成了OpenFeign用于服務(wù)調(diào)用,SpringCloud的OpenFeign使用SpringMVCContract來解析OpenFeign的接口定義。 但是SpringMVCContract的Post接口解析實(shí)現(xiàn)有個(gè)巨坑,就是如果使用的是@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的錯(cuò)誤(HTTP Status 400)。
一般有兩種情況:
- nginx 返回400
- java應(yīng)用返回400
通過分析發(fā)現(xiàn)400是java應(yīng)用返回的,那么可以確定是OpenFeign客戶端發(fā)起跨服務(wù)請求時(shí)出現(xiàn)異常了。 但是查看源碼發(fā)現(xiàn)出現(xiàn)這個(gè)問題的服務(wù)接口非常簡單,就是一個(gè)只有三個(gè)參數(shù)的POST請求接口,這個(gè)錯(cuò)誤并不是必現(xiàn)的錯(cuò)誤,而是當(dāng)參數(shù)值比較長(String)的時(shí)候才會(huì)出現(xiàn)。 所以可以初步確認(rèn)可能是參數(shù)太長導(dǎo)致請求400,對于POST請求因參數(shù)太長導(dǎo)致400錯(cuò)誤非常疑惑,POST請求除非把參數(shù)掛在URL上,否則不應(yīng)該出現(xiàn)400才對。
問題排查
為了驗(yàn)證上面的猜測,手寫了一個(gè)簡單的示例,在驗(yàn)證過程中特意開啟了OpenFeign的debug日志。
首先編寫服務(wù)接口
這是一個(gè)簡單的Post接口,僅有一個(gè)參數(shù)(這里的代碼僅用于驗(yàn)證,非正式代碼)
@FeignClient(name = "user-service-provider") public interface HelloService { @PostMapping("/hello") public String hello(@RequestParam("name") String name); }
編寫服務(wù)
服務(wù)這里隨便寫一個(gè)Http接口即可(同上,代碼僅用于驗(yàn)證)
@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í)是直接將參數(shù)掛在URL上,如下圖:
正是因?yàn)檫@個(gè)巨坑,導(dǎo)致了線上服務(wù)器經(jīng)常性高CPU負(fù)載預(yù)警。
問題解決
問題知道了,那么就好解決了,用SpringCloud官方的說法是可以使用@RequestBody來解決這個(gè)問題,但是@RequestBody的使用是有限制的,也就是參數(shù)只能有一個(gè),而且需要整個(gè)調(diào)用鏈路都做相應(yīng)的調(diào)整,這個(gè)代價(jià)有點(diǎn)高,這里不采用這種方式,而是采用RequestInterceptor來處理。
編寫RequestInterceptor
這里將request的queryLine取下來放在body中,需要注意的是,只有body沒有值的時(shí)候才能這么做。
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個(gè)步驟:
編寫請求參數(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ù)實(shí)現(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í)有所幫助,也希望大家多多支持腳本之家。
- Java之Springcloud Feign組件詳解
- Java之Springcloud Gateway內(nèi)置路由案例講解
- Java之SpringCloud nocos注冊中心講解
- Java之SpringCloud Eurka注冊錯(cuò)誤解決方案
- SpringSecurit鹽值加密的密碼驗(yàn)證以及強(qiáng)密碼驗(yàn)證過程
- SpringCloud OpenFeign基本介紹與實(shí)現(xiàn)示例
- springcloud引入spring-cloud-starter-openfeign失敗的解決
- Java中?springcloud.openfeign應(yīng)用案例解析
相關(guān)文章
Mybatis單個(gè)參數(shù)的if判斷報(bào)異常There is no getter for property named ''x
今天小編就為大家分享一篇關(guān)于Mybatis單個(gè)參數(shù)的if判斷報(bào)異常There is no getter for property named 'xxx' in 'class java.lang.Integer'的解決方案,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12淺談SpringMVC中Interceptor和Filter區(qū)別
這篇文章主要介紹了淺談SpringMVC中Interceptor和Filter區(qū)別,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-04-04java求數(shù)組元素重復(fù)次數(shù)和java字符串比較大小示例
這篇文章主要介紹了java求數(shù)組元素重復(fù)次數(shù)和java字符串比較大小示例,需要的朋友可以參考下2014-04-04Java開發(fā)學(xué)習(xí)之Bean的作用域和生命周期詳解
這篇文章主要介紹了淺談Spring中Bean的作用域,生命周期和注解,從創(chuàng)建到消亡的完整過程,例如人從出生到死亡的整個(gè)過程就是一個(gè)生命周期。本文將通過示例為大家詳細(xì)講講,感興趣的可以學(xué)習(xí)一下2022-06-06SpringMvc根據(jù)返回值類型不同處理響應(yīng)的方法
這篇文章主要介紹了SpringMvc根據(jù)返回值類型不同處理響應(yīng),我們可以通過控制器方法的返回值設(shè)置跳轉(zhuǎn)的視圖,控制器支持如void,String,ModelAndView類型,需要的朋友可以參考下2023-09-09Spring?Boot?整合持久層之Spring Data JPA
在介紹Spring Data JPA的時(shí)候,我們首先認(rèn)識下Hibernate。Hibernate是數(shù)據(jù)訪問解決技術(shù)的絕對霸主,使用O/R映射技術(shù)實(shí)現(xiàn)數(shù)據(jù)訪問,O/R映射即將領(lǐng)域模型類和數(shù)據(jù)庫的表進(jìn)行映射,通過程序操作對象而實(shí)現(xiàn)表數(shù)據(jù)操作的能力,讓數(shù)據(jù)訪問操作無須關(guān)注數(shù)據(jù)庫相關(guān)的技術(shù)2022-08-08