HttpServletRequest.getInputStream() 多次獲取post的數據
在實際的開發過程中,我們會在Filter或者AOP中讀取body數據進行數據校驗,
GET方法獲取參數比較簡單。可以直接對HttpServletRequest類使用getQueryString和getParameterMap獲取到,但是對於POST方法,可使用如下方法從request中獲取body參數:
private String getPostData(HttpServletRequest request) throws IOException {
InputStream in = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
StringBuffer sb = new StringBuffer("");
String temp;
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
if (in != null) {
in.close();
}
if (br != null) {
br.close();
}
return sb.toString();
}
這樣子雖然能夠獲取到post的數據,但是系統會報一個異常:
java.lang.IllegalStateException: getInputStream() has already been called ...
原來:
- 一個InputStream對象在被讀取完成后,將無法被再次讀取,始終返回-1;
- InputStream並沒有實現reset方法(可以重置首次讀取的位置),無法實現重置操作;
因此,當自己寫的Filter中調用了一次getInputStream()
后,后面再調用getInputStream()
讀取的數據都為空,所以才報IllegalStateException錯誤。
解決辦法
1、新建一個 MyRequestWrapper
類 對父類的 HttpServletRequestWrapper
的getInputStream()
方法進行重寫,代碼如下:
import com.tusdao.log.util.RequestParamAware;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
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();
request.setAttribute("body",body); //將post的body數據放入緩存,這樣子在后面就能夠隨時取用
}
@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 boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
2、在攔截器中傳入Wrapper對象:
@Order(1)
@Component
@Slf4j
@WebFilter(filterName="logFilter", urlPatterns="/*")
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("filter before");
// 轉換成自己覆寫的類
MyRequestWrapper req = new MyRequestWrapper((HttpServletRequest)request);
log.info("獲取到post信息為:{}", RequestParamAware.extractPostBody(request));
// 如果沒有覆寫HttpServletRequestWrapper
// doFilter(request, response)之后 再用到body
// 會拋出類似錯誤 Cannot call getInputStream(), getReader() already called
// 注意doFilter這里傳進去的參數req 而不是request !!!!
chain.doFilter(req, response);
log.info("filter after");
}
@Override
public void destroy() {
}
}
這樣,位於后面的controller就可以擁有唯一一次調用HttpServletRequest.getInputStream()
的機會了。並且在后序的業務中可以通過getAttribute獲取到post的數值。