天天看點

輸入過濾器——InputFilter

一般情況下我們通過請求體讀取器InputStreamInputBuffer擷取的僅僅是源資料,即未經過任何處理發送方發來的位元組。但有些時候在這個讀取的過程中希望做一些額外的處理,并且這些額外處理可能是根據不同條件做不同的處理,考慮到程式解耦與擴充,于是引入過濾器(過濾器模式)——輸入過濾器InputFilter。在讀取資料過程中對于額外的操作隻需要通過添加不同的過濾器即可實作,例如添加對http1.1協定分塊傳輸的相關操作的過濾器。

如下圖,在套接字輸入緩沖裝置中,從作業系統底層讀取的位元組會緩沖在buf中,請求行和請求頭部被解析後緩沖區buf的指針指向請求體起始位置,通過請求體讀取器InputStreamInputBuffer可進行讀取操作,它會自動判定buf是否已經讀完,讀完則重新從作業系統底層讀取位元組到buf。當其他元件從套接字輸入緩沖裝置讀取請求體時,裝置将判定是否包含過濾器,假設有則通過一層層的過濾器完成過濾操作後才能到desBuf,這個過程就像被加入了一道道處理關卡,經過關卡都會被執行相應操作,最終完成源資料到目的資料的操作。

輸入過濾器——InputFilter

過濾器是一種設計模式,在Java的各種架構及容器都有頻繁地使用以達到更好的擴充性和邏輯解耦。往下用一個例子看看過濾器如何工作。

① 輸入緩沖接口InputBuffer,提供讀取操作:

public interface InputBuffer {

   public int doRead(byte[] chunk) throws IOException;

}

② 輸入過濾器接口InputFilter,繼承InputBuffer類,額外提供setBuffer方法設定前一個緩沖:

public interface InputFilter extends InputBuffer {

public void setBuffer(InputBuffer buffer);

③ 輸入緩沖裝置,模拟通過請求體讀取器InputStreamInputBuffer從操作底層擷取請求體位元組數組,并且裡面包含了若幹個過濾器,緩沖裝置在執行讀取操作時會自動判斷是否有過濾器,如存在則将讀取後的位元組在經過層層過濾,得到最終的目的資料。

public class InternalInputBuffer implements InputBuffer {

boolean isEnd = false; 

byte[] buf = new byte[4]; 

protected int lastActiveFilter = -1; 

protected InputFilter[] activeFilters = new InputFilter[2]; 

InputBuffer inputStreamInputBuffer = (InputBuffer) new InputStreamInputBuffer();

public void addActiveFilter(InputFilter filter) {

if (lastActiveFilter == -1) {

filter.setBuffer(inputStreamInputBuffer);

} else {

for (int i = 0; i <= lastActiveFilter; i++) {

if (activeFilters[i] == filter)

return;

filter.setBuffer(activeFilters[lastActiveFilter]);

activeFilters[++lastActiveFilter] = filter; 

public int doRead(byte[] chunk) throws IOException {

if (lastActiveFilter == -1)

return inputStreamInputBuffer.doRead(chunk);

else

return activeFilters[lastActiveFilter].doRead(chunk);

protected class InputStreamInputBuffer implements InputBuffer {

if (isEnd == false) {

buf[0] = 'a';

buf[1] = 'b';

buf[2] = 'a';

buf[3] = 'd';

System.arraycopy(buf, 0, chunk, 0, 4);

isEnd = true;

return chunk.length;

return -1;

④ 清理過濾器ClearFilter,負責将讀取的位元組數組中的字元a換成f:

public class ClearFilter implements InputFilter {

protected InputBuffer buffer;

int i = buffer.doRead(chunk);

if (i == -1)

for (int j = 0; j < chunk.length; j++)

if (chunk[j] == 'a')

chunk[j] = 'f';

return i;

public InputBuffer getBuffer() {

return buffer;

public void setBuffer(InputBuffer buffer) {

this.buffer = buffer;

⑤ 大寫過濾器UpperFilter,負責将讀取的位元組數組全部變成大寫:

public class UpperFilter implements InputFilter {

chunk[j] = (byte) (chunk[j] - 'a' + 'A');

⑥ 測試類,建立輸入緩沖裝置,接着建立清理過濾器和大寫過濾器,把它們添加到輸入緩沖裝置,執行讀取操作,出來的就是經過兩個過濾器處理後的資料了,結果為“FBFD”,如果有其他處理需求通過實作InputFilter接口編寫過濾器并添加即可。

public class Test {

public static void main(String[] args) {

InternalInputBuffer internalInputBuffer = new InternalInputBuffer();

ClearFilter clearFilter = new ClearFilter();

UpperFilter upperFilter = new UpperFilter();

internalInputBuffer.addActiveFilter(clearFilter);

internalInputBuffer.addActiveFilter(upperFilter);

byte[] chunk = new byte[4];

try {

int i = 0;

while (i != -1) {

i = internalInputBuffer.doRead(chunk);

break;

} catch (IOException e) {

e.printStackTrace();

System.out.println(new String(chunk));

由于篇幅問題,上面過程已經盡量模拟描述tomcat輸入緩沖的工作流程,但實際使用的過濾器并非上面所述,主要包含四個過濾器:IdentityInputFilter、VoidInputFilter、BufferedInputFilter、ChunkedInputFilter。IdentityInputFilter過濾器在http包含content-length頭部并且指定的長度大于0時使用,它将根據指定的長度從底層讀取響應長度的位元組數組,當讀取足夠資料後将直接傳回-1,避免再次執行底層操作;VoidInputFilter過濾器用于攔截讀取底層資料操作,當http不包含content-length頭部時說明沒有請求體,沒必要執行讀取套接字底層操作,是以用這個過濾器攔截;BufferedInputFilter過濾器負責讀取請求體并将其緩存起來,後面讀取請求體時直接從此緩沖區讀取;ChunkedInputFilter過濾器專門用于處理分塊傳輸,分塊傳輸是一種資料傳輸機制,當沒有指定content-length時可通過分塊傳輸完成通信。

以上就是tomcat的套接字緩沖裝置的過濾器的機制及其實作方法,并且簡單介紹了tomcat中不同的過濾器的功能,過濾器模式讓tomcat在後期程式擴充更新變得更容易。

<a target="_blank" href="https://item.jd.com/12185360.html">點選訂購作者《Tomcat核心設計剖析》</a>