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

使用ResponseWrapper时出现分块编码解析错误的技术求助

解决分块编码解析错误:Invalid character in chunk size / Illegal or missing hexadecimal sequence

问题现象

  • 使用Postman调用接口时,触发错误:Parse Error: Invalid character in chunk size
  • 使用curl调用时,触发错误:Illegal or missing hexadecimal sequence in chunked-encoding
    仅在启用自定义的GenericResponseWrapper过滤器时才会出现该问题,该过滤器用于捕获响应体、请求头等指标。

问题根源

你的GenericResponseWrapper直接包装了容器的原始ServletOutputStream,当容器采用**分块编码(chunked)**传输响应时,分块的大小标识等元数据会通过这个输出流写入。但你的实现存在两个关键问题:

  1. 包装的输出流干扰了容器分块编码的字节传输逻辑,导致客户端接收到的分块大小不是合法的十六进制序列,触发解析错误。
  2. FilterServletOutputStream的isReady()方法固定返回false,可能导致容器的异步输出逻辑异常,进一步破坏分块传输。

修复方案

修改GenericResponseWrapper,先将响应内容全部缓存到本地字节流,再在过滤器最后统一写入原始响应,避免干扰容器的分块处理:

package com.demo.utils;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;

public class GenericResponseWrapper extends HttpServletResponseWrapper {
    private ByteArrayOutputStream cachedContent;
    private ServletOutputStream outputStream;
    private PrintWriter writer;

    public GenericResponseWrapper(HttpServletResponse response) {
        super(response);
        this.cachedContent = new ByteArrayOutputStream(1024);
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (writer != null) {
            throw new IllegalStateException("getWriter() has already been called on this response.");
        }

        if (outputStream == null) {
            outputStream = new CachedServletOutputStream();
        }
        return outputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (outputStream != null) {
            throw new IllegalStateException("getOutputStream() has already been called on this response.");
        }

        if (writer == null) {
            writer = new PrintWriter(new OutputStreamWriter(cachedContent, getResponse().getCharacterEncoding()), true);
        }
        return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
        if (writer != null) {
            writer.flush();
        }
        if (outputStream != null) {
            outputStream.flush();
        }
    }

    @Override
    public void reset() {
        cachedContent.reset();
        super.reset();
    }

    @Override
    public void sendError(int sc) throws IOException {
        flushBuffer();
        super.sendError(sc);
    }

    @Override
    public void sendError(int sc, String msg) throws IOException {
        flushBuffer();
        super.sendError(sc, msg);
    }

    @Override
    public void sendRedirect(String location) throws IOException {
        flushBuffer();
        super.sendRedirect(location);
    }

    public byte[] getData() {
        flushBuffer();
        return cachedContent.toByteArray();
    }

    private class CachedServletOutputStream extends ServletOutputStream {
        @Override
        public void write(int b) throws IOException {
            cachedContent.write(b);
        }

        @Override
        public void write(byte[] b) throws IOException {
            cachedContent.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            cachedContent.write(b, off, len);
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            // 若需支持异步请求,可在此实现监听逻辑
        }
    }
}

过滤器配套修改

在你的过滤器doFilter方法中,最后要将缓存的内容写入原始响应,并设置内容长度(避免容器再次使用分块编码):

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    GenericResponseWrapper wrappedResponse = new GenericResponseWrapper((HttpServletResponse) response);
    // 执行后续过滤器和业务逻辑
    chain.doFilter(request, wrappedResponse);

    // 获取缓存的响应数据,用于指标统计
    byte[] responseData = wrappedResponse.getData();
    // 此处添加你的响应指标处理逻辑...

    // 将缓存数据写入原始响应
    HttpServletResponse originalResponse = (HttpServletResponse) response;
    // 设置内容长度,让容器直接输出完整内容,不再使用分块编码
    originalResponse.setContentLength(responseData.length);
    originalResponse.getOutputStream().write(responseData);
    originalResponse.getOutputStream().flush();
}

关键修复点说明

  1. 本地缓存响应内容:不再直接包装容器的输出流,所有响应内容先写入本地ByteArrayOutputStream,彻底避免干扰分块编码逻辑。
  2. 修正isReady()方法:返回true,确保容器的输出逻辑正常执行。
  3. 完善响应操作:重写sendError、sendRedirect等方法,确保执行前刷新缓存,避免数据丢失。
  4. 统一输出缓存内容:在过滤器最后手动写入原始响应,并指定内容长度,让容器以非分块方式输出。

内容的提问来源于stack exchange,提问作者Gourav Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 11:20:37