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