Springboot如何設置過濾器及重復讀取request里的body
需求:
request的content-type為applciation/json,進入controller之前需要把body中的參數(shù)取出來做一次處理,然后和hearder中的另一個參數(shù)做對比。
思路:
加一個過濾器,在過濾器中取出參數(shù)做處理,然后比較
注意:
body里的數(shù)據(jù)用流來讀取,只能讀取一次
HttpServletRequest的輸入流只能讀取一次的原因
我們先來看看為什么HttpServletRequest的輸入流只能讀一次,當我們調(diào)用getInputStream()方法獲取輸入流時得到的是一個InputStream對象,而實際類型是ServletInputStream,它繼承于InputStream。
InputStream的read()方法內(nèi)部有一個postion,標志當前流被讀取到的位置,每讀取一次,該標志就會移動一次,如果讀到最后,read()會返回-1,表示已經(jīng)讀取完了。如果想要重新讀取則需要調(diào)用reset()方法,position就會移動到上次調(diào)用mark的位置,mark默認是0,所以就能從頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當然能否reset也是有條件的,它取決于markSupported()方法是否返回true。
InputStream默認不實現(xiàn)reset(),并且markSupported()默認也是返回false,這一點查看其源碼便知:
我們再來看看ServletInputStream,可以看到該類沒有重寫mark(),reset()以及markSupported()方法:
綜上,InputStream默認不實現(xiàn)reset的相關(guān)方法,而ServletInputStream也沒有重寫reset的相關(guān)方法,這樣就無法重復讀取流,這就是我們從request對象中獲取的輸入流就只能讀取一次的原因。
重復讀取body中數(shù)據(jù)的方法
這個自定義的requestWrapper繼承了HttpServletRequestWrapper ,HttpServletRequestWrapper 是一個ServletRequest的包裝類同時也是ServletRequest的實現(xiàn)類。
在這個自定義的requestWrapper里,用一個String做緩存,在構(gòu)造方法里先把request的body中的數(shù)據(jù)緩存起來,然后重寫了getInputStream,返回這個緩存的body,而不是從流中讀取。
這樣,在需要多次讀取body的地方,只需要在過濾器中把原來的request換成這個自定義的request,然后把這個自定義的帶緩存功能的request傳到后續(xù)的過濾器鏈中。
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper { private final String body; /** * * @param request */ public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException{ super(request); StringBuilder sb = new StringBuilder(); InputStream ins = request.getInputStream(); BufferedReader isr = null; try{ if(ins != null){ isr = new BufferedReader(new InputStreamReader(ins)); char[] charBuffer = new char[128]; int readCount = 0; while((readCount = isr.read(charBuffer)) != -1){ sb.append(charBuffer,0,readCount); } }else{ sb.append(""); } }catch (IOException e){ throw e; }finally { if(isr != null) { isr.close(); } } sb.toString(); body = sb.toString(); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletIns = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayIns.read(); } }; return servletIns; } }
springboot的過濾器
2個注解:
@WebFilter
(過濾器上)@ServletComponentScan
(加在啟動類上,支持servlet components掃描(為了webfilter))
@Order(999) // 序號越低,優(yōu)先級越高 // 加上WebFilter即可成為過濾器 @WebFilter(filterName="myFilter", urlPatterns="/api/workorder/service/selfAppeal") public class ExternalFilter implements Filter { private final static Logger logger = LoggerFactory.getLogger(ExternalFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { logger.info("filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ResponseObject object = new ResponseObject(); HttpServletRequest req = (HttpServletRequest)servletRequest; HttpServletResponse res = (HttpServletResponse)servletResponse; // 一個request的包裝類,初始化時緩存了body,重寫了getInputStream返回緩存的body,實現(xiàn)重復讀取body BodyReaderRequestWrapper requestWrapper = new BodyReaderRequestWrapper(req); String requestURI = requestWrapper.getRequestURI(); System.out.println("--------------------->過濾器:請求地址" + requestURI); String md5 = requestWrapper.getHeader("md5") ; if (md5 == null || !md5.toLowerCase().equals(MD5.md5(ReqGetBody.getBody(requestWrapper)).toLowerCase())) { object.setStatus(501); object.setStatusText("數(shù)據(jù)md5校驗失敗"); render(object, res); return; } // 這里傳遞下去的就是自定義的request了,所以后續(xù)的Controller才能重復讀取到body里的參數(shù) filterChain.doFilter(requestWrapper, res); } @Override public void destroy() { } /** * @Title: render * @Description: 發(fā)送Response * @param object * @param response void * @author MasterYi * @date 2020年1月15日上午10:48:45 */ private void render(ResponseObject object, HttpServletResponse response) { response.setContentType("application/json;charset=UTF-8"); try { response.setStatus(200); response.getWriter().write(JSONObject.toJSON(object).toString()); } catch (IOException e) { logger.error("ExternalFilter寫入response異常"); } } }
上面的getBody的代碼
從body中取值的具體操作
public class ReqGetBody { static public String getBody(HttpServletRequest request) { try { ServletInputStream in = request.getInputStream(); String body; body = StreamUtils.copyToString(in, Charset.forName("UTF-8")); return body; } catch (IOException e) { e.printStackTrace(); return ""; } } }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合Mybatis Plus實現(xiàn)基本CRUD的示例代碼
Mybatis Plus是在Mybatis的基礎上的增強,使得我們對一些基本的CRUD使用起來更方便,本文主要介紹了SpringBoot整合Mybatis Plus實現(xiàn)基本CRUD的示例代碼,具有一定的參考價值,感興趣的可以了解一下2023-05-05實體類使用@Builder,導致@ConfigurationProperties注入屬性失敗問題
這篇文章主要介紹了實體類使用@Builder,導致@ConfigurationProperties注入屬性失敗問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12springboot使用AOP+反射實現(xiàn)Excel數(shù)據(jù)的讀取
本文主要介紹了springboot使用AOP+反射實現(xiàn)Excel數(shù)據(jù)的讀取,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01