SpringCloud OpenFeign Post请求400错误解决方案 | 您所在的位置:网站首页 › openfeign调用太慢 › SpringCloud OpenFeign Post请求400错误解决方案 |
SpringCloud OpenFeign Post请求400错误解决方案 在微服务开发中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.bodyhttp://(query.substring(1)); } template.header("Content-Type", "application/x-www-form-urlencoded;"); } } } 配置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) { tUDqxTJmjshis.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请求 |
CopyRight 2018-2019 实验室设备网 版权所有 |