记一次SpringBoot SpringCloud 版本升级Feign调用抛出400错误问题

648 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

记一次SpringBoot SpringCloud 版本升级Feign调用抛出400错误问题

问题背景

公司新的项目使用SpringBoot2.5.5 SpringCloud Hoxton.SR9版本搭建, Feign的相关配置沿用的老项目SpringBoot2.1.18.RELEASE SpringCloud Greenwich.RELEASE版本的配置, 结果运行时Feign远程调用抛出400参数过长错误, Feign调用的参数中包含大文本字段, 在老的项目中可以正常调用, 升级之后出现异常

  • Feign客户端使用的是POST+@RequestBody方式调用
  • Feign配置如下, 两个版本配置一样:
feign:
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 120000
  compression:
    request:
      enabled: true
    response:
      enabled: true
  okhttp:
    enabled: true
server:
  max-http-header-size: 8192

排查过程

由于错误是在调用方抛出, 那基本上就是因为Feign在构造HttpConnection的过程中触发了这个错误, 我们可以发现升级之后feign-core的版本变更为10.10.1了, 我们只需要这个feign-core版本与之前的版本构造出来的请求有什么不同, 就能找出问题的原因了

找到feign客户端构造类feign.Feign.Builder#build, 从这个入口处开始断点跟踪调用链

  • 相关代码如下:
 public Feign build() {
   ......
   SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
       new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
           logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
   ......
   return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
 }
  • 根据上面的代码我们最终找到相关处理类feign.SynchronousMethodHandlerfeign.SynchronousMethodHandler#targetRequest 经过对此处代码的单步调试, 发现版本升级之后的fegin构造的request的headers多一个gzip相关的配置"Content-Encoding", feign.Client.Default#convertAndSend 相关代码:
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      ......
   // 这里获取出来的结果不一样
   // 2.5.5版本的可以获取到
   Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
   boolean gzipEncodedRequest =
       contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
   boolean deflateEncodedRequest =
       contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);

   boolean hasAcceptHeader = false;
   Integer contentLength = null;
   for (String field : request.headers().keySet()) {
     if (field.equalsIgnoreCase("Accept")) {
       hasAcceptHeader = true;
     }
     for (String value : request.headers().get(field)) {
       if (field.equals(CONTENT_LENGTH)) {
         // 导致这里的contentLength不同
         if (!gzipEncodedRequest && !deflateEncodedRequest) {
           contentLength = Integer.valueOf(value);
           connection.addRequestProperty(field, value);
         }
       } else {
         connection.addRequestProperty(field, value);
       }
     }
   }
   // Some servers choke on the default accept string.
   if (!hasAcceptHeader) {
     connection.addRequestProperty("Accept", "*/*");
   }
   if (request.body() != null) {
     if (disableRequestBuffering) {
       // 最终的connection设置不同的contentLength
       if (contentLength != null) {
         connection.setFixedLengthStreamingMode(contentLength);
       } else {
         connection.setChunkedStreamingMode(8196);
       }
     }
    ......
   }
   return connection;
 }

总结

根据上述分析, 我们发现导致400的最终原因是由于不同版本的默认压缩配置不同, 升级之后的版本删除请求压缩配置(compression.request.enabled), 即可正常调用