請謹慎使用 avaliable 方法來申請緩衝區

楷哥發表於2021-01-26

問題

今天開始嘗試用 Java 寫 http 伺服器,開局就遇到 Bug。

我先寫了一個多執行緒的、BIO 的 http 伺服器,其中接收請求的部分,會將請求的第一行列印出來。

下面是瀏覽器發出的請求和控制檯的輸出情況。我們竟然收到了一個空的請求!!這是為什麼呢?

我解析請求的部分程式碼如下。

// request
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] buffer = new byte[bis.available()];
int len = bis.read(buffer);
String firstLine = new String(buffer).split("\n")[0];
System.out.println("".equals(firstLine) ? "EMPTY" : firstLine);

這是為什麼呢?我們先打個斷點看看是個什麼狀況。

重新整理瀏覽器,重新請求一下試試。驚奇的發現,bug 消失了。。

如果把斷點打在後面幾行,bug 又出現了。

emmm,太迷了。

分析

我們仔細看看上面第二個斷點的截圖,我們會發現 buffer 的值為 {},這意味著申請的空間是 0!這就意味著 available 返回的是 0。

這個函式的註釋中寫到,返回一個可以被讀取的位元組數的估計值。這個估計值並不是實際的長度,在網路請求中,這個值有可能是 0,因此,我們申請的空間就是 0,之後呼叫 read 方法自然也讀不到任何東西。

呼叫鏈是這樣子的:

解決方法

我們自己開一個固定大小的緩衝區,然後讀取就好了,read 方法會阻塞直到資料流進來。

// request
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] buffer = new byte[BUFFER_SIZE];
int len = bis.read(buffer);
System.out.println(new String(buffer).split("\n")[0]);

那這個方法就沒有問題了嗎?其實還是有問題的,比如 POST 提交檔案的時候,顯然檔案的大小並不是只有 BUFFER_SIZE 那麼小而已。我們還需要想辦法來獲取一個未知長度的 InputStream。未知長度的處理方法,可以使用 timeout 來做,如果過了一段時間還沒有讀入資料,那就不讀了。比較穩妥的方法是,在請求頭裡面寫好長度,這樣就能知道要讀多少了。

相關文章