解決okhttp沒法重用鏈接的問題

解決okhttp沒法重用鏈接的問題服務器

最近在一個程序中使用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

StackOverflow問題鏈接調試

相關文章
相關標籤/搜索