纳尼?feign报400?

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

有个接口是这样的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方式请求成功。

image-20210709195835500.png

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

用postman直接访问服务测试

通过拼接的长url访问失败

image-20210709201102690.png

通过 form-url方式访问成功

image-20210709201219862.png

问题出现在url的长度限制

url长度限制

  1. 浏览器url长度限制
  2. 服务器长度限制 如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,
                    "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方式调用服务。

相关文章
|
JSON 负载均衡 Java
SpringCloud Feign 远程调用(史上最详细讲解)
SpringCloud Feign 远程调用(史上最详细讲解)
8484 0
SpringCloud Feign 远程调用(史上最详细讲解)
|
JavaScript Java
二十二.SpringCloud源码剖析-Hystrix降级
这篇文章是接上一篇《[SpringCloud源码剖析-Hystrix初始化](https://blog.csdn.net/u014494148/article/details/117221635?spm=1001.2014.3001.5501)》,继续完成Hystrix未完成的执行流程
|
负载均衡 Java 网络架构
十六.SpringCloud源码剖析-Feign源码分析
Spring Cloud OpenFeign 对 Netflix Feign 进行了封装,我们通常都使用Spring Cloud OpenFeign作为服务的负载均衡,本文章主要是探讨一下OpenFeign的初始化流程,以及生成代理类注入到Spring的过程
|
JSON 负载均衡 算法
OpenFeign夺命连环9问?
OpenFeign夺命连环9问?
|
前端开发 Java 关系型数据库
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(二)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(二)
|
JSON 负载均衡 算法
openFeign夺命连环9问,这谁受得了?
openFeign夺命连环9问,这谁受得了?
|
XML JSON 前端开发
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(一)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)
《我要进大厂》- Spring/SpringBoot 常用注解 夺命连环11问,你能坚持到第几问?(Spring/SpringBoot注解篇)(一)
|
存储 Java
Feign接口踩坑
由于业务需要,需要在接口中传递参数,调用消息中心的短信接口,进行短信的发送。如果使用Feign接口,没有携带token时,调用Feign接口,可以正常调用,但是如果携带token,就会出现appId拼接参数的情况。appId出现拼接时什么原因导致的呢?
276 0
Feign接口踩坑
|
设计模式 XML 前端开发
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)(三)
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)(三)
|
XML 前端开发 Java
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)(一)
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)
《我要进大厂》- Spring框架 夺命连环22问,你能坚持到第几问?(Spring高频问题)(一)
下一篇
DataWorks