ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot 请求体 InputStream 多次读取的问题(2021.05.11)

2022-05-26 16:02:33  阅读:212  来源: 互联网

标签:11 读取 2021.05 InputStream IOException import public 请求


SpringBoot 请求体 InputStream 多次读取的问题(2021.05.11)

目录

1. 问题描述

笔者最近为一个 SpringBoot Web 项目添加日志审查,需要在请求完成后记录接口的响应时间、请求参数等信息,在请求完成后读取 RequestBody 时遇到了 java.io.IOException: Stream closed 的异常,如果改为在请求处理之前进行记录则在 controller 层无法获取到 RequestBody 的内容。

2. 原因分析

经过分析,定位原因为 Java 中 InputStream 只能读取一次,所以在请求完成之后读取时流已经关闭了,同理在请求处理之前进行读取则在请求处理时就获取不到内容了。

3. 问题解决

问题出在 InputStream 无法多次读取,所以我们只需要将 RequestBody 的内容缓存下来,之后每次获取 InputStream 时根据缓存内容返回一个新的 InputStream 对象即可。

实现思路为自定义类 MultiReadHttpServletRequest 继承 HttpServletRequestWrapper 类重写 getInputStreamgetReader 方法,使其每次根据缓存的内容返回新的 InputStream 对象,然后在过滤器中将 HttpServletRequest 对象替换为可多次读取请求体的 MultiReadHttpServletRequest 对象。

自定义类 MultiReadHttpServletRequest.java 代码如下:

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * 自定义类继承 <code>HttpServletRequestWrapper</code> 实现请求体 <code>RequestBody</code> 的多次读取.
 * <p>
 * 为了解决请求体的 <code>RequestBody</code> 无法多次读取而编写,通过重写 <code>getInputStream</code> 与
 * <code>getReader</code> 方法实现.
 *
 * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
 * @date 2021/05/06
 * @since 1.0
 */
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {

    private ByteArrayOutputStream cachedBytes;

    public MultiReadHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null) {
            cacheInputStream();
        }
        return new CachedServletInputStream();
    }

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

    /**
     * Cache the inputstream in order to read it multiple times. For
     * convenience, I use apache.commons IOUtils.
     *
     * @throws IOException IOException
     * @author xiaoqqya
     * @date 2021/05/06
     */
    private void cacheInputStream() throws IOException {
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    /**
     * An inputstream which reads the cached request body.
     *
     * @author xiaoqqya
     * @date 2021/05/06
     */
    public class CachedServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream input;

        public CachedServletInputStream() {
            // create a new input stream from the cached request body
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }

        @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 input.read();
        }
    }
}

过滤器 MultiReadHttpServletRequestFilter.java 代码如下:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 请求体过滤器.
 * <p>
 * 将请求体由 <code>HttpServletRequest</code> 类型替换为自定义 <code>MultiReadHttpServletRequest</code>
 * 类型,实现请求体 <code>RequestBody</code> 的多次读取.
 *
 * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
 * @date 2021/05/06
 * @since 1.0
 */
@WebFilter(
        filterName = "MultiReadHttpServletRequestFilter",
        urlPatterns = "/api/*"
)
public class MultiReadHttpServletRequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest multiReadRequest = null;
        if (servletRequest instanceof HttpServletRequest) {
            multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) servletRequest);
        }
        if (multiReadRequest == null) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            filterChain.doFilter(multiReadRequest, servletResponse);
        }
    }
}

参考文章:Http Servlet request lose params from POST body after read it once - Stack Overflow

标签:11,读取,2021.05,InputStream,IOException,import,public,请求
来源: https://www.cnblogs.com/xiaoQQya/p/16313751.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有