接口请求一切正常,但是会报错Required request body is missing的错误。
百般查询和debug后都未查到问题,最后还是在stackOverFlow中找解决方案:
原因是:我新增了一个Filter用于打印请求和返回日志,部分代码如下:
@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); } }
@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只可以读取一次!看到这句话,基本问题就迎刃而解了。最简单的方式就是在filter里不要读取inputstream,当然如果你想读取的话,可以采用下面的方式,思路也是上图中说的:decorate the httpServletRequest。代码如下:
@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只可以读取一次! (我要是对基础有深刻的了解,就不会查这么久了。)
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; }