getInputStream/getReader() has already been called for this request
一、背景
定义了一个安全校验过滤器,现在要获取请求参数。
通过request.getParameter获取请求参数,然而这种方式只能获取POST方式中的Content-Type: application/x-www-form-urlencoded。
通过request.getInputStream(或者getReader)读取请求数据流(能解析出content-Type为 application/x-www-form-urlencoded ,application/json , text/xml这三种提交方式的数据(注:multipart/form-data不行),但是!!!在后续controller接口中无法获取该数据流
二、原因
The Servlet API documentation for getReader() and getInputStream() says:
public java.io.BufferedReader getReader()
Throws:
java.lang.IllegalStateException - if getInputStream() method has been called on this request
public ServletInputStream getInputStream()
Throws:
java.lang.IllegalStateException - if the getReader() method has already been called for this request
主要问题在于:不能在过滤器中读取一次二进制流(字符流),又在另外一个Servlet中读取一次,即一个InputSteam(BufferedReader)对象在被读取完成后,将无法再次被读取。
三、解决方案
使用MyRequestWrapper来包装HttpServletRequest,在MyRequestWrapper中初始化读取request的BufferedReader数据,以String形式缓存在其中,然后在Filter中将request转换为包装过的request(即克隆请求的思想)
public class MyRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder sb = new StringBuilder();
String line;
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
ServletRequest myRequestWrapper = null;
Map<String, String> params = new HashMap<>(16);
// 封装请求参数
switch (request.getMethod()) {
case BaseConsts.HttpConts.METHOD_POST:
myRequestWrapper = new MyRequestWrapper(request);
JSONObject jsonObject = JSONObject.parseObject(myRequestWrapper.getBody());
for (String key : jsonObject.keySet()) {
params.put(key, jsonObject.get(key).toString());
}
break;
case BaseConsts.HttpConts.METHOD_GET:
Enumeration<String> pNames = request.getParameterNames();
while (pNames.hasMoreElements()) {
String pName = pNames.nextElement();
params.put(pName, request.getParameter(pName));
}
break;
default:
break;
}
CheckReult checkReult = checkAndSign(params);
if (checkReult.isSuccess()) {
filterChain.doFilter(myRequestWrapper == null ? request : myRequestWrapper, response);
} else {
try {
mappingJackson2JsonView.render(checkReult.getErrData(), request, response);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}