请求的流只能读取1次的问题

参照:request.getInputStream()输入流只能读取一次问题

问题

  • 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;
  • InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

因此,当调用了一次getInputStream(),并且将此输入流读取完后,后面再去调用getInputStream(),读取的数据都为空,导致异常:java.io.IOException: Stream closed。

分析

request的inputStream返回的是CoyoteInputStream,该输入流将读取操作实质上是委托给了InputBuffer来实现,而在InputBuffer中,会判断流是否已关闭,如果已关闭,那么将会抛出java.io.IOException: Stream closed的异常。那么流为什么会关闭呢?

这个流不会主动关闭。但是如果在项目里面使用@RequestBody注解,那么jackson在解析json时,读取完流之后,就会关闭这个流,注意是jackson去关闭的这个输入流,一旦关闭这个输入流,inputBuffer里的close属性就置为了true,就会抛出这个异常。已经解释了为什么会抛出这个异常。

但是还有个问题没解决,就是这个CoyoteInputStream输入流,它没有提供reset()方法,也就是读完之后,你再去读它,始终都会返回-1,表示没有数据了,全都读完了。

所以要实现这个输入流的复用,我们首先要拿到这个流,然后把流全部读完,将读取的数据都先存起来,然后再去使用这个读取的数据,并且我们可以使用ByteArrayInputStream将读取的字节转化成输入流,这个字节输入流支持reset方法,下次我们再拿流的的时候,可以调用reset方法,重置下,就可以继续读取了。

解决

请注意:在以下示例中,如果你是在Filter中就把流全都读了一遍的话,因为CoyoteInputStream不支持reset,这个输入流就已经废了,后面再使用@RequestBody注解(内部使用了jackson读取流),就读取不到任何内容了

   @PostMapping("test01")
    @ResponseBody         // 使用@RequestBody会读取一次流, 并且在读取完毕后, 会关闭此流
    public Object test01(@RequestBody Person person,HttpServletRequest request) throws IOException {
        System.out.println(person);
        request.getInputStream().reset(); // 重置输入流, 可再次读取该流
        InputStreamReader isReader = new InputStreamReader(request.getInputStream());
        BufferedReader bReader = new BufferedReader(isReader);
        String line = null;
        while ((line = bReader.readLine()) != null) {
            System.out.println(line);
        }
        request.getInputStream().close();
        bReader.close();
        return "ok";
    }

在这里插入图片描述
在这里插入图片描述

InputStreamFilter

在springboot中,标记为组件的Filter将会自动被识别到,添加到tomcat容器中作为过滤器

@Component
public class InputStreamFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        MyServletRequest req = new MyServletRequest(servletRequest);
        filterChain.doFilter(req,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

MyServletRequest

自定义ServletRequest包装原始的ServletRequest,重写它的getInputStream方法

@Slf4j
public class MyServletRequest extends HttpServletRequestWrapper {

    private final ByteArrayInputStream bais;

    public MyServletRequest(HttpServletRequest request) {
        super(request);
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            StreamUtils.copy(request.getInputStream(), baos);
            byte[] bodyBytes = baos.toByteArray();
            bais = new ByteArrayInputStream(bodyBytes);
        } catch (IOException e) {
            log.error("获取流数据,发生错误");
            throw new RuntimeException();
        }

    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
    	new ByteArrayInputStream()
        ServletRequest request = super.getRequest();
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                try {
                    return request.getInputStream().isFinished();
                } catch (IOException e) {
                    return false;
                }
            }

            @Override
            public boolean isReady() {
                try {
                    return request.getInputStream().isReady();
                } catch (IOException e) {
                    return false;
                }
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public synchronized void reset() throws IOException {
                bais.reset();
            }

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

方式二(推荐)

可以再加一个判断,如果前端传过来的content-type是json的话,那就替换,不是的话,就不替换

@Slf4j
public class MyServletRequest extends HttpServletRequestWrapper {

    private byte[] bodyBytes;

    public MyServletRequest(HttpServletRequest request) {
        super(request);
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            StreamUtils.copy(request.getInputStream(), baos);
            bodyBytes = baos.toByteArray();
        } catch (IOException e) {
            log.error("获取流数据,发生错误");
            throw new RuntimeException();
        }

    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 每次获取输入流的时候, 都返回一个新的输入流
        ByteArrayInputStream bais = new ByteArrayInputStream(bodyBytes);

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

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

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
}
 @PostMapping("test01")
 @ResponseBody        // 这样,我们同时使用2个@RequestBody都没关系了
 public Object test01(@RequestBody Person person,
                      @RequestBody Person person2) throws IOException {
     System.out.println(person);
     System.out.println(person2);
     return "ok";
 }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值