HttpsURLConnection可能會在ReadTimeout時自動重新嘗試


JavaURLConnection有兩種逾時(Timeout)設定,分別是建立連結(Connect)時的逾時-ConnectTimeout,以及傳遞串流資料(Stream)時的逾時-ReadTimeout。當Timeout發生時,URLConnection會拋出SocketTimeoutException,並立即中止連線。但是最近筆者發現HttpsURLConnection(或是URLConnection本身)使用POST Request(或是其他Request)發生ReadTimeout(或是ConnectTimeout)時,在拋出SocketTimeoutException前又會自動重新重新嘗試(Retry)同樣的POST Request,導致伺服器出現了兩次同樣的Request紀錄。

發生過程

事情是這樣的發生的,筆者在Android上使用HttpsURLConnection傳送POST Request給Server,但是Server處理Request時花的時間太長,太久沒有回應,使得HttpsURLConnection拋出SocketTimeoutException。理論上連線應該只有一次才對,但是Server竟然收到兩次同樣的Request。而且第二次的Request,似乎是在別的執行緒(未知的執行緒)上執行的,因此無法得知Request的結果。第二次的Request不管有沒有成功,都不會再有第三次的Request。

推測原因

單就問題的發生過程來看,AndroidJava Runtime環境可能有BUG,會導致HttpsURLConnection傳送POST Request時,在因發生ReadTimeout而拋出SocketTimeoutException前,自動以未知的執行緒重新嘗試傳送同樣的Request。

最遭的情況是,任意平台下的Java Runtime環境可能都有這個BUG,會導致URLConnection傳送任意的Request時,在因發生ReadTimeoutConnectTimeout而拋出SocketTimeoutException前,自動以未知的執行緒重新嘗試傳送同樣的Request。

但是筆者在PC上以HttpURLConnection來測試ReadTimeout,並沒有發生Retry的狀況,所以也許情況並沒有那麼遭。

Google搜尋了一下,在Stack Overflow上也有人遇到一樣的問題,請參考以下圖片:
HttpsURLConnection可能會在ReadTimeout時自動重新嘗試

處理方式與結論

Stack Overflow上發問者的解決方式是使用Apache的HttpClient來代替HttpsURLConnection,HttpClient能夠設定是否要Retry。但如果還是要使用HttpsURLConnection的話,建議手動實作Timeout,避免在SocketTimeoutException時發出兩次同樣的Request,程式如下:

使用ByteArrayOutputStream可以避免InputStreamReader可能造成的BUG,詳細請看這篇

關於作者

Magic Len

各位好,我是Magic Len,是這網站的管理員。我是台灣台中大肚山上人,畢業於台中高工資訊科和台灣科技大學資訊工程系,曾在桃機航警局服役。我熱愛自然也熱愛科學,喜歡和別人分享自己的知識與經驗。如果你有興趣認識我,可以加我的Facebook,並且請註明是從MagicLen來的。

相關文章