You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OkHttp3.14.9添加日志拦截器触发Stream closed异常如何解决

OkHttp 3.14.x ResponseBody 多次消费异常修复方案

错误原因

  • OkHttp 的 ResponseBody 底层封装了网络请求的输入流,调用 string()/bytes() 等方法消费内容后,流会被自动关闭,后续业务逻辑再读取 body 就会抛出 Stream closed 异常
  • 你代码中直接读取了 response body 内容后直接返回原 response,相当于把已经消费过的 body 交给后续逻辑,因此报错

修复方案

有两种常用实现方式,可按需选择:

方案1:使用 peekBody 方法(推荐,代码简洁)

OkHttp 官方提供了 peekBody(long maxBytes) 方法,该方法会复制一份 body 的缓冲区内容返回,不会消费原始 response 的 body。
注意:peekBody 的入参是允许读取的最大字节数,若实际 body 长度超过该值,只会返回前对应长度的内容,不会影响原始 body 的完整性,建议根据业务场景设置合理值,避免大 body 导致 OOM
修复后的代码:

public class LoggingRequestInterceptor implements Interceptor {
    // 示例最大读取1M的body内容,可按需调整
    private static final long MAX_LOG_BODY_SIZE = 1024 * 1024;

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        
        String responseBodyLog = response.body() == null ? null : response.peekBody(MAX_LOG_BODY_SIZE).string();
        log.debug(
                "{}, {}, {}, {}, {}",
                request.url(),
                request.method(),
                request.body() == null ? null : JSONUtil.toJsonStr(request.body()),
                request.headers(),
                responseBodyLog
        );

        return response;
    }
}

方案2:手动构造新 Response 返回

如果需要自定义处理 body 的读取逻辑,也可以先把原始 body 的内容全部读出来,再重新构造一个新的 Response,把读出来的内容重新包装成 ResponseBody 设置回去,后续业务代码读取的是新构造的 body,不会受影响。
示例代码:

public class LoggingRequestInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        ResponseBody originalBody = response.body();
        if (originalBody == null) {
            return response;
        }
        
        // 读取原始body内容打印日志
        String bodyContent = originalBody.string();
        log.debug(
                "{}, {}, {}, {}, {}",
                request.url(),
                request.method(),
                request.body() == null ? null : JSONUtil.toJsonStr(request.body()),
                request.headers(),
                bodyContent
        );
        
        // 构造新的ResponseBody设置回Response返回
        ResponseBody newBody = ResponseBody.create(originalBody.contentType(), bodyContent.getBytes(StandardCharsets.UTF_8));
        return response.newBuilder()
                .body(newBody)
                .build();
    }
}

额外注意事项

  • 如果请求是上传类的大 RequestBody,直接调用 JSONUtil.toJsonStr(request.body()) 也可能出现类似的流消费问题,建议同样对 RequestBody 做复制处理,或者仅对特定 contentType 的请求打印 body
  • 生产环境建议限制打印的 body 长度,避免大文件请求导致日志内容过大,占用过多磁盘空间或者触发 OOM

内容的提问来源于stack exchange,提问作者zack zhang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 11:24:10