记录一次Required request body is m

接口请求一切正常,但是会报错Required request body is missing的错误

百般查询和debug后都未查到问题,最后还是在stackOverFlow中找解决方案:

stackoverflow.com/questions/3…

原因是:我新增了一个Filter用于打印请求和返回日志,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vbscript复制代码    @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
       this.preHandle(request, (HttpServletResponse) response);
       chain.doFilter(request, response);
       this.afterCompletion((HttpServletRequest) request, (HttpServletResponse) response);
  }

   private void preHandle(HttpServletRequest request, HttpServletResponse response) {
           try {
               RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
               log.info("request params:{}", JSON.toJSON(requestWrapper.getBody()));
          } catch (Exception e) {
               log.info("log error ignore", e);
          }
  }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
java复制代码@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

   private final String body;

   public RequestWrapper(HttpServletRequest request) {
       super(request);
       StringBuilder stringBuilder = new StringBuilder();
       BufferedReader bufferedReader = null;
       InputStream inputStream = null;
       try {
           inputStream = request.getInputStream();
           if (inputStream != null) {
               bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
               char[] charBuffer = new char[128];
               int bytesRead = -1;
               while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                   stringBuilder.append(charBuffer, 0, bytesRead);
              }
          } else {
               stringBuilder.append("");
          }
      } catch (IOException ex) {

      } finally {
           if (inputStream != null) {
               try {
                   inputStream.close();
              } catch (IOException e) {
                   log.error("inputStream close error", e);
              }
          }
           if (bufferedReader != null) {
               try {
                   bufferedReader.close();
              } catch (IOException e) {
                   log.error("bufferedReader close error", e);
              }
          }
      }
       body = stringBuilder.toString();
  }

   @Override
   public ServletInputStream getInputStream() throws IOException {
       final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
       ServletInputStream servletInputStream = new ServletInputStream() {
           @Override
           public boolean isFinished() {
               return false;
          }

           @Override
           public boolean isReady() {
               return false;
          }

           @Override
           public void setReadListener(ReadListener readListener) {
          }

           @Override
           public int read() throws IOException {
               return byteArrayInputStream.read();
          }
      };
       return servletInputStream;

  }

   @Override
   public BufferedReader getReader() throws IOException {
       return new BufferedReader(new InputStreamReader(this.getInputStream()));
  }

   public String getBody() {
       return this.body;
  }

}

开始总是找不到原因,最后是上述链接里的一番话,才让我恍然大悟,找到根源

inputstream.png

inputstream只可以读取一次! ,看到这句话,基本问题就迎刃而解了。最简单的方式就是在filter里不要读取inputstream,当然如果你想读取的话,可以采用下面的方式,思路也是上图中说的:decorate the httpServletRequest。代码如下:

1
2
3
4
5
6
7
8
vbscript复制代码    @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
       this.preHandle(requestWrapper, (HttpServletResponse) response);
       //此处传封装后的HttpServletRequest
       chain.doFilter(requestWrapper, response);
       this.afterCompletion((HttpServletRequest) request, (HttpServletResponse) response);
  }

RequestWrapper不用改,封装HttpServletRequest之后,doFilter也传入封装的HttpServletRequest即可。

总结

一. 为什么inputStream只可以读取一次

InputStream read方法内部会记录position,用于记录当前流读取到的位置,若已读完,read方法会返回-1,(经常和inputStream打交道的,应该都会while(read() != -1) ,用这种方式读取inputStream)。若读取完再次读取的话可以调用inputStream.reset方法,此方法的前提是此方法markSupported返回true。HttpServletRequest使用的是ServletInputStream,看源码可知ServletInputStream没有实现reset和markSupported方法,那么ServletInputStream无法reset之后再次读取,所以inputStream只可以读取一次! (我要是对基础有深刻的了解,就不会查这么久了。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码​
   public synchronized void reset() throws IOException {
       throw new IOException("mark/reset not supported");
  }

   /**
    * Tests if this input stream supports the <code>mark</code> and
    * <code>reset</code> methods. Whether or not <code>mark</code> and
    * <code>reset</code> are supported is an invariant property of a
    * particular input stream instance. The <code>markSupported</code> method
    * of <code>InputStream</code> returns <code>false</code>.
    *
    * @return <code>true</code> if this stream instance supports the mark
    *         and reset methods; <code>false</code> otherwise.
    * @see     java.io.InputStream#mark(int)
    * @see     java.io.InputStream#reset()
    */
   public boolean markSupported() {
       return false;
  }

二. StackOverFlow真靠谱,有问题先上StackOverFlow

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%