栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【SpringBoot】68、SpringBoot解决HttpServletRequest中输入流不能重复读的问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【SpringBoot】68、SpringBoot解决HttpServletRequest中输入流不能重复读的问题

Java 中的输入流 InputStream 的 read 方法中有一个标志位 postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,InputStream.read方法会返回-1,标志已经读取完了,如果想再次读取,可以调用inputstream.reset方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。

1、原因分析

HttpServletRequest 中的 getInputStream() 方法返回的是 ServletInputStream 对象,此对象中并没有重写
InputStream 的 reset() 方法,默认的 InputStream 中的 reset() 方法如下:

public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
}

public boolean markSupported() {
    return false;
}

所以在 request.getInputStream() 读取一次之后,postion 移动到了末尾,第二次就读取不到数据,由于无法 reset(),所以,request.getInputStream() 只能读取一次。

而在 SpringBoot 中,我们常用的参数传递的方式有两种,一种是跟在 url 后面,例如:

http://www.xxxx.com?id=1

第二种,我们是将参数传递在 body 中,在 controller 层,我们使用 @RequestBody 获取参数,这种方式实质上就是使用 request.getInputStream() 方式读取的

2、场景分析

上述原因分析中,我们知道 HttpServletRequest 中的输入流对象只能读取一次,我们常有的时候需要在请求前拦截器处或者请求后返回时,异常处理时保存操作日志,异常日志等,就需要获取请求的参数,这就导致了重复读取输入流对象会报异常

3、解决方案

由于过滤器的执行顺序在最前,如图所示:

我们定义一个过滤器,在最外层将 HttpServletRequest 对象封装一下,代码如下:

import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;


@Component
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "
    public static class RequestWrapper extends HttpServletRequestWrapper {

        
        private byte[] body;

        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            // 将body数据存储起来
            body = StreamUtils.copyToByteArray(request.getInputStream());
        }

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

        @Override
        public ServletInputStream getInputStream() throws IOException {
            // 创建字节数组输入流
            final ByteArrayInputStream bais = new ByteArrayInputStream(body);

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

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

                @Override
                public void setReadListener(ReadListener readListener) {

                }

                @Override
                public int read() throws IOException {
                    return bais.read();
                }
            };
        }
    }
}

我们定义了一个 HttpServletRequestFilter 对象,也就是 HttpServletRequest 的一个包装器,将 body 中的输入流存储起来,使得 request.getInputStream() 可以重复读

4、文件上传问题

上述将 HttpServletRequest 进行包装之后,文件上传功能会受影响,查询资料之后,需要加一个判断条件

4.1 定义文件上传请求类型
private static final String CONTENT_TYPE = "multipart/form-data";
4.2 过滤方法优化
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ServletRequest requestWrapper = null;
    // http请求才会被过滤
    if (servletRequest instanceof HttpServletRequest) {
        // 转换http请求对象
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 文件上传请求
        if (request.getContentType() != null && request.getContentType().contains(CONTENT_TYPE)) {
            requestWrapper = new StandardServletMultipartResolver().resolveMultipart(request);
        }
        // 普通请求
        else {
            requestWrapper = new RequestWrapper(request);
        }
    }
    // 需要过滤
    if (null != requestWrapper) {
        filterChain.doFilter(requestWrapper, servletResponse);
    }
    // 不需要过滤
    else {
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

我们判断当前请求是否为文件上传请求,对其做特殊处理

5、Druid 登录问题

在进行 HttpServletRequest 包装之后,会出现 druid 登录失败的问题,这个问题困扰了我好久,经查证为该过滤器的问题,我们放开 druid 的路径过滤即可

5.1 定义排除路径
private static final String DRUID_PATHS = "/druid
public static boolean matches(String url1, String url2) {
    return new PathPatternParser().parse(url1).matches(PathContainer.parsePath(url2));
}

这样就解决了 druid 的登录失败问题

如您在阅读中发现不足,欢迎留言!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/988951.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号