SpringBoot之HandlerInterceptor拦截器的使用 ——(三)@RequestBody获取请求参数解决java.io.IOException: Stream closed

清疚 2022-05-16 11:52 108阅读 0赞

现在开发的项目是基于SpringBoot的maven项目,拦截器的使用很多时候是必不可少的,当有需要需要你对body中的值进行校验,例如加密验签、防重复提交、内容校验等等。
当你开开心心的在拦截器中通过request.getInputStream();获取到body中的信息后,你会发现你在controller中使用了@RequestBody注解获取参数报如下错误

  1. I/O error while reading input message; nested exception is java.io.IOException: Stream closed
  2. org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
  3. at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:229)
  4. at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150)
  5. at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:128)
  6. at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
  7. at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
  8. at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
  9. at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
  10. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
  11. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
  12. at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
  13. at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
  14. at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
  15. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
  16. at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
  17. at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
  18. at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
  19. at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
  20. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  21. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  22. at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
  23. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  24. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  25. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  26. at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96)
  27. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  28. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  29. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  30. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  31. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  32. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  33. at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
  34. at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
  35. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  36. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)

IO流关闭只能读取一次,接下来我们就开始解决这个BUG,有两种大的方向:

  • 这个需求咱不做了,特么有本事就把东西都放在请求头里面传过来;
  • 解决流只能读取一次的问题,让它可以被多次重复读取;

考虑到生活还要继续,妻儿老小在家等着我回去告诉他们晚上吃冷面还是鸡,要这需求不给搞定那也只能去打包收拾东西滚蛋,回家路上顺便卖点血换钱了,我选择了第二种

最简单的方案就是 先读取流,然后在将流写进去就行了

引入包

  1. <dependency>
  2. <groupId>org.apache.tomcat.embed</groupId>
  3. <artifactId>tomcat-embed-core</artifactId>
  4. <version>8.5.15</version>
  5. </dependency>

新建HttpHelper (用于读取Body)

  1. package com.xxx.util.core.filter.request;
  2. import javax.servlet.http.HttpServletRequest;
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.nio.charset.Charset;
  8. public class HttpHelper {
  9. public static String getBodyString(HttpServletRequest request) throws IOException {
  10. StringBuilder sb = new StringBuilder();
  11. InputStream inputStream = null;
  12. BufferedReader reader = null;
  13. try {
  14. inputStream = request.getInputStream();
  15. reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
  16. String line = "";
  17. while ((line = reader.readLine()) != null) {
  18. sb.append(line);
  19. }
  20. } catch (IOException e) {
  21. e.printStackTrace();
  22. } finally {
  23. if (inputStream != null) {
  24. try {
  25. inputStream.close();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. if (reader != null) {
  31. try {
  32. reader.close();
  33. } catch (IOException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. return sb.toString();
  39. }
  40. }

新建RequestReaderHttpServletRequestWrapper(防止流丢失)

  1. package com.xxx.util.core.filter.request;
  2. import java.io.BufferedReader;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.nio.charset.Charset;
  7. import javax.servlet.ReadListener;
  8. import javax.servlet.ServletInputStream;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletRequestWrapper;
  11. public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{
  12. private final byte[] body;
  13. public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
  14. super(request);
  15. body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
  16. }
  17. @Override
  18. public BufferedReader getReader() throws IOException {
  19. return new BufferedReader(new InputStreamReader(getInputStream()));
  20. }
  21. @Override
  22. public ServletInputStream getInputStream() throws IOException {
  23. final ByteArrayInputStream bais = new ByteArrayInputStream(body);
  24. return new ServletInputStream() {
  25. @Override
  26. public int read() throws IOException {
  27. return bais.read();
  28. }
  29. @Override
  30. public boolean isFinished() {
  31. return false;
  32. }
  33. @Override
  34. public boolean isReady() {
  35. return false;
  36. }
  37. @Override
  38. public void setReadListener(ReadListener readListener) {
  39. }
  40. };
  41. }
  42. }

新建HttpServletRequestReplacedFilter(过滤器)

  1. package com.xxx.util.core.filter.request;
  2. import javax.servlet.*;
  3. import javax.servlet.http.HttpServletRequest;
  4. import java.io.IOException;
  5. public class HttpServletRequestReplacedFilter implements Filter {
  6. @Override
  7. public void destroy() {
  8. }
  9. @Override
  10. public void doFilter(ServletRequest request, ServletResponse response,
  11. FilterChain chain) throws IOException, ServletException {
  12. ServletRequest requestWrapper = null;
  13. if(request instanceof HttpServletRequest) {
  14. requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
  15. }
  16. //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
  17. // 在chain.doFiler方法中传递新的request对象
  18. if(requestWrapper == null) {
  19. chain.doFilter(request, response);
  20. } else {
  21. chain.doFilter(requestWrapper, response);
  22. }
  23. }
  24. @Override
  25. public void init(FilterConfig arg0) throws ServletException {
  26. }
  27. }

最后我们只需要在Application.java中加上如下代码注入过滤器即可

  1. @Bean
  2. public FilterRegistrationBean httpServletRequestReplacedRegistration() {
  3. FilterRegistrationBean registration = new FilterRegistrationBean();
  4. registration.setFilter(new HttpServletRequestReplacedFilter());
  5. registration.addUrlPatterns("/*");
  6. registration.addInitParameter("paramName", "paramValue");
  7. registration.setName("httpServletRequestReplacedFilter");
  8. registration.setOrder(1);
  9. return registration;
  10. }

如下代码即可在拦截其中获取body且保证了controller中依旧可以再次获取

  1. HttpHelper.getBodyString(request);

SpringBoot之HandlerInterceptor拦截器的使用 ——(一)
SpringBoot之HandlerInterceptor拦截器的使用 ——(二)自定义注解
SpringBoot之HandlerInterceptor拦截器的使用 ——(四)防重复提交

发表评论

表情:
评论列表 (有 0 条评论,108人围观)

还没有评论,来说两句吧...

相关阅读