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

自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:30:59