商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > springboot解决过滤器HttpServletRequestbodyIO流只能读取一次的问题

springboot解决过滤器HttpServletRequestbodyIO流只能读取一次的问题

  发布于2026-07-01 阅读(0)

扫一扫,手机访问

问题

做过过滤器的朋友都知道,想在过滤器中拿到请求参数,这事儿其实有点讲究。URL上拼接的参数倒是好说,直接调一下request.getParameterMap()就能搞定。但body体里的内容就没这么简单了——request里提供了一个getInputStream()方法,问题在于,这个流是一次性的。如果你在过滤器中把它读出来了,那到了控制层那边,就什么都拿不到了。

springboot解决过滤器HttpServletRequestbodyIO流只能读取一次的问题

错误方式:直接读取原始流打印(导致控制器无法获取)

看一个典型错误做法:在过滤器中直接调用原始请求的 getInputStream()getReader() 来打印body。表面上看没问题,但流一旦被读取就会被消耗,等传到控制器那边,数据早就没了。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    // 直接读取原始流并打印(错误!)
    String body = IOUtils.toString(httpRequest.getInputStream(), StandardCharsets.UTF_8);
    System.out.println("Filter打印body:" + body);
    // 此时流已被消耗,控制器无法读取
    chain.doFilter(request, response); 
}

方案

问题的根源其实很清楚:输入流的“一次性”特性。

ServletRequest 提供的输入流(getInputStream() / getReader())是单向流动的字节流,默认情况下,一旦你读了(比如在过滤器中打印),流的指针就会移动到末尾。等后面控制器再来读,能拿到的自然就是空数据了。

所以核心不在于“是否在过滤器中打印”,而在于“打印时有没有消耗原始流,并且没有做缓存”。

正确方式:通过包装类缓存后打印(控制器可正常获取)

正确的思路是这样的:先通过 HttpServletRequestWrapper 把流数据缓存起来,然后在过滤器中读取缓存的数据来打印,这样原始流就不会被消耗,控制器那边也能通过包装类的缓存正常读取。

过滤器内容

@Component
@0rder(o)
@Slf4j
public class WebFilter extends OncePerRequestFilter {

@0verride
protected void dofilterInternal(HttpservletRequest request, HttpservletResponse response, Filterchain filterchain)throws servletException, I0Exception{
// 正确示例:基于包装类的缓存打印,不消耗原始流
    if (request instanceof HttpServletRequest) {
        // 先包装请求,缓存body
        CachedServletRequestWrapper wrappedRequest = new CachedServletRequestWrapper((HttpServletRequest) request);
        // 从包装类的缓存中读取并打印(不影响原始流)
        String body = new String(wrappedRequest.getCachedBody(), StandardCharsets.UTF_8);
        System.out.println("Filter打印body:" + body);
        // 传递包装后的请求,控制器可从缓存中读取
        chain.doFilter(wrappedRequest, response);
    } else {
        chain.doFilter(request, response);
    }
}

CachedServletRequestWrapper.ja va

import ja vax.servlet.ReadListener;
import ja vax.servlet.ServletInputStream;
import ja vax.servlet.http.HttpServletRequest;
import ja vax.servlet.http.HttpServletRequestWrapper;
import ja va.io.ByteArrayInputStream;
import ja va.io.ByteArrayOutputStream;
import ja va.io.IOException;
import ja va.io.InputStream;
import ja va.nio.charset.StandardCharsets;

/**
 * 缓存请求体的包装类,解决ServletRequest输入流只能读取一次的问题
 */
public class CachedServletRequestWrapper extends HttpServletRequestWrapper {

    // 缓存的请求体字节数组
    private final byte[] cachedBody;

    /**
     * 构造方法:读取原始请求的输入流并缓存
     */
    public CachedServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = readRequestBody(request);
    }

    /**
     * 读取原始请求的输入流并转换为字节数组
     */
    private byte[] readRequestBody(HttpServletRequest request) throws IOException {
        // GET/DELETE等方法通常没有请求体,直接返回空数组
        String method = request.getMethod();
        if ("GET".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
            return new byte[0];
        }

        // 读取输入流并缓存
        try (InputStream inputStream = request.getInputStream();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            return outputStream.toByteArray();
        }
    }

    /**
     * 重写getInputStream(),返回基于缓存的输入流
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.a vailable() == 0;
            }

            @Override
            public boolean isReady() {
                return true; // 同步读取时始终就绪
            }

            @Override
            public void setReadListener(ReadListener listener) {
                // 异步场景可忽略,同步场景无需实现
            }
        };
    }

    /**
     * 重写getReader(),基于缓存的输入流构建字符读取器
     */
    @Override
    public ja va.io.BufferedReader getReader() throws IOException {
        return new ja va.io.BufferedReader(
            new ja va.io.InputStreamReader(getInputStream(), StandardCharsets.UTF_8)
        );
    }

    /**
     * 提供获取缓存的请求体字节数组的方法(方便过滤器/拦截器直接使用)
     */
    public byte[] getCachedBody() {
        return cachedBody;
    }

    /**
     * 提供获取缓存的请求体字符串的方法(简化使用)
     */
    public String getCachedBodyAsString() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }
}

从运行结果来看,body数据已经被正常打印了。

springboot解决过滤器HttpServletRequestbodyIO流只能读取一次的问题

本文转载于:https://www.jb51.net/program/364141wi2.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注