在Springboot
的項目中使用Servlet
的Filter
來實現方法簽名時,發現ServletInputStream
不支持屢次讀取流。apache
雖然網上有不少解決方案的例子,可是我發現沒有一篇文章解釋爲何會這樣的文章,因此決定本身去研究源碼。tomcat
首先確定是研究ServletInputStream
這個類了,卻發現這個類只是一個抽象類,它繼承了InputStream
這個類。服務器
那麼首先研究ServletInputStream
,卻發現惟一和流讀取的方法readLine()
並未限制流進行重複讀取。eclipse
既然這樣,那限制流重複讀取的緣由是不是在InputStream
中呢?ide
卻在InputStream
中發現了其實流是支持重複讀取的相關方法定義:this
mark()
標記當前流讀取的位置reset()
重置流到mark()
所標記的位置markSupported()
是否支持標記既然不是因爲ServletInputStream
引發的,那隻好辛苦點,調試整個請求的鏈路了。設計
全鏈路跟蹤調試後,總算是發現了端倪,在AbstractMessageConverterMethodArgumentResolver
中發現了關鍵方法readWithMessageConverters()
,關鍵代碼以下調試
@Nullable protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { ...省略非關鍵代碼... EmptyBodyCheckingHttpInputMessage message; try { // 此處爲關鍵代碼 message = new EmptyBodyCheckingHttpInputMessage(inputMessage); ...省略非關鍵代碼... } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } ...省略非關鍵代碼... return body; }
在上面代碼中EmptyBodyCheckingHttpInputMessage
這個類就是關鍵類,而這個關鍵實際上是AbstractMessageConverterMethodArgumentResolver
的內部類,關鍵代碼以下code
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException { this.headers = inputMessage.getHeaders(); InputStream inputStream = inputMessage.getBody(); // 判斷InputStream支持mark() if (inputStream.markSupported()) { // 在InputStream起始位置進行標記 inputStream.mark(1); // 若是InputStream不爲空則賦值 this.body = (inputStream.read() != -1 ? inputStream : null); // 重置流,表示流能夠進行重複讀取 inputStream.reset(); } else { // PushbackInputStream是一個支持重複讀取的流 PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream); int b = pushbackInputStream.read(); if (b == -1) { // 爲-1表示流中沒有數據 this.body = null; } else { this.body = pushbackInputStream; // 回退操做,使InputStream能夠進行重複讀取 pushbackInputStream.unread(b); } } }
從上面的代碼能夠看出,其實Spring MVC
對於ServletInputStream
是支持重複讀的(關於PushbackInputStream
的源碼這裏不進行展開)。可是爲何會出現ServletInputStream
不能重複讀取的狀況呢?server
因而我又再次進行調試,總算髮現了問題在於應用服務器上,因爲我調試的代碼是用SpringBoot
的,使用的應用服務器是tomcat
。
在tomcat
的org.apache.catalina.connector.Request
實現了HttpServletRequest
,咱們首先要關注其實現的getInputStream()
方法,關鍵代碼以下
/** * ServletInputStream */ protected CoyoteInputStream inputStream = new CoyoteInputStream(inputBuffer); // ...省略非關鍵代碼... @Override public ServletInputStream getInputStream() throws IOException { ...省略非關鍵代碼... if (inputStream == null) { // 關鍵代碼 inputStream = new CoyoteInputStream(inputBuffer); } return inputStream; }
從上面的關鍵代碼能夠得知,實際返回ServletInputStream
實際上是CoyoteInputStream
,繼續研究CoyoteInputStream
後發現其內部實際上是使用一個InputBuffer
對象來存儲實際的流數據,關鍵代碼以下:
/** * 實際存儲的數據 */ protected InputBuffer ib; @Override public int read() throws IOException { checkNonBlockingRead(); if (SecurityUtil.isPackageProtectionEnabled()) { ...省略非關鍵代碼... } else { // 關鍵代碼 return ib.readByte(); } }
從上面的關鍵代碼能夠得知,實際上對於流的讀取仍是使用了org.apache.catalina.connector.InputBuffer
的readByte()
方法,InputBuffer
的關鍵代碼以下:
/** * The byte buffer. */ private ByteBuffer bb; ...省略非關鍵代碼... public int readByte() throws IOException { if (closed) { throw new IOException(sm.getString("inputBuffer.streamClosed")); } // 關鍵代碼 if (checkByteBufferEof()) { return -1; } return bb.get() & 0xFF; } private boolean checkByteBufferEof() throws IOException { if (bb.remaining() == 0) { int n = realReadBytes(); if (n < 0) { return true; } } return false; }
後續不進行展開,由於tomcat
的調用關係特別複雜。可是能夠肯定了ServletInputStream
不支持屢次讀取是因爲tomcat
引發的。
後續我調試跟蹤了jetty
和undertow
,下面會提供關鍵類及關鍵方法,有興趣的朋友能夠自行斷點調試。
jetty
也是不支持ServletInputStream
屢次讀取,關鍵類及關鍵方法爲org.eclipse.jetty.server.HttpInput
的read()
方法
jetty
也是不支持ServletInputStream
屢次讀取,關鍵類及關鍵方法爲io.undertow.servlet.spec
的read()
方法
爲何應用服務器都將ServletInputStream
設計爲不可重複讀取?