最近在一個程序中使用okhttp調用http接口。開始時一切正常,可是測試運行一段時間後,okhttp就會報告recv失敗。同時在調用端機器上,netstat顯示不少套接字是TIMEWAIT狀態。原來每次調用接口,okhttp都創建了一個新鏈接。而被調用的服務器在鏈接超過必定數量後會拒絕服務。網絡
最初的想法是用鏈接池下降鏈接數。app
OkHttpClient httpClient = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(5, 20, TimeUnit.SECONDS)) .build();
但是運行一段時間後,又出現了recv失敗和大量的TIMEWAIT。鏈接池方法無效。爲何會這樣呢?上網搜索一番,發現StackOverflow上有人提到,若是Request或Response的頭部包含Connection: close,okhttp會關閉鏈接。下斷點調試,果真服務器返回了Connection: close。okhttp的CallServerInterceptor在收到應答後,直接關閉了鏈接。ide
要怎麼處理這種狀況呢?直觀的想法是用攔截器攔截應答,覆蓋http頭。測試
OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { // overwrite header } }) .build();
但是在攔截器收到應答以前,CallServerInterceptor已經將鏈接斷開。此路不通。不過在調試過程當中,發現OkHttpClient.Builder還有一個addNetworkInterceptor()方法。爲何會有兩種類型的攔截器呢?原來addInterceptor()攔截器在構造請求以前調用,addNetworkInterceptor()在創建網絡鏈接、發送請求以前調用。addNetworkInterceptor()攔截器能夠拿到HttpCodec對象,後者正是解析http應答的類。所以產生了一個想法,替換HttpCodec對象,在解析http應答的時候修改http頭。ui
public class HttpCodecWrapper implements HttpCodec { private HttpCodec codec; public HttpCodecWrapper(HttpCodec codec) { this.codec = codec; } // ... @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException { // 覆蓋Connection,避免CallServerInterceptor關閉鏈接。 return codec.readResponseHeaders(expectContinue) .addHeader("Connection", "keep-alive"); } } OkHttpClient httpClient = new OkHttpClient.Builder() .addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation allocation = realChain.streamAllocation(); HttpCodec codec = new Http1CodecWrapper(realChain.httpStream()); RealConnection connection = (RealConnection) realChain.connection(); return realChain.proceed(request, allocation, codec, connection); } }) .build();
覆蓋Connection頭後,鏈接沒有斷開,能夠正常重用。this