关于servlet inputstream stream closed异常,以及HttpServletRequestWrapper 上传文件的问题

项目场景:

现有需求要提供出一些接口,对第三方使用,在对接口签名的过程中出现了问题。

问题描述:

通过AOP获得请求中@RequestBody数据时,出现 "stream closed"异常的问题。

java.io.IOException: Stream closed

原因分析:

在进入AOP前,spring框架在使用 对象类型转换器(converter)时,已经使用过一次body了(获取body是以流的形式),所以在AOP切面处理时,想获取body体数据,发生"stream closed"异常。

 

spring源码中的核心方法 :

readWithMessageConverters()

代码片段
在这里插入图片描述
感兴趣的同学可以断点试一下,第一次断点进入时,这里是正常的;可以观察一下这段代码和AOP的执行顺序。


解决方案:

自己封装一个新的request

public class RequestWrapper extends HttpServletRequestWrapper {
    /**
     * 存储body数据的容器
     */
    private final byte[] body;


    public  RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);


        // 将body数据存储起来
        String bodyStr = getBodyString(request);
        body = bodyStr.getBytes(Charset.defaultCharset());
    }


    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final ServletRequest request) throws IOException {
            return inputStream2String(request.getInputStream());
    }


    /**
     * 获取请求Body
     *
     * @return String
     */
    public String getBodyString() throws IOException {
        final InputStream inputStream = new ByteArrayInputStream(body);


        return inputStream2String(inputStream);
    }


    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream) throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;


        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    }


    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {


        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);


        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }


            @Override
            public boolean isFinished() {
                return false;
            }


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


            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
}
/**
* @author 签名校验过滤器
*/
@Slf4j
public class SignStreamFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        String contentType = req.getContentType();
        String method = "multipart/form-data";

            if (contentType != null && contentType.contains(method)) {
            // 将转化后的 request 放入过滤链中
            request = new StandardServletMultipartResolver().resolveMultipart(request);
        }

        // 扩展request,使其能够能够重复读取requestBody
        ServletRequest requestWrapper = new RequestWrapper(request);
        // 这里需要放行,但是要注意放行的 request是requestWrapper
        chain.doFilter(requestWrapper, resp);

    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }

}
@Configuration
public class SignAutoConfiguration {

    @Bean
    public SignStreamFilter initSignFilter() {
        return new SignStreamFilter();
    }

    @Bean
    public FilterRegistrationBean webAuthFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(initSignFilter());
        registration.setName("signFilter");
        registration.addUrlPatterns("/*");
        registration.setOrder(0);
        return registration;
    }
}

再放两张debug图

在这里插入图片描述
在这里插入图片描述
第一张是原生请求的request对象,第二张是针对inputstream流只能用一次封装的request对象,可以看出封装的request中多出了"body"属性。


PS : 上面的方法有一个坑点,之前上面解决了"stream closed"的后,如果有使用到上传文件功能(multipart)一样会出现异常。在方法中已经解决;主要思路是,request进入过滤器时,判断类型如果是multipart类型,则将request手动封装为multipart类型。

   HttpServletRequest request = (HttpServletRequest) req;
        String contentType = req.getContentType();
        String method = "multipart/form-data";
            if (contentType != null && contentType.contains(method)) {
            // 将转化后的 request 放入过滤链中
            request = new StandardServletMultipartResolver().resolveMultipart(request);
        }
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值