SpringCloud OpenFeign Post请求的坑
在微服务开发中SpringCloud全家桶集成了OpenFeign用于服务调用,SpringCloud的OpenFeign使用SpringMVCContract来解析OpenFeign的接口定义。
但是SpringMVCContract的Post接口解析实现有个巨坑,就是如果使用的是@RequestParam传参的Post请求,参数是直接挂在URL上的。
问题发现与分析
最近线上服务器突然经常性出现CPU高负载的预警,经过排查发现日志出来了大量的OpenFeign跨服务调用出现400的错误(HTTP Status 400)。
一般有两种情况:
- nginx 返回400
- java应用返回400
通过分析发现400是java应用返回的,那么可以确定是OpenFeign客户端发起跨服务请求时出现异常了。
但是查看源码发现出现这个问题的服务接口非常简单,就是一个只有三个参数的POST请求接口,这个错误并不是必现的错误,而是当参数值比较长(String)的时候才会出现。
所以可以初步确认可能是参数太长导致请求400,对于POST请求因参数太长导致400错误非常疑惑,POST请求除非把参数挂在URL上,否则不应该出现400才对。
问题排查
为了验证上面的猜测,手写了一个简单的示例,在验证过程中特意开启了OpenFeign的debug日志。
首先编写服务接口
这是一个简单的Post接口,仅有一个参数(这里的代码仅用于验证,非正式代码)
@FeignClient(name = "user-service-provider")
public interface HelloService {
@PostMapping("/hello")
public String hello(@RequestParam("name") String name);
}
编写服务
服务这里随便写一个Http接口即可(同上,代码仅用于验证)
@SpringBootApplication
@RestController
public class Starter {
@RequestMapping("/hello")
public String hello(String name) {
return "User服务返回值:Hello " + String.valueOf(name);
}
public static void main(String[] args) {
SpringApplication.run(Starter.class, args);
}
}
服务注册并调用
将服务注册到注册中心上,通过调用hello服务
@Autowired
public HelloService helloService;
@RequestMapping("/hello")
public String hello(String name) {
return helloService.hello(name);
}
通过日志可以发现,SpringCloud集成OpenFeign的POST请求确实是直接将参数挂在URL上,如下图:
正是因为这个巨坑,导致了线上服务器经常性高CPU负载预警。
问题解决
问题知道了,那么就好解决了,用SpringCloud官方的说法是可以使用@RequestBody来解决这个问题,但是@RequestBody的使用是有限制的,也就是参数只能有一个,而且需要整个调用链路都做相应的调整,这个代价有点高,这里不采用这种方式,而是采用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
- 在下图可以看出请求参数不再挂在URL上了
@RequestBody的解决方案
问题虽然解决了,但采用的不是官方推荐的方案,这里将官方推荐的这种@RequestBody的解决方法也贴出来。
使用@RequestBody解决,需要4个步骤:
- 编写请求参数Bean
public class HelloReqForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 调整接口声明
@PostMapping("/hello")
public String hello(@RequestBody HelloReqForm form);
- 调整服务调用
HelloReqForm form = new HelloReqForm();
form.setName(name);
return helloService.hello(form);
- 调整服务实现
@RequestMapping("/hello")
public String hello(@RequestBody HelloReqForm form) {
}
- 最终调用日志
涉及的Java类
最后列出一些涉及的java类:
- SpringMVCContract 服务接口解析
- RequestParamParameterProcessor 参数解析
- feign.RequestTemplate Rest请求构造器
- feign.Request 处理http请求
=========================================================
关注公众号,阅读更多文章。
最新文章
- 苹果微信下载 iOS微信各版本列表
- Apache Shiro 简单概念
- DllImport dll中有些啥函数 及 dll中是否用到了别的dll
- Mathematics:Pseudoprime numbers(POJ 3641)
- Cloudera Manager 5和CDH5离线安装
- Python 基礎 - 變量
- 2015年8月18日,杨学明老师《技术部门的绩效管理提升(研讨会)》在中国科学院下属机构CNNIC成功举办!
- [转]JavaScript实现 页面滚动图片加载
- thinkphp的mvc理解
- [视频]ARM告诉你物联网怎么玩,mbed 6LoWPan demo
- xe mysql
- Java 学习 第六篇;接口
- java:nextInt()和nextLine()一起使用出错
- 搞懂ES6的import export
- codeforces493B
- G - Intersecting Rectangles Kattis - intersectingrectangles (扫描线)(判断多个矩形相交)
- 廖雪峰Java3异常处理-1错误处理-4自定义异常
- 今天折腾phantomjs+selenium的笔记
- C++程序设计入门(上) 之对象和类
- [MAC OS] NSButton tag 获取