背景
最近使用了一个微服务权限管理框架,在使用feign
调用其他服务的post
接口时,总是调不通,报错如下
bash
feign.RetryableException:
Incomplete output stream executing POST http://xxx
表示 Feign 在发送请求体(request body)时,输出流还没写完就被中断了
经过多方查找,发现是由于框架在实现RequestInterceptor 的配置类上,错误处理了Content-Length
分析
原先的配置类代码如下
java
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return;
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
template.header(name, value);
}
}
}
}
}
这段代码是一个 Feign 请求拦截器(RequestInterceptor
) 的实现,
主要用于在微服务间通过 Feign
进行 HTTP
调用时,自动透传 当前 HTTP
请求的请求头(Headers
),确保关键信息(如认证 Token、追踪 ID
等)在服务调用链中不丢失。
其中,关键的代码在于
java
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
template.header(name, value);
}
作用是:取原始请求的所有 Header
字段名称,将原始请求的 Header
键值对注入到 Feign
请求模板,确保下游服务能接收到相同的头信息
但是,这里忽略了一个问题,将所有的 Header
字段都进行了注入,包括Content-Length
Content-Length
是 HTTP 报文头部(Header)中的一个标准字段,用来 指明请求体或响应体(body)的字节长度,单位是字节(byte)
场景 | 请求体类型 | 是否自动生成 | 说明 |
---|---|---|---|
浏览器(fetch / axios ) |
JSON / 表单 | ✅ 是 | 浏览器自动设置 |
Postman / curl | 任意 | ✅ 是 | 工具自动处理 |
Java RestTemplate |
JSON / 表单 | ✅ 是 | Spring 自动计算 |
Java HttpClient (JDK 11+) |
字符串 / JSON | ✅ 是 | 自动生成 |
Feign(常见调用) | @RequestBody JSON |
✅ 是 | 自动编码并设置 |
Feign 上传文件 | MultipartFile |
⚠️ 不一定 | 多数用 chunked,不设置 Content-Length |
手写 HttpURLConnection |
自定义 OutputStream | ❌ 否 | 需要手动设置 |
我们的接口调用路径是这样的:
浏览器 -> 微服务A 接口1 -> feign 拦截器 -> 微服务B 接口2
结合上面的表格,在这个过程中,我们的Content-Length
都应该是自动生成的
但是,我们在feign
拦截器中,将微服务A 接口1
的Content-Length
一并复制给了微服务B 接口2
Content-Length
是根据 HTTP 请求体或响应体的字节长度(byte) 计算出来的
而两者的请求体 字节长度不同,计算出来的Content-Length
也不会相同
这样造成的结果便是:Content-Length
不匹配问题,即太大或者太小
如果
Content-Length
数值设置错误,可能导致:
- 数据截断(太小)
- 请求挂起或超时(太大)
如图所示

最终微服务B 接口2
得到了错误的Content-Length:10
实际上,它却发送了 100
字节的数据,表现为表现为 写流中断 → Incomplete output stream
解决方法
既然知道了原因,那么修改也就简单了
我们可以在遍历Header
字段时,不注入Content-Length
,使其自动生成,即
java
String name = headerNames.nextElement();
if (!"content-length".equals(name)) {
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
template.header(name, value);
}
}
这样,就避免了Content-Length
不一致情况,修改之后,微服务接口接口也能成功调用了