成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從零搭建開發腳手架之 HttpServletRequest多次讀取異常問題的因和果

開發 前端
在過濾器或者Controller中多次調用HttpServletRequest.getReader()或getInputStream()方法,會導致異常。

[[382286]]

本文轉載自微信公眾號「Java大廠面試官」,作者laker。轉載本文請聯系Java大廠面試官公眾號。

背景

在過濾器或者Controller中多次調用HttpServletRequest.getReader()或getInputStream()方法,會導致異常。

給出示例代碼如下:

  1. @RequestMapping(value = "/param"
  2. private ResponseEntity<String> param(HttpServletRequest request, @RequestBody Map body){ 
  3.       // ... 
  4.       String string = IOUtils.toString(request.getInputStream()); 
  5.       // ... 

Postman請求如下:

 

錯誤如下:

  1. java.lang.IllegalStateException: getInputStream() has already been called for this request 
  2.  at org.apache.catalina.connector.Request.getReader(Request.java:1222) ~[tomcat-embed-core-9.0.41.jar:9.0.41] 
  3.  at org.apache.catalina.connector.RequestFacade.getReader(RequestFacade.java:504) ~[tomcat-embed-core-9.0.41.jar:9.0.41] 
  4.  at com.laker.notes.easy.http.HttpController.param(HttpController.java:64) ~[classes/:na] 
  5.     ... 

原因

Json數據是放在Http協議的Body中的,我們需要通過request.getInputStream()或者@RequestBody(本質也是調用request.getInputStream())獲取請求體內容。

當我們調用request.getInputStream()時,可以查看其Api,其返回的是ServletInputStream繼承于InputStream。

  1. public ServletInputStream getInputStream() throws IOException; 
  2.  
  3. public abstract class ServletInputStream extends InputStream { 
  4.     // ... 

下面我們來復習下流的知識:

InputStream的read方法內部有一個position,標志當前讀取到的位置,讀取到最后會返回-1,表示讀取完畢。如果想要重新讀取則需要使用mark和reset方法配合使用,把position移動到起始位置,就能從頭讀取實現多次讀取,但是InputStream和ServletInputStream都未重寫mark和reset方法。

所以就導致HttpServletRequest.getReader()或getInputStream()方法不能多次讀取。

解決辦法

使用HttpServletRequestWrapper,此類是HttpServletRequest的包裝類,基于裝飾器模式實現HttpServletRequest功能擴展。我們可以通過繼承包裝類HttpServletRequestWrapper來實現自定義擴展功能。

  • 我們重新定義一個容器(字節數組),把讀取到的流數據存儲其中供以后多次使用。
  • 重寫getReader()和getInputStream()方法,改為每次從自定義容器中獲取內容。
  • 再配合Filter把原始的HttpServletRequest替換為我們自定義的包裝類xxxHttpServletRequestWrapper。

代碼如下:

  • CachedBodyHttpServletRequestWrapper.java
  1. public class CachedBodyHttpServletRequestWrapper extends HttpServletRequestWrapper { 
  2.     private byte[] cachedBody; 
  3.     public CachedBodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException { 
  4.         super(request); 
  5.         InputStream requestInputStream = request.getInputStream(); 
  6.         this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); 
  7.     } 
  8.     @Override 
  9.     public ServletInputStream getInputStream() throws IOException { 
  10.         return new CachedBodyServletInputStream(this.cachedBody); 
  11.     } 
  12.     @Override 
  13.     public BufferedReader getReader() throws IOException { 
  14.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); 
  15.         return new BufferedReader(new InputStreamReader(byteArrayInputStream)); 
  16.     } 
  17.     public class CachedBodyServletInputStream extends ServletInputStream { 
  18.         private InputStream cachedBodyInputStream; 
  19.         public CachedBodyServletInputStream(byte[] cachedBody) { 
  20.             this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); 
  21.         } 
  22.         @Override 
  23.         public int read() throws IOException { 
  24.             return cachedBodyInputStream.read(); 
  25.         } 
  26.         // ... 
  27.     } 
  • ContentCachingFilter.java
  1. @Order(value = Ordered.HIGHEST_PRECEDENCE) 
  2. @Component 
  3. @WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*"
  4. public class ContentCachingFilter extends OncePerRequestFilter { 
  5.  
  6.     @Override 
  7.     protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 
  8.         System.out.println("IN  ContentCachingFilter "); 
  9.         CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest); 
  10.         filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse); 
  11.     } 

擴展思考

1.是否存在線程安全問題?

實測結果如下圖,非單例,不存在線程安全問題。

 

2.加載順序問題?

ContentCachingFilter必須在Filter鏈中的第一個,否則后面使用的是非自定義包裝類而是默認的HttpServletRequest,將無法起作用。

3.OncePerRequestFilter和Filter的區別

OncePerRequestFilter 實現了 Filter 接口。

  1. OncePerRequestFilter extends GenericFilterBean implements Filter{ 

在Spring中,Filter默認繼承OncePerRequestFilter。

 

OncePerRequestFilter:顧名思義,它能夠確保在一次請求中只通過一次filter,而需要重復的執行。大家常識上都認為,一次請求本來就只filter一次,為什么還要由此特別限定呢。

往往我們的常識和實際的實現并不真的一樣,經過一番資料的查閱,此方法是為了兼容不同的web container,也就是說并不是所有的container都入我們期望的只過濾一次,servlet版本不同,執行過程也不同,我們可以看看Spring的javadoc怎么說:

  1. * <p>As of Servlet 3.0, a filter may be invoked as part of a 
  2. * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or 
  3. * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in 
  4. * separate threads. A filter can be configured in {@code web.xml} whether it 
  5. * should be involved in async dispatches. However, in some cases servlet 
  6. * containers assume different default configuration.  

簡單的說就是去適配了不同的web容器,以及對異步請求,也只過濾一次的需求。另外打個比方:如:servlet2.3與servlet2.4也有一定差異:

在servlet2.3中,Filter會經過一切請求,包括服務器內部使用的forward轉發請求和<%@ include file=”/login.jsp”%>的情況 servlet2.4中的Filter默認情況下只過濾外部提交的請求,forward和include這些內部轉發都不會被過濾,因此此處我有個建議:我們若是在Spring環境下使用Filter的話,個人建議繼承OncePerRequestFilter吧,而不是直接實現Filter接口。這是一個比較穩妥的選擇

參考:

https://cloud.tencent.com/developer/article/1497822

 

責任編輯:武曉燕 來源: Java大廠面試官
相關推薦

2021-04-28 16:10:48

開發腳手架 Spring

2021-06-02 17:58:49

腳手架 冪等性前端

2021-05-13 17:02:38

MDC腳手架日志

2021-07-13 18:42:38

Spring Boot腳手架開發

2021-04-13 14:47:53

認證授權Java

2021-07-29 18:49:49

Spring開發腳手架

2020-08-19 08:55:47

Redis緩存數據庫

2021-03-11 14:16:47

Spring Boo開發腳手架

2021-04-20 19:24:16

腳手架 Java微信

2021-09-01 10:07:43

開發零搭建Groovy

2021-03-09 17:11:09

數據庫腳手架開發

2016-08-10 14:59:41

前端Javascript工具

2021-01-07 05:34:07

腳手架JDK緩存

2023-11-21 17:36:04

OpenFeignSentinel

2018-06-11 14:39:57

前端腳手架工具node.js

2018-08-30 16:08:37

Node.js腳手架工具

2014-08-15 09:36:06

2022-07-18 07:58:46

Spring工具工具類

2021-12-23 10:35:32

SpringCloud腳手架架構

2016-09-07 15:35:06

VueReact腳手架
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 99久久婷婷国产综合精品电影 | 成人在线 | 欧美一区二区三区小说 | 久久狠狠 | 黄色三级毛片 | 涩涩视频网站在线观看 | 精品少妇一区二区三区日产乱码 | 国产精选一区 | 欧美激情a∨在线视频播放 成人免费共享视频 | 天天干在线播放 | 伊人精品视频 | 国产精品久久 | 99精品国自产在线观看 | 亚洲精品乱码久久久久久9色 | 日韩视频在线免费观看 | 91久久精品日日躁夜夜躁欧美 | 午夜爱爱毛片xxxx视频免费看 | 国产男女精品 | 成人亚洲 | 久久草视频 | 免费一级淫片aaa片毛片a级 | 日韩一区二区在线视频 | 亚洲国产日本 | 91精品麻豆日日躁夜夜躁 | 在线观看中文视频 | 久久专区 | 国产激情一区二区三区 | 99精品99久久久久久宅男 | 久草视频观看 | 国产精品1区2区 | 国产精彩视频在线观看 | 国精品一区 | 午夜影院在线免费观看视频 | 性一交一乱一透一a级 | 亚洲视频欧美视频 | 一区二区视屏 | 黄色成人在线 | av资源在线看 | 91麻豆精品国产91久久久久久久久 | 伊人影院99 | 91成人在线视频 |