使用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)**传输响应时,分块的大小标识等元数据会通过这个输出流写入。但你的实现存在两个关键问题:
- 包装的输出流干扰了容器分块编码的字节传输逻辑,导致客户端接收到的分块大小不是合法的十六进制序列,触发解析错误。
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(); }
关键修复点说明
- 本地缓存响应内容:不再直接包装容器的输出流,所有响应内容先写入本地
ByteArrayOutputStream,彻底避免干扰分块编码逻辑。 - 修正
isReady()方法:返回true,确保容器的输出逻辑正常执行。 - 完善响应操作:重写
sendError、sendRedirect等方法,确保执行前刷新缓存,避免数据丢失。 - 统一输出缓存内容:在过滤器最后手动写入原始响应,并指定内容长度,让容器以非分块方式输出。
内容的提问来源于stack exchange,提问作者Gourav Kumar
相关产品推荐
相关产品推荐

