持续创作,加速成长!这是我参与「掘金日新计划 · 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.SynchronousMethodHandler
、feign.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
), 即可正常调用