原文地址:http://liwx2000.iteye.com/blog/1542431
原文作者:liwx2000
為了提高項目安全性,攔截非法訪問,要給項目增加了一個過濾器,攔截所有的請求,校驗是否有不安全因素。
這個過程就遇到了一個問題:ServletRequest的getReader()和getInputStream()兩個方法只能被調用一次,而且不能兩個都調用。那么如果Filter中調用了一次,在Controller里面就不能再調用了。沒辦法,就去網上搜羅了一通,發現了一個超贊的解決方法。為了記錄備案,無恥的轉了過來,原文地址在上,內容在下。
1. 查看了下ServletRequest的說明,如下:
/** * Retrieves the body of the request as binary data using * a {@link ServletInputStream}. Either this method or * {@link #getReader} may be called to read the body, not both. * * @return a {@link ServletInputStream} object containing * the body of the request * * @exception IllegalStateException if the {@link #getReader} method * has already been called for this request * * @exception IOException if an input or output exception occurred * */ public ServletInputStream getInputStream() throws IOException; /** * Retrieves the body of the request as character data using * a <code>BufferedReader</code>. The reader translates the character * data according to the character encoding used on the body. * Either this method or {@link #getInputStream} may be called to read the * body, not both. * * * @return a <code>BufferedReader</code> * containing the body of the request * * @exception UnsupportedEncodingException if the character set encoding * used is not supported and the * text cannot be decoded * * @exception IllegalStateException if {@link #getInputStream} method * has been called on this request * * @exception IOException if an input or output exception occurred * * @see #getInputStream * */ public BufferedReader getReader() throws IOException;
兩個方法都注明方法只能被調用一次,由於RequestBody是流的形式讀取,那么流讀了一次就沒有了,所以只能被調用一次。既然是因為流只能讀一次的原因,那么只要將流的內容保存下來,就可以實現反復讀取了。byte數組允許被多次讀取,而不會丟失內容。下面使用byte數組將流的內容保存下來。
2. 工具方法:
先將RequestBody保存為一個byte數組,然后通過Servlet自帶的HttpServletRequestWrapper類覆蓋getReader()和getInputStream()方法,使流從保存的byte數組讀取。然后再Filter中將ServletRequest替換為ServletRequestWrapper。
代碼如下:
BodyReaderHttpServletRequestWrapper類包裝ServletRequest,將流保存為byte[],然后將getReader()和getInputStream()方法的流的讀取指向byte[]。
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import jodd.JoddDefault; import jodd.io.StreamUtil; public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); // body = StreamUtil.readBytes(request.getReader(), JoddDefault.encoding);
// 因為http協議默認傳輸的編碼就是iso-8859-1,如果使用utf-8轉碼亂碼的話,可以嘗試使用iso-8859-1
body = StreamUtil.readBytes(request.getReader(), "iso-8859-1"); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } }; } }
3. 在Filter中的使用:
在Filter中將ServletRequest替換為ServletRequestWrapper
public class HttpServletRequestReplacedFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { //Do nothing } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(request instanceof HttpServletRequest) { requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request); } if(null == requestWrapper) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void destroy() { //Do nothing } }