springboot请求request只读取一次问题
在springboot项目中,我们会遇到将请求写入日志的操作,但是在实际的项目中,当你在filter或者拦截器中获取request之后进行读取数据,就会发现后续的controller将会
miss request body
,那到底应该怎么做呢?
1.springboot中request的请求执行顺序
请求 -> filter -> servlet -> interceptor -> aop -> controller
只要在controller前读取过一次请求流,那么后面就再拿不到请求参数了。
2.原因分析
当我们调用getInputStream()方法获取输入流时,得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承InputStream。
查看InputStream的源码可以看到(这里就不贴代码,大家有兴趣可以去找具体的源码部分),读取流的时候会根据position来获取当前位置,每读取一次,该标志就会移动一次,如果读到最后,read()返回-1,表示已经读取完了。如果想要重新读取,可以调用inputstream.reset方法,但是能否reset取决于markSupported方法,返回true可以reset,反之则不行。查看ServletInputStream可知,这个类并没有重写markSupported和reset方法。
综上,InputStream默认不实现reset方法,而ServletInputStream也没有重写reset相关方法,这样就无法重复读取流,这就是我们从request对象中输入流只能读取一次的原因。
3.解决办法
在filter中将流读取出来然后进行存储,方便后续读取使用
BodyReaderHttpServletRequestWrapper
它是一个http请求包装器,其基于装饰者模式实现了HttpServletRequest界面。
我们定义一个容器,将输入流里面的数据存储到这个容器里,然后我们重写getInputStream方法,每次都从这个容器里读数据,这样我们的输入流就可以读取任意次了。
import org.springframework.util.StreamUtils;
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;
/**
* <p>项目名称: zzw-tech </p>
* <p>包名称: com.zzw.mis.framework.security.filter </p>
* <p>描述: </p>
* <p> </p>
* <p>创建时间: 2022/12/26 17 </p>
*
* @author coco
* @version v1.0
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;//用于将流保存下来
public BodyReaderHttpServletRequestWrapper (HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream () throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read () throws IOException {
return bais.read();
}
@Override
public boolean isFinished () {
return false;
}
@Override
public boolean isReady () {
return false;
}
@Override
public void setReadListener (ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader () throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
AuthenticationTokenFilter处理
@Override
protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
try {
String contentType = request.getContentType();
if (StrUtil.isNotBlank(contentType) && contentType.contains(ContentType.MULTIPART.getValue())) {//multipart/form-data类型
//spring中使用MultipartResolver处理文件上传,所以这里需要将其封装往后传递
MultipartResolver resolver = new StandardServletMultipartResolver();
MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
chain.doFilter(multipartRequest, responseWrapper);
} else {
//对于其他的情况,我们统一使用包装类,将请求流进行缓存到容器
BodyReaderHttpServletRequestWrapper cachedBodyHttpServletRequest = new BodyReaderHttpServletRequestWrapper(request);
// TODO do somethings
/*
……
*/
chain.doFilter(cachedBodyHttpServletRequest, responseWrapper);
}
} finally {
//读取完 Response body 之后,通过这个设置回去,就可以使得接口调用者可以正常接收响应了,否则会产生空响应的情况
//注意要在过滤器方法的最后调用
responseWrapper.copyBodyToResponse();
}
}
后续拦截器日志使用请求体参数
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String param = "";
if (isJson(request)) {
param = getParamsFromRequestBody(request);
}
RequestLog requestLog = new RequestLog();
Integer userId = RequestUtils.getUserId(request);
String uri = request.getRequestURI();
requestLog.setUserId(userId);
requestLog.setReqIp(IpUtil.getIpAddress(request));
requestLog.setReqParam(param);
requestLog.setReqUri(uri);
requestLog.setReqTime(new Date());
request.setAttribute(LOGGER_ENTITY, requestLog);
return true;
}
/**
* 获取请求体内容
* @return
* @throws IOException
*/
private String getParamsFromRequestBody(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder builder = new StringBuilder();
try {
String line = null;
while((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 判断是不是json格式的请求
*/
private boolean isJson(HttpServletRequest request) {
if (request.getContentType() != null) {
return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
return false;
}
- 本文标签: Java Spring Boot
- 本文链接: https://blog.wangqi2020.top/article/54
- 版权声明: 本文由王祁原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权