HttpServletRequest中InputStream读取问题

最近在设置统一interceptor拦截请求校验请求参数的时候出现了一个问题,就是我需要在拦截器中获取请求头中所带的参数,包括GET参数和请求体中的参数。

问题:当在拦截器中拦截到请求体中的参数的时候对应的controller方法中的**@RequestBody注解失效**,

报错原因:没有传入所需参数

但是我在拦截器中确实获取到了请求头中包含的请求体参数,为什么会在Controller层中获取不到?

原因:当我在读取请求头中的请求体参数的时候,请求体是以流的形式存储,我获取到请求体的时候,请求体中的指针会指到最后一个位置,导致Controller层在通过请求头获取请求体参数的时候发现请求体中的指针后面没有东西,于是报错:没有传入请求体参数

解决方法:自己包装一个Request请求类实现ServletRequest类,重写getInputStream方法,并使用过滤器,在请求还没有到拦截器之前将请求体换成自己的封装的请求体类即可

自己封装的请求体

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
public class CachingHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 设置存储请求体中的内容的参数,当我们需要获取请求体参数的时候,
* 直接从body这个参数中获取即可,
* 不用在受限于只能读取一次的请求头流了
*/
private final byte[] body;


/**
* Constructs a request object wrapping the given request.
*
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public CachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 初始化body中的内容
this.body = StreamUtils.copyToByteArray(request.getInputStream());
}

@Override
public ServletInputStream getInputStream() {
// 每次使用GetInputStream读取请求头中的内容的时候都会新建一个流对象,这样就不会出现,请求体中的流对象只能够被读取一次了
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {

@Override
public boolean isFinished() {
return bais.available() == 0;
}

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

@Override
public void setReadListener(ReadListener listener) {

}

@Override
public int read() {
// 重写读取方法,每次读取的时候都能够从body中读取内容
return bais.read();
}
};
}
}

使用过滤器来过滤所有请求,并将其中的HttpServletRequest替换成我们包装的CachingHttpServletRequestWrapper

1
2
3
4
5
6
7
8
9
10
@WebFilter(filterName = "CachingHttpServletRequestFilter", urlPatterns = "/*")
public class CachingHttpServletRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 拦截到请求,将请求转换成CachingHttpServletRequestWrapper
CachingHttpServletRequestWrapper cachingHttpServletRequestWrapper = new CachingHttpServletRequestWrapper((HttpServletRequest) request);
// 使用链式过滤器将自己封装的请求头传递给下一层
chain.doFilter(cachingHttpServletRequestWrapper, response);
}
}

在SpringBootApplication启动类中添加@ServletComponentScan确保能够读取到@WebFilter注解信息

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@MapperScan("com.nuyoah.mapper")
@EnableTransactionManagement
@ServletComponentScan
public class NuyoahAdminApplication {
public static void main(String[] args) {
SpringApplication.run(NuyoahAdminApplication.class);
}
}

这样在拦截器获取到的时候,读取一次请求头中的内容,请求头中的内容就可以二次读取了

1
2
3
4
5
6
public class SqlInjectionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}