postmain报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上,如下图:

e5f14f228cb05cc44faff3bb10f3da99.png

正是因为这个巨坑,导致了线上服务器经常性高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上了

9044f5c33f024ab931ac3a7242510910.png

@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) {

}

最终调用日志

6b33d253c85f95734bdb1f1defcb6ffc.png

涉及的Java类

最后列出一些涉及的java类:

SpringMVCContract 服务接口

RequestParamParameterProcessor 参数

feign.RequestTemplate Rest请求构造器

feign.Request 处理http请求

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值