有些场景,比如验签,需要在拦截器中读取 Request Body 进行校验的操作。如果通过 HttpServletRequest#getInputStream()
方法读取,会导致后续的读取操作都失败。因为它只能读取一次,第二次读取会抛出异常。最常见的问题就是 @RequestBody
注解失效。
接下来我们通过包装 HttpServletRequest
和 Filter
来处理这个问题。
包装 HttpServletRequest 通过 body 字节数组进行缓存。
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 public class ContentCachingHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte [] body; private BufferedReader reader; private ServletInputStream inputStream; public ContentCachingHttpServletRequestWrapper (HttpServletRequest request) throws IOException { super (request); loadBody(request); } private void loadBody (HttpServletRequest request) throws IOException { body = IOUtils.toByteArray(request.getInputStream()); inputStream = new RequestCachingInputStream (body); } public byte [] getBody() { return body; } @Override public ServletInputStream getInputStream () throws IOException { if (inputStream != null ) { return inputStream; } return super .getInputStream(); } @Override public BufferedReader getReader () throws IOException { if (reader == null ) { reader = new BufferedReader (new InputStreamReader (inputStream, getCharacterEncoding())); } return reader; } private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream (byte [] bytes) { inputStream = new ByteArrayInputStream (bytes); } @Override public int read () throws IOException { return inputStream.read(); } @Override public boolean isFinished () { return inputStream.available() == 0 ; } @Override public boolean isReady () { return true ; } @Override public void setReadListener (ReadListener readlistener) { } } }
Filter 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Component public class RequestBodyRetrieveFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String contentType = httpServletRequest.getContentType(); if (contentType != null && contentType.startsWith("multipart/" )) { chain.doFilter(request, response); return ; } String requestURI = httpServletRequest.getRequestURI(); if (requestURI.contains("/druid" )) { chain.doFilter(request, response); return ; } ContentCachingHttpServletRequestWrapper requestWrapper = new ContentCachingHttpServletRequestWrapper ((HttpServletRequest) request); chain.doFilter(requestWrapper, response); } }
Interceptor 中使用 1 2 3 4 5 6 7 @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestBody = "" ; if (request instanceof ContentCachingRequestWrapper) { requestBody = new String (((ContentCachingRequestWrapper) request).getBody(), request.getCharacterEncoding()); } }
至此,即可完成 Request Body 的重复读取。