自定义HttpServletRequestWrapper发送请求时抛出IOException排查
问题描述
实现了带请求重定向能力的网关,新增请求体解密功能,通过自定义HttpServletRequestWrapper包装原请求,解密过程正常,但请求未到达目标服务就抛出IOException。
自定义Wrapper代码:
public class MyRequestWraper extends HttpServletRequestWrapper { public MyRequestWraper(HttpServletRequest request) { super(request); } @Override public ServletInputStream getInputStream() throws IOException { try { String decryptedPayload = decryptPayload(getRequest().getInputStream(), getRequest().getCharacterEncoding()); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedPayload.getBytes(getRequest().getCharacterEncoding())); return new ServletInputStream(){ @SuppressWarnings("unused") private ReadListener readListener = null; @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public void close() throws IOException { byteArrayInputStream.close(); } @Override public int read(byte[] b, int off, int len) throws IOException { return byteArrayInputStream.read(b, off, len); } @Override public boolean isFinished() { return byteArrayInputStream.available() > 0; } @Override public boolean isReady() { return isFinished(); } @Override public void setReadListener(ReadListener readListener) { this.readListener = readListener; if (!isFinished()) { try { readListener.onDataAvailable(); } catch (IOException e) { readListener.onError(e); } } else { try { readListener.onAllDataRead(); } catch (IOException e) { readListener.onError(e); } } } }; } catch (Exception e) { e.printStackTrace(); return super.getInputStream(); } } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }
抛出的异常:
org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: UT000128: Remote peer closed connection before all data could be read at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:243) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractJackson2HttpMessageConverter.java:201)
Wrapper已在Filter中传入chain.doFilter,尝试用byte[]替代ByteArrayInputStream仍未解决问题。
排查与修复思路
修正
isFinished()核心逻辑错误
原代码中isFinished()的逻辑完全颠倒:return byteArrayInputStream.available() > 0意味着还有数据可读时,框架会判定流已读完,直接终止后续读取操作,抛出连接关闭异常。正确逻辑应为return byteArrayInputStream.available() == 0(无剩余数据时返回true)。缓存解密后的字节数组,避免重复读取原请求流
原请求流仅能读取一次,当前代码每次调用getInputStream()都会重新读取原流解密,第二次调用时原流已空,会导致数据丢失或解密失败。建议在Wrapper初始化阶段就完成解密并缓存字节数组:public class MyRequestWraper extends HttpServletRequestWrapper { private final byte[] decryptedBytes; private final String characterEncoding; public MyRequestWraper(HttpServletRequest request) throws IOException { super(request); this.characterEncoding = request.getCharacterEncoding() != null ? request.getCharacterEncoding() : StandardCharsets.UTF_8.name(); // 一次性读取并解密原请求流 byte[] originalBytes = IOUtils.toByteArray(request.getInputStream()); String decryptedPayload = decryptPayload(new ByteArrayInputStream(originalBytes), characterEncoding); this.decryptedBytes = decryptedPayload.getBytes(characterEncoding); } @Override public ServletInputStream getInputStream() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedBytes); return new ServletInputStream() { private ReadListener readListener; @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public int read(byte[] b, int off, int len) throws IOException { return byteArrayInputStream.read(b, off, len); } @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; } @Override public boolean isReady() { // 内存流随时可读,返回true return true; } @Override public void setReadListener(ReadListener readListener) { this.readListener = readListener; if (isFinished()) { try { readListener.onAllDataRead(); } catch (IOException e) { readListener.onError(e); } } else { try { readListener.onDataAvailable(); } catch (IOException e) { readListener.onError(e); } } } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }(注:
IOUtils来自Apache Commons IO,也可自行实现流读取逻辑)完善
isReady()方法实现
原代码isReady()直接返回isFinished()的结果,会导致流未完成时框架判定流暂时不可读,触发异步读取逻辑引发异常。对于内存中的ByteArrayInputStream,isReady()应始终返回true。检查Filter执行顺序与请求流读取情况
确保解密Filter在所有可能读取请求流的Filter(如日志、认证Filter)之前执行,避免其他Filter提前读取原请求流导致数据丢失;同时确认Filter中正确调用chain.doFilter(wrapper, response),未误用原请求对象。验证解密后的数据完整性
在解密完成后,打印解密后的字符串或字节数组长度,确认数据格式正确、长度符合预期,避免因解密后数据损坏导致框架解析失败。
内容的提问来源于stack exchange,提问作者maxivis

