
HttpSevletRequest Body信息不能被多次读取的问题
在 Java Web 开发中,HTTP 请求体是客户端向服务器发送数据的主要载体,例如表单提交、JSON 数据等。当服务器收到请求后,通常通过 HttpServletRequest
类获取请求体的内容。然而,HTTP 请求体通常只能被读取一次。这是因为请求体使用输入流(InputStream
)进行读取,一旦读取完毕,流的位置就会到达流的末尾,无法再读取。因此,在开发中,如果不小心读取了请求体一次,后续的代码将无法访问请求体数据,从而导致数据丢失或者异常。
1. 问题背景
HTTP 请求体的读取限制
HTTP 请求体(如 POST 请求中的 JSON 数据或表单数据)是通过输入流(InputStream
)传输的。Servlet 容器在接收到请求时,会将输入流读取并解析到内存中,然后进行后续的处理。但是,HTTP 请求体的流一次性读取特性意味着:
- 只能读取一次:一旦读取完请求体的数据,流的位置就会指向末尾,无法再次读取同一数据。
- 重复访问时失败:如果在请求处理过程中,多个组件或方法需要访问请求体数据,但该数据已经被读取过一次,那么后续的读取操作将无法成功,通常会抛出异常或返回空数据。
这种问题在需要多次读取请求体的场景中尤为明显。例如,在拦截器或过滤器中读取请求体数据时,后续的业务处理方法(如 Controller)可能无法再访问请求体。
2. 问题产生的场景
以下是一些常见的导致请求体只能读取一次的场景:
- 使用 Servlet 读取请求体:当使用
HttpServletRequest
获取请求体时,流会被消耗,无法再次获取。 - Spring MVC 中的 @RequestBody:Spring MVC 提供了
@RequestBody
注解,用于直接将请求体映射为 Java 对象。但一旦请求体被映射,流就会被消耗,导致后续无法再访问请求体数据。 - 自定义过滤器或拦截器:在一些需要多次读取请求体的场景下,过滤器或拦截器读取请求体后,后续代码无法再次访问请求体数据。
3. 解决方案
使用 HttpServletRequestWrapper
包装请求体
通过创建一个自定义的 HttpServletRequestWrapper
来缓存请求体的数据,并提供多次读取的能力。这种方式允许请求体数据在第一次读取时被缓存,后续的请求处理流程可以从缓存中读取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package io.github.wj0410.zcscloud.framework.web.core.filter;
import cn.hutool.extra.servlet.ServletUtil;
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; 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;
public class CacheRequestBodyWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public CacheRequestBodyWrapper(HttpServletRequest request) { super(request); body = ServletUtil.getBodyBytes(request); }
@Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); }
@Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); return new ServletInputStream() {
@Override public int read() { return inputStream.read(); }
@Override public boolean isFinished() { return false; }
@Override public boolean isReady() { return false; }
@Override public void setReadListener(ReadListener readListener) {}
@Override public int available() { return body.length; }
}; }
}
|
创建过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package io.github.wj0410.zcscloud.framework.web.core.filter;
import cn.hutool.core.util.StrUtil; import org.springframework.http.MediaType; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
public class CacheRequestBodyFilter extends OncePerRequestFilter {
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(new CacheRequestBodyWrapper(request), response); }
@Override protected boolean shouldNotFilter(HttpServletRequest request) { return !isJsonRequest(request); } public static boolean isJsonRequest(ServletRequest request) { return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE); } }
|
自动配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package io.github.wj0410.zcscloud.framework.web.config;
import io.github.wj0410.zcscloud.framework.common.enums.WebFilterOrderEnum; import io.github.wj0410.zcscloud.framework.web.core.filter.CacheRequestBodyFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Filter;
@Configuration public class WebConfiguration implements WebMvcConfigurer {
@Bean public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() { return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER); } public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) { FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter); bean.setOrder(order); return bean; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package io.github.wj0410.zcscloud.framework.common.enums;
public interface WebFilterOrderEnum {
...
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
...
}
|