解决okhttp无法重用连接的问题

旧城冷巷雨未停 提交于 2020-08-12 04:52:17

解决okhttp无法重用连接的问题

最近在一个程序中使用okhttp调用http接口。开始时一切正常,但是测试运行一段时间后,okhttp就会报告recv失败。同时在调用端机器上,netstat显示很多套接字是TIMEWAIT状态。原来每次调用接口,okhttp都建立了一个新连接。而被调用的服务器在连接超过一定数量后会拒绝服务。

最初的想法是用连接池降低连接数。

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在收到应答后,直接关闭了连接。

要怎么处理这种情况呢?直观的想法是用拦截器拦截应答,覆盖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头。

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头后,连接没有断开,可以正常重用。

StackOverflow问题连接

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!