複寫HttpServlet多次獲取請求body
阿新 • • 發佈:2021-10-26
場景需求
- 介面地址、介面請求全域性過濾列印日誌
- 介面報錯時,通過釘釘等開放API傳送警告資訊
遇到問題
請求資料經過介面處理後,資料流已經關閉,無法再在報錯處理中通過讀取流的方式獲取body資料,HttpServlet本身也沒有類似getBody()
的方法,所以無法在提醒中正確傳遞出問題介面的具體引數
解決思路
在全域性過濾器中,對所有請求通過過載HttpServletRequestWrapper
的getInputStream
複寫HttpServletRequest
請求,將body引數讀取出來後,存入請求的attribute
屬性中,後面如果有需要再次獲取引數時,不再通過流讀取,而是直接通過setAttribute
HttpServletRequestWrapper
中讀取後,記得重置流讀取標誌位,不要影響後面介面的正常使用
程式碼實現
// RequestWrapper.java import org.apache.commons.io.IOUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; /** * @Description: 對HttpServletRequest進行重寫, * 1、用來接收application/json引數資料型別,即@RequestBody註解標註的引數,解決多次讀取問題 * 2、用來解決註解@RequestParam通過POST/PUT/DELETE/PATCH方法傳遞引數,解決多次讀取問題 * 首先看一下springboot控制器三個註解: * 1、@PathVariable註解是REST風格url獲取引數的方式,只能用在GET請求型別,通過getParameter獲取引數 * 2、@RequestParam註解支援GET和POST/PUT/DELETE/PATCH方式,Get方式通過getParameter獲取引數和post方式通過getInputStream或getReader獲取引數 * 3、@RequestBody註解支援POST/PUT/DELETE/PATCH,可以通過getInputStream和getReader獲取引數 */ public class RequestWrapper extends HttpServletRequestWrapper { /** * 引數位元組陣列 */ private byte[] requestBody; /** * Http請求物件 */ private HttpServletRequest request; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); this.request = request; } /** * @return * @throws IOException */ @Override public ServletInputStream getInputStream() throws IOException { /** * 每次呼叫此方法時將資料流中的資料讀取出來,然後再回填到InputStream之中 * 解決通過@RequestBody和@RequestParam(POST方式)讀取一次後控制器拿不到引數問題 */ if (null == this.requestBody) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(request.getInputStream(), baos); this.requestBody = baos.toByteArray(); // 後面需要拿請求體的地方通過getAttribute("body")獲取 this.request.setAttribute("body", baos); } final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } @Override public int read() { return bais.read(); } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } }
// ChannelFilter.java import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 全域性介面請求攔截 */ @Component @WebFilter(filterName = "channelFilter", urlPatterns = {"/*"}) public class ChannelFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { try { ServletRequest requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) request); } if (requestWrapper == null) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } catch (IOException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } } @Override public void destroy() { } }