有些场景,比如验签,需要在拦截器中读取 Request Body 进行校验的操作。如果通过 HttpServletRequest#getInputStream() 方法读取,会导致后续的读取操作都失败。因为它只能读取一次,第二次读取会抛出异常。最常见的问题就是 @RequestBody 注解失效。

接下来我们通过包装 HttpServletRequestFilter 来处理这个问题。

包装 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;

/**
* Constructs a request object wrapping the given request.
*
* @param request the {@link HttpServletRequest} to be wrapped.
* @throws IllegalArgumentException if the request is null
*/
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 的重复读取。