springboot请求体中的流只能读取一次的问题 httpServletRequest中的流只能读取一次的原因 springboot-拦截器-过滤器-Required request body is

场景交代

在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证。当参数在url后面时(queryString)获取参数进行验证之后程序正常运行。但是,当请求参数在请求体中的时候,通过流的方式将请求体取出参数进行验证之后,发现后续流程抛出错误:

 
 Required request body is missing ...

经过排查,发现ServletInputStream的流只能读取一次(参考:httpServletRequest中的流只能读取一次的原因)。

这就是为什么在拦截器中读取消息体之后,controller的@RequestBody注解无法获取参数的原因。

解决思路

既然知道了原因,那就可以想到一个大概思路了:可不可以把请求的body流换成可重复读的流?

答案是可以的。可以通过继承HttpServletRequestWrapper类进行。

解决方案

1. 继承HttpServletRequestWrapper

继承HttpServletRequestWrapper类,将请求体中的流copy一份出来,覆写getInputStream()和getReader()方法供外部使用。如下,每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。

 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
  
 import javax.servlet.ReadListener;
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
  
 import org.springframework.util.StreamUtils;
  
 /**
 *
 * 从请求体中获取参数请求包装类:<br>
 * @author nick
 * @version 5.0 since 2018年9月5日
 */
 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()));
 }
 }
  

2. 替换原始request对象

现在可重复读取流的请求对象构造好了,但是需要在拦截器中获取,就需要将包装后的请求对象放在拦截器中。由于filter在interceptor之前执行,因此可以通过filter进行实现。

创建filer,在filter中对request对象用包装后的request替换。

 
 import java.io.IOException;
  
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.annotation.WebFilter;
 import javax.servlet.http.HttpServletRequest;
  
 import com.znz.dns.controller.interceptor.auth.BodyReaderHttpServletRequestWrapper;
  
 @WebFilter(filterName="bodyReaderFilter",urlPatterns="/*")
 public class BodyReaderFilter implements Filter{
  
 @Override
 public void init(FilterConfig filterConfig) throws ServletException {
 // do nothing
 }
  
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
 throws IOException, ServletException {
 ServletRequest requestWrapper=null;
 if(request instanceof HttpServletRequest) {
 requestWrapper=new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request);
 }
 if(requestWrapper==null) {
 chain.doFilter(request, response);
 }else {
 chain.doFilter(requestWrapper, response);
 }
  
 }
  
 @Override
 public void destroy() {
 // do nothing
  
 }
  
 }
  

配置filter

 
 @Bean
 public FilterRegistrationBean<BodyReaderFilter> Filters() {
 FilterRegistrationBean<BodyReaderFilter> registrationBean = new FilterRegistrationBean<BodyReaderFilter>();
 registrationBean.setFilter(new BodyReaderFilter());
 registrationBean.addUrlPatterns("/*");
 registrationBean.setName("koalaSignFilter");
 return registrationBean;
 }

3. 获取请求体

既然request对象流已经换成了wrapper reqest,那么流就可以重复读取了。接下来就是获取。

在拦截器中,直接从request中获取流,并进行读取:

 
 /**
 * 获取请求体内容
 * @return
 * @throws IOException
 */
 private Map<String, Object> 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);
 }
 String bodyString = builder.toString();
 return objectMapper.readValue(bodyString, Map.class);
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 try {
 reader.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 return new HashMap<>();
 }

补充

在网上找了一些关于“springboot请求体中流不可重复读取问题”的相关文章,解决了自己的问题,但是觉得整个逻辑不怎么清晰(可能是自己没理解?) 因此,根据自己的思路重新整理了一遍。

参考文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万事俱备,就差一个程序员了

谢谢您,赏俺根辣条,尝尝鲜.谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值