Feign调Post接口异常:Incomplete output stream

零 Java教程评论21字数 2349阅读7分49秒阅读模式

背景


最近使用了一个微服务权限管理框架,在使用feign调用其他服务的post接口时,总是调不通,报错如下

feign.RetryableException: 
    Incomplete output stream executing POST http://xxx

表示 Feign 在发送请求体(request body)时,输出流还没写完就被中断了文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

经过多方查找,发现是由于框架在实现RequestInterceptor的配置类上,错误处理了Content-Length文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

分析


原先的配置类代码如下文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

@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 等)在服务调用链中不丢失。文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

其中,关键的代码在于文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
    String value = values.nextElement();
    template.header(name, value);
}

作用是:取原始请求的所有 Header 字段名称,将原始请求的 Header 键值对注入到 Feign 请求模板,确保下游服务能接收到相同的头信息文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

但是,这里忽略了一个问题,将所有的 Header 字段都进行了注入,包括Content-Length文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

Content-Length 是 HTTP 报文头部(Header)中的一个标准字段,用来 指明请求体或响应体(body)的字节长度,单位是字节(byte)文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

场景 请求体类型 是否自动生成 说明
浏览器(fetch / axios JSON / 表单 ✅ 是 浏览器自动设置
Postman / curl 任意 ✅ 是 工具自动处理
Java RestTemplate JSON / 表单 ✅ 是 Spring 自动计算
Java HttpClient(JDK 11+) 字符串 / JSON ✅ 是 自动生成
Feign(常见调用) @RequestBody JSON ✅ 是 自动编码并设置
Feign 上传文件 MultipartFile ⚠️ 不一定 多数用 chunked,不设置 Content-Length
手写 HttpURLConnection 自定义 OutputStream ❌ 否 需要手动设置

我们的接口调用路径是这样的:文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

浏览器 -> 微服务A 接口1 -> feign 拦截器 -> 微服务B 接口2文章源自灵鲨社区-https://www.0s52.com/bcjc/javajc/18604.html

结合上面的表格,在这个过程中,我们的Content-Length都应该是自动生成的

但是,我们在feign拦截器中,将微服务A 接口1Content-Length一并复制给了微服务B 接口2

Content-Length 是根据 HTTP 请求体或响应体的字节长度(byte) 计算出来的

而两者的请求体字节长度不同,计算出来的Content-Length也不会相同

这样造成的结果便是:Content-Length不匹配问题,即太大或者太小

如果 Content-Length 数值设置错误,可能导致:

  • 数据截断(太小)
  • 请求挂起或超时(太大)

如图所示

1751623282176.png

最终微服务B 接口2得到了错误的Content-Length:10

实际上,它却发送了 100字节的数据,表现为表现为 写流中断 → Incomplete output stream

解决方法


既然知道了原因,那么修改也就简单了

我们可以在遍历Header字段时,不注入Content-Length,使其自动生成,即

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不一致情况,修改之后,微服务接口接口也能成功调用了

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javajc/18604.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论