大白話講解如何解決HttpServletRequest的請求參數只能讀取一次的問題
- 2021 年 11 月 9 日
- 筆記
大家在開發過程中,可能會遇到對請求參數做下處理的場景,比如讀取上送的參數中看調用方上送的系統編號是否是白名單裏面的(更多的會用request中獲取IP地址判斷)、需要對請求方上送的參數進行大小寫轉換或者字符處理、或者對請求方上送的用戶名參數判斷是否有對當前請求地址的訪問權限(多用于越權處理)等,這些都可以通過實現Filter自定義一個類,在該類中進行相應處理。但是會有個問題,如果是POST請求過來,在Filter的實現類中讀取了請求參數,那麼後續在Controller裏面就無法獲取,這是由於我們獲取POST請求參數的時候,是通過讀取request的IO流來實現的,一旦讀取了那麼流關閉後,後續就用不了了。
那麼解決這個問題,核心考慮就是支持多次讀取,可通過緩存方式把流解析的數據緩存下來,這樣就可以重複讀取緩存下來的數據。舉個不恰當的例子,如下
public class MockHttpRequest {
public static String readBook(String bookName) { String fullName = bookName.concat(" 作者:xxx"); System.out.println(fullName); bookName = null; return bookName; } public static void readTwice(String bookName) { bookName = readBook(bookName); System.out.println(bookName); } public static void main(String[] args) { readTwice("hello"); } }
=========執行結果如下=========在readbook方法中讀取了bookName後,對bookName做了清空處理(類比流讀取後清空),那麼第二次訪問就拿不到了
hello 作者:xxx
null
如果我們把bookName緩存下來,那麼其他方法都讀取緩存的字段,就可以實現重複多次讀取,如下
public class MockHttpRequest { private static String bufferBook = null; public static String readBook(String bookName) { String fullName = bookName.concat(" 作者:xxx"); System.out.println(fullName); bufferBook = bookName; bookName = null; return bookName; } public static void readTwice(String bookName) { bookName = readBook(bookName); System.out.println(bufferBook); } public static void main(String[] args) { readTwice("hello"); } }
================執行結果如下=============無論readTwice還是更多次,只要讀取緩存下來的字段bufferBook,就能一直獲取bookName
hello 作者:xxx
hello
所以【解決HttpServletRequest的請求參數只能讀取一次的問題】就從數據緩存角度考慮,我們在HttpServletRequest裏面創建一個私有屬性,用來緩存用戶上傳的參數,不就可以實現多次讀取的功能了嗎?我們考慮新生產一個類實現HttpServletRequest,然後在這個類中定義一個屬性字段,後續多次讀取參數都從這個屬性中獲取。
tomcat提供的jar中已經有一個應用了裝飾器模式的類HttpServletRequestWrapper(該類實現了HttpServletRequest),我們可以定義類NotesHttpServletRequestWrapper extends HttpServletRequestWrapper,然後在NotesHttpServletRequestWrapper中定義一個屬性requestBody,具體代碼見下:
public class NotesHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] requestBody = null;//用來緩存從HttpServletRequest的io流中讀取的參數轉為位元組緩存下來 //初始化的時候,就從request讀取放到屬性字段 //比如在filter中通過NotesHttpServletRequestWrapper requestWrapper = new NotesHttpServletRequestWrapper(request); public NotesHttpServletRequestWrapper(HttpServletRequest request) { super(request); try { requestBody = StreamUtils.copyToByteArray(request.getInputStream()); } catch (IOException e) { e.printStackTrace(); } } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException {//後續讀取流的操作都是從屬性字段中讀取的緩存下來的信息 final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); 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 listener) { return; } }; } //獲取request請求body中參數 public static Map<String,Object> getRequestParamsFromStream(InputStream in) { String param= null; BufferedReader buffReader=null; try { buffReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8.name())); StringBuilder paramsBuilder = new StringBuilder(); String inputStr; while ((inputStr = buffReader.readLine()) != null) paramsBuilder.append(inputStr); if(!JSONValidator.from(paramsBuilder.toString()).validate()){ return new HashMap<String, Object>(); } JSONObject jsonObject = JSONObject.parseObject(paramsBuilder.toString()); if(jsonObject==null){ return new HashMap<String, Object>(); } param= jsonObject.toJSONString(); } catch (Exception e) { e.printStackTrace(); }finally{ if(buffReader!=null){ try { buffReader.close(); } catch (IOException e) { e.printStackTrace(); } } } return JSONObject.parseObject(param,Map.class); } }
自己實現的HttpServletRequestWrapper類的使用,一般是結合Filter進行,如下:
@Component("notesRequestWrapperFilter")
public class NotesRequestWrapperFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
if (!(servletRequest instanceof HttpServletRequest) ||
!(servletResponse instanceof HttpServletResponse)) {
throw new ServletException("Unsupport request");
}
//這裡可以做一些請求權限的校驗,杜絕越權漏洞
//TODO 比如header中存放token,token解析出賬號,判斷賬號的角色下是否有關聯該請求接口
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("uri===" + request.getRequestURI());
System.out.println("url===" + request.getRequestURL());
NotesHttpServletRequestWrapper requestWrapper = new NotesHttpServletRequestWrapper(request);//初始化HttpServletRequest的封裝類
System.out.println("===HttpMethod.POST.name()===" + HttpMethod.POST.name());
String systemCode = "";
if(HttpMethod.POST.name().equals(request.getMethod())) {
//獲取request請求body中參數
Map<String,Object> paramsMap = requestWrapper.getRequestParamsFromStream(requestWrapper.getInputStream());
systemCode = paramsMap.get("systemCode");
}else {
System.out.println("===請求方式===" + request.getMethod());
systemCode = request.getParameter("systemCode");
}
//判斷請求方是否位於白名單,系統白名單存在庫表、配置中心都可以
List<String> systemsPermit = new ArrayList<String>();
if(!systemsPermit.contains(systemCode)) {
throw new ServletException("Unsupport System");
}
chain.doFilter(requestWrapper, servletResponse);//注意這個地方往後傳的就是我們自己封裝的類的實例了
}


