当前位置:网站首页>feign报400处理

feign报400处理

2022-04-23 16:45:00 InfoQ

有个接口是这样的
getXxByIds(String Ids)
 id用','分隔,运行一段时间报了400。

报错内容

feign.FeignException$BadRequest: [400] during [POST] to [http...

问题重现

来模拟下问题

服务接口

@Slf4j
@RestController
public class SkyController {

 @GetMapping("/sky")
 public String sky (@RequestParam(required = false) String name) {
 log.info(name);
 return name;
 }

 @PostMapping("/deliver")
 public String deliver (String packageBox) {
 log.info(packageBox);
 return packageBox;
 }


 @PostMapping("/feignPost")
 public String feignPost(@RequestParam(name = "name") String name){
 log.info(name);
 return name;
 }


 @PostMapping("/feignBody")
 public String feignBody(@RequestBody String name){
 log.info(name);
 return name;
 }


}

另建一个服务 创建feignClinet, 示例服务加入了网关,使用了服务名称,可以直接使用url

// 网关 服务名称
@FeignClient(value = "paw-dogs-sky-service")
public interface SkyFeignClient {

 @PostMapping("/deliver")
 String deliver (@RequestParam(name = "packageBox") String packageBox);

 @PostMapping("/feignPost")
 String feignPost(@RequestParam(name = "name") String name);

 @PostMapping("/feignBody")
 String feignBody(@RequestBody String name);
}

编写测试类

@Slf4j
@SpringBootTest
class SkyFeignClientTest {


 @Autowired
 SkyFeignClient skyFeignClient;

 @Test
 void deliver () {
 String param = RandomUtil.randomString(10*1024);
 log.info(param);
 String result = skyFeignClient.deliver(param);
 log.info(result);

 }

 @Test
 void feignPost () {
 String param = RandomUtil.randomString(10*1024);
 log.info(param);
 String result = skyFeignClient.feignPost(param);
 log.info(result);
 }

 @Test
 void feignBody () {
 String param = RandomUtil.randomString(1*1024);
 log.info(param);
 String result = skyFeignClient.feignBody(param);
 log.info(result);
 }
}

运行测试发现 param较大时 get、post form-url 请求失败,requestBody方式请求成功。

null
运行post form-url 请求 会发现post请求发送的也是url拼接的方式
用postman直接访问服务测试
通过拼接的长url访问失败

null
通过 form-url方式访问成功

null
问题出现在url的长度限制

url长度限制

  • 浏览器url长度限制
  • 服务器长度限制 如tomcat限制, nginx url限制
  • SpringBoot 项目长度限制 
    max-http-header-size
     默认8k,更改提供服务sky的配置100*1024 (100k) ,重试服务正常调用。
  • DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8)

 server:
 port: 8080
 max-http-header-size: 102400

源码分析

从异常类SynchronousMethodHandler入手debug跟踪

构建请求的类 
RequestTemplate
 对应query请求 用有序链表存放参数 
Map<String, QueryTemplate> queries = new LinkedHashMap<>()
 形如 key-->value 
packageBox--> packageBox={packageBox}

SpringMvcContract
 对注解进行处理

类上的注解
processAnnotationOnClass

方法上的注解
processAnnotationOnMethod

参数上的注解 
processAnnotationsOnParameter

AnnotatedParameterProcessor
 的实现类 
RequestParamParameterProcessor
 对query参数进行封装

public boolean processArgument(AnnotatedParameterContext context,
 Annotation annotation, Method method) {
 int parameterIndex = context.getParameterIndex();
 Class<?> parameterType = method.getParameterTypes()[parameterIndex];
 MethodMetadata data = context.getMethodMetadata();

 if (Map.class.isAssignableFrom(parameterType)) {
 checkState(data.queryMapIndex() == null,
 &quot;Query map can only be present once.&quot;);
 data.queryMapIndex(parameterIndex);

 return true;
 }

 RequestParam requestParam = ANNOTATION.cast(annotation);
 String name = requestParam.value();
 checkState(emptyToNull(name) != null,
 &quot;RequestParam.value() was empty on parameter %s&quot;, parameterIndex);
 context.setParameterName(name);

 // 对参数进行封装
 Collection<String> query = context.setTemplateParameter(name,
 data.template().queries().get(name));
 data.template().query(name, query);
 return true;
 }

SynchronousMethodHandler
 invoke 方法 
executeAndDecode(template, options)
 执行http请求并解码。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
 // 构建请求
 Request request = targetRequest(template);

 if (logLevel != Logger.Level.NONE) {
 logger.logRequest(metadata.configKey(), logLevel, request);
 }

 Response response;
 long start = System.nanoTime();
 try {
 // 执行http请求
 response = client.execute(request, options);
 ...
 }

构建Request请求,通过client访问http服务。实现的client有
Default
 
LoadBalancerFeignClient
 
FeignBlockingLoadBalancerClient

解决

1. 调大服务提供者的header参数(微服务较多 不太适用) 
2. 改为requestBody调用服务

总结

​ feign通过解析接口类、方法、参数上的注解,通过
RequestTemplate
 @RequestParam以Url拼接的方式,构建了
Request
请求,通过Client访问http服务。对较长参数改为RequestBody方式调用服务。

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/26efaf86283ef5499d55ccc28