完美解決request請求流只能讀取一次的問題
解決request請求流只能讀取一次的問題
實際開發(fā)碰到的問題
解決request請求流中的數(shù)據(jù)二次或多次使用問題
實際開發(fā)碰到的問題
springboot項目中,為了防止sql注入,采用Filter攔截器對所有請求流中的json數(shù)據(jù)進行校驗,請求數(shù)據(jù)沒問題則繼續(xù)向下執(zhí)行,在后邊的代碼中應用到請求參數(shù)值時,發(fā)現(xiàn)request中的json數(shù)據(jù)為空;
除上邊描述的情況,嘗試過兩次從request中獲取json數(shù)據(jù),第二次同樣是獲取不到的。
解決request請求流中的數(shù)據(jù)二次或多次使用問題
繼承HttpServletRequestWrapper,將請求體中的流copy一份,覆寫getInputStream()和getReader()方法供外部使用。每次調(diào)用覆寫后的getInputStream()方法都是從復制出來的二進制數(shù)組中進行獲取,這個二進制數(shù)組在對象存在期間一直存在,這樣就實現(xiàn)了流的重復讀取。
//增強類 public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { //保存流 private byte[] requestBody = null; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); requestBody = StreamUtils.copyToByteArray(request.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 readListener) { } }; } @Override public BufferedReader getReader() throws IOException{ return new BufferedReader(new InputStreamReader(getInputStream())); } } //過濾器 @Component @WebFilter public class RequestSqlValidFilter implements Filter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request); //請求參數(shù)合法,無sql注入 if((sqlValid(request, response))){ chain.doFilter(requestWrapper, response);//requestWrapper中保存著供二次使用的請求數(shù)據(jù) }else { logger.error("RequestSqlValidFilter sqlValid param error"); } } @Override public void destroy() { }
補充知識:【java web】解決流讀完一次就不能再次獲取body數(shù)據(jù)的問題
問題來自我工作業(yè)務上的需求:前端請求時需要將json用RSA算法加密,數(shù)據(jù)經(jīng)過后端過濾器進行自動解密,這樣做的好處是以后不需要在每一個方法里都手動解密一次,增加代碼的簡潔性、可維護性。
但這樣一來便會面臨一個問題:http的request請求的輸入流在過濾器中就已經(jīng)被讀取了(因為需要讀取并解密request body 里被前端加密了的json數(shù)據(jù)),流只能被讀取一次,這樣一來數(shù)據(jù)便傳不進controller里,導致接下來的業(yè)務無法進行。
于是我上網(wǎng)找了一些資料并成功解決了這個問題,基本思路是封裝原生的HttpServletRequest請求,將其輸入流里的數(shù)據(jù)保存在字節(jié)數(shù)組里,最后重寫getInputStream方法,使其之后每次讀取數(shù)據(jù)都是從字節(jié)數(shù)組里讀取的。
第一步:寫一個Request包裝類BodyReaderHttpServletRequestWrapper
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @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(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } public void setInputStream(byte[] body){ this.body = body; }
里面涉及一個HttpHelper類,順便也貼出來
public class HttpHelper { /** * 獲取請求Body * @param request * @return */ public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } }
第二步:編寫包裝Filter
這個filter類可以將經(jīng)過的原生Request請求自動包裝成BodyReaderHttpServletRequestWrapper
/** * desc : 用于包裝原生request, 解決流讀完一次就不能再次獲取body數(shù)據(jù)的問題 * Created by Lon on 2018/3/9. */ public class RequestWrapperFilter implements Filter{ private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapperFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { } @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) { LOGGER.error("包裝request失敗!將返回原來的request"); chain.doFilter(request, response); } else { LOGGER.info("包裝request成功"); chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }
第三步:在web.xml上配置過濾器
<filter> <filter-name>RequestWrapperFilter</filter-name> <filter-class>com.kx.security.filter.RequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>RequestWrapperFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
這里要注意的是這個包裝過濾器可能要寫在web.xml配置文件里某些過濾器的前面,比如解密過濾器,否則被解密過濾器先讀取流的話,包裝過濾器就讀取不了流了。
至此一個簡單的解決方法就完成啦!
以上這篇完美解決request請求流只能讀取一次的問題就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java根據(jù)開始時間和結束時間及周幾計算日期的示例代碼
在Java 7中,java.time包不存在,所以我們需要使用java.util.Calendar和java.util.Date類來實現(xiàn)類似的功能,這篇文章主要介紹了Java根據(jù)開始時間和結束時間及周幾計算出日期的示例代碼,需要的朋友可以參考下2024-06-06java統(tǒng)計字符串中指定元素出現(xiàn)次數(shù)方法
這篇文章主要介紹了java統(tǒng)計字符串中指定元素出現(xiàn)次數(shù)方法,需要的朋友可以參考下2015-12-12Java實現(xiàn)字節(jié)數(shù)B轉(zhuǎn)化為KB、MB、GB的方法示例【測試可用】
這篇文章主要介紹了Java實現(xiàn)字節(jié)數(shù)B轉(zhuǎn)化為KB、MB、GB的方法,結合實例形式分析了java字節(jié)數(shù)的轉(zhuǎn)換運算相關操作技巧,需要的朋友可以參考下2017-08-08Spring Security自定義異常 AccessDeniedHandler不生效解決方法
本文主要介紹了Spring Security自定義異常 AccessDeniedHandler不生效解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07