Feign调Post接口异常:Incomplete output stream

背景


最近使用了一个微服务权限管理框架,在使用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 接口1Content-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不一致情况,修改之后,微服务接口接口也能成功调用了

参考资料


相关推荐
懒惰蜗牛1 小时前
Day24 | Java泛型通配符与边界解析
java·后端·java-ee
HezhezhiyuLe1 小时前
MAC idea 环境变量设置失效
java·macos·intellij-idea
fatfishccc1 小时前
(七)API 重构的艺术:打造优雅、可维护的 API
java·驱动开发·intellij-idea·软件研发·后端开发·代码重构·api重构
Eoch771 小时前
从买菜到秒杀:Redis为什么能让你的网站快如闪电?
java·后端
我不是混子1 小时前
奇葩面试题:线程调用两次start方法会怎样?
java·后端
凤年徐1 小时前
【C++模板编程】从泛型思想到实战应用
java·c语言·开发语言·c++
摸鱼总工2 小时前
为什么读源码总迷路?有破解办法吗
后端
仙俊红2 小时前
深入理解 ThreadLocal —— 在 Spring Boot 中的应用与原理
java·spring boot·后端
飞鱼&2 小时前
RabbitMQ-高可用机制
java·rabbitmq·java-rabbitmq