写点什么

feign 报 400 处理

作者:Rubble
  • 2022 年 4 月 23 日
  • 本文字数:2349 字

    阅读完需:约 8 分钟

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


报错内容


feign.FeignException$BadRequest: [400] during [POST] to [http...
复制代码

问题重现

来模拟下问题


服务接口


@Slf4j@RestControllerpublic 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@SpringBootTestclass 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 方式请求成功。



运行 post form-url 请求 会发现 post 请求发送的也是 url 拼接的方式

用 postman 直接访问服务测试

通过拼接的长 url 访问失败



通过 form-url 方式访问成功



问题出现在 url 的长度限制

url 长度限制

  1. 浏览器 url 长度限制

  2. 服务器长度限制 如 tomcat 限制, nginx url 限制

  3. SpringBoot 项目长度限制 max-http-header-size 默认 8k,更改提供服务 sky 的配置 100*1024 (100k) ,重试服务正常调用。

  4. 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, "Query map can only be present once."); data.queryMapIndex(parameterIndex);
return true; }
RequestParam requestParam = ANNOTATION.cast(annotation); String name = requestParam.value(); checkState(emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s", 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 方式调用服务。


用户头像

Rubble

关注

还未添加个人签名 2021.06.01 加入

还未添加个人简介

评论

发布
暂无评论
feign报400处理_4月日更_Rubble_InfoQ写作社区