大白話講解如何解決HttpServletRequest的請求引數只能讀取一次的問題

哎喔別走發表於2021-11-09

        大家在開發過程中,可能會遇到對請求引數做下處理的場景,比如讀取上送的引數中看呼叫方上送的系統編號是否是白名單裡面的(更多的會用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);//注意這個地方往後傳的就是我們自己封裝的類的例項了
    }

 

相關文章