请求中多次调用getInputStream
后续获取为空的问题
原因
在处理HTTP请求时,HttpServletRequest
对象的 getInputStream
方法只能被调用一次。这是因为请求的输入流(InputStream
)是一个一次性资源,一旦被读取,就不能再次读取。因此,如果你在同一个请求中多次调用 getInputStream
,后续的调用将会返回一个空的输入流或者抛出异常。
解决方案
解决方案如下:
思路为利用过滤器对请求进行缓存,需要自定义类继承HttpServletRequestWrapper
并实现缓存逻辑
自定义RequestWrapper类
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
private final Map<String, MultipartFile> fileMap = new HashMap<>();
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
if (resolver.isMultipart(request)) {
MultipartHttpServletRequest multipartHttpServletRequest = resolver.resolveMultipart(request);
Collection<MultipartFile> files = multipartHttpServletRequest.getFiles("file");
for (MultipartFile file : files) {
fileMap.put(file.getName(), file);
}
}
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 inputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
};
}
}
自定义过滤器
import club.tgucsdn.common.pojo.RequestWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(filterName = "streamFilter",urlPatterns = "/*")
@ConditionalOnClass({MybatisPlusInterceptor.class, BaseMapper.class})
public class RequestStreamFilter implements Filter {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
}
随后在后续需要的地方可以使用getInputStream
方法,其返回值将不在为null
解释
自定义的RequestWrapper
中存放两个对象
-
body
缓存请求的原始内容
-
fileMap
存储从多部分请求(multipart request)中解析出来的文件。具体来说,
fileMap
是一个Map<String, MultipartFile>
,用于将文件名与对应的MultipartFile
对象关联起来。这样可以在后续的处理中方便地访问这些文件
fileMap
属性的具体用途
- 存储文件:
- 当请求是多部分请求时,
StandardServletMultipartResolver
会解析请求中的文件部分,并将这些文件存储在fileMap
中。 fileMap
的键是文件的名称(即表单字段的名称),值是MultipartFile
对象,表示上传的文件。
- 当请求是多部分请求时,
- 方便后续访问:
- 通过
fileMap
,你可以在后续的处理逻辑中通过文件名快速访问到上传的文件。例如,你可以从fileMap
中获取某个特定的文件,进行进一步的处理,如保存到文件系统、读取文件内容等。
- 通过
body
属性的具体用途
-
允许多次读取请求体:
HTTP请求的输入流(
InputStream
)是一次性的资源,一旦被读取,就不能再次读取。通过将请求体的内容缓存到body
数组中,可以在后续的处理中多次读取请求体的内容,而不会导致输入流耗尽。 -
提供一致的请求体访问:
通过缓存请求体内容,可以确保在不同的处理阶段或不同的方法调用中,请求体的内容保持一致。这对于需要多次访问请求体的场景非常有用,例如在日志记录、安全检查、业务逻辑处理等过程中。
-
支持
getInputStream
和getReader
方法的重写RequestWrapper
类重写了getInputStream
和getReader
方法,返回基于body
数组的新输入流和读取器。这样可以确保每次调用这些方法时都能获得完整的请求体内容,而不会因为输入流已被读取而返回空或抛出异常。