coding++:拦截器拦截requestbody数据如何防止流被读取后数据丢失
1):现在开发的项目是基于SpringBoot的maven项目,拦截器的使用很多时候是必不可少的,当有需要需要你对body中的值进行校验,例如加密验签、防重复提交、内容校验等等。
2):当你开开心心的在拦截器中通过 request.getInputStream(); 获取到body中的信息后。
你会发现你在 controlle r中使用了 @RequestBody 或者 再次 request.getInputStream() 获取数据时报错了,流数据丢失了。
3):@RequestBody 只能以流的方式读取,流被读过一次后,就不在存在了,会导致会续无法处理,因此不能直接读流。
I/O error while reading input message; nested exception is java.io.IOException: Stream closed org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:229) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:128) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
为了解决这个问题,思路如下:
1、读取流前先把流保存一下
2、使用过滤器拦截读取,再通过chain.doFilter(wrapper, response);将保存的流丢到后面程序处理
最简单的方案就是 先读取流,然后在将流写进去就行了
1):创建 HttpHelper.java 用于读取Body
package mlq.pic.filter.requestbodyfilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; /** * @author MaLQ * @Description: 用于读取Body * @date 2020年3月19日14:38:12 */ public class HttpHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); public static String getBodyString(HttpServletRequest request) throws IOException { LOGGER.info("开始读取requestBody数据..."); StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } LOGGER.info("结束读取requestBody数据...body={}", sb.toString()); return sb.toString(); } }
2):创建 RequestReaderHttpServletRequestWrapper.java 用于 requestBody 数据流处理
package mlq.pic.filter.requestbodyfilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * @author MaLQ * @Description: requestBody 数据流处理 * @date 2020年3月19日14:38:12 */ public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final Logger LOGGER = LoggerFactory.getLogger(RequestReaderHttpServletRequestWrapper.class); private final byte[] body; public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); 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) { } }; } }
3):创建 HttpServletRequestReplacedFilter.java 过滤器
package mlq.pic.filter.requestbodyfilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @author MaLQ * @Description: 过滤器拦截 * @date 2020年3月19日14:38:12 */ public class HttpServletRequestReplacedFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(HttpServletRequestReplacedFilter.class); @Override public void init(FilterConfig arg0) throws ServletException { LOGGER.info("HttpServletRequestReplacedFilter...init"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { LOGGER.info("HttpServletRequestReplacedFilter...doFilter"); ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request); } //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。 // 在chain.doFiler方法中传递新的request对象 if (requestWrapper == null) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void destroy() { LOGGER.info("HttpServletRequestReplacedFilter...destroy"); } }
4):最后我们只需要在 Application.java 启动时加上如下代码注入过滤器即可
package mlq.pic.config; import mlq.pic.filter.requestbodyfilter.HttpServletRequestReplacedFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfigurerConfig { @Bean public FilterRegistrationBean httpServletRequestReplacedRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new HttpServletRequestReplacedFilter()); registration.addUrlPatterns("/*"); registration.addInitParameter("paramName", "paramValue"); registration.setName("httpServletRequestReplacedFilter"); registration.setOrder(1); return registration; } }
5):获取@RequestBody 里面的数据(在HttpServletRequest里是以流的方式传输的)
/** * @author MaLQ * @Description: 获取@RequestBody 里面的数据(在HttpServletRequest里是以流的方式传输的) * @date 2020年3月19日13:39:31 */ public static Object getBodyParam(HttpServletRequest request,Class cls) { ServletInputStream inputStream = null; try { inputStream = request.getInputStream(); return JSONObject.parseObject(inputStream, Charset.forName("UTF-8"), cls); } catch (Exception e) { LOGGER.error("获取@RequestBody数据转换异常:error={}" + e.getMessage(), e); } return null; } // 案例 WxAlbumDetailVo detailVo = (WxAlbumDetailVo) RequestUtils.getBodyParam(request, WxAlbumDetailVo.class);
以上操作就可以解决 requestbody 数据获取一次丢失问题。