OkHttp 源码之拦截器

1,069 阅读18分钟

前言

在 Android 开发中,需要网络请求的时候我们一般都选择使用 OkHttp。它的功能特别强大,比如拦截器,自动重连,请求缓存策略,socket 连接池,响应数据 gzip 压缩,HTTP2,文件上传和下载,Cookie 持久化,websocket 等等。下面就从拦截器角度出发学习下它。

拦截器

为什么 OkHttp 要使用拦截器呢?我们想想,一个网络请求是从发起 Request 到得到 Response 的过程,这个过程里面要考虑到重定向,Header 字段处理,请求缓存问题,连接池复用,TCP/TLS,Response 的读写。这个过程任何一个步骤出现问题了,都要尝试自动重连。这是不是特别适合责任链模式呢?那在 OkHttp 里面这些角色分别对应着:控制权的对象是实现 Interceptor 接口的对象,它们要承担起处理各个流程的责任,然后请求者就是 Chain 接口的实现类。那实现的关系图就是:

image.png

如果按自己实现步骤代码简化下会是

interface Chain {
    fun request():Request
}

class RealInterceptorChain(private var originRequest: Request) : Chain {
    override fun request(): Request {
        return originRequest
    }
}

abstract class Interceptor(private var next: Interceptor?) {
    abstract fun intercept(chain: Chain): Response?
    fun setNext(next: Interceptor?) {
        this.next = next
    }

    protected fun getNext(): Interceptor? {
        return next
    }
}

class RetryAndFollowUpInterceptor(next: Interceptor? = null) : Interceptor(next) {
    override fun intercept(chain: Chain): Response? {
        val request = chain.request()
        println("RetryAndFollowUpInterceptor 处理")
        if (request.type == 1) {
            return Response("RetryAndFollowUpInterceptor say ok")
        }
        return getNext()?.intercept(chain)
    }
}
//..其他 interceptor 类似,省略..

执行测试代码:

fun main() {
    val chain = RealInterceptorChain(Request(5))
    val list = listOf(
        RetryAndFollowUpInterceptor(),
        BridgeInterceptor(), CacheInterceptor(), ConnectInterceptor(), CallServerInterceptor()
    )
    list.forEachIndexed { index, interceptor ->
        interceptor.setNext(list.getOrNull(index + 1))
    }
    println("得到响应:${list.first().intercept(chain)?.info}")
}

得到执行结果:

RetryAndFollowUpInterceptor 处理
BridgeInterceptor 处理
CacheInterceptor 处理
ConnectInterceptor 处理
CallServerInterceptor 处理
得到响应:CallServerInterceptor say ok

通过上面实现步骤会发现,每个控制对象要指明下个询问的控制对象是谁,不太好,怎么解决呢?OkHttp 是在 Chain 里面加了一个方法来调用下个控制对象的 intercept 方法,这个方法就是 proceed 方法

interface Chain {
  Request request();

  Response proceed(Request request) throws IOException;
}

那上面代码就可以换成

interface Chain {
    fun request(): Request
    fun proceed(request: Request): Response?
}

class RealInterceptorChain(
    private var originRequest: Request,
    private var interceptors: List<Interceptor>,
    private var index: Int
) : Chain {

    override fun request(): Request {
        return originRequest
    }

    override fun proceed(request: Request): Response? {
        val next = RealInterceptorChain(originRequest, interceptors, index + 1)
        return interceptors.getOrNull(index)?.intercept(next)
    }
}

interface Interceptor {
    fun intercept(chain: Chain): Response?
}

class RetryAndFollowUpInterceptor : Interceptor {
    override fun intercept(chain: Chain): Response? {
        val request = chain.request()
        println("RetryAndFollowUpInterceptor 处理")
        if (request.type == 1) {
            return Response("RetryAndFollowUpInterceptor say ok")
        }
        return chain.proceed(request)
    }
}

fun main() {
    val list = listOf(
        RetryAndFollowUpInterceptor(),
        BridgeInterceptor(), CacheInterceptor(), ConnectInterceptor(), CallServerInterceptor()
    )
    val originRequest = Request(5)
    val chain = RealInterceptorChain(originRequest,list,0)
    println("得到相应:${chain.proceed(originRequest)?.info}")
}

那有个问题 proceed 里面可不可以不 new 一个 RealInterceptorChain,而是直接通过 index++ 来实现呢?如果每个链调用 proceed 一次是没问题的,但一个链多调用几遍就对应不上了了,所以这里给每个链配置一个对应 index 的链。拦截器的责任链模式大概就说到这里。

接下来就来看看 OkHttp 的拦截器在哪里以及它们分别干了什么工作?在拦截器之前,OkHttp还做了大量工作,这里拿一次异步请求来说:

val mOkHttpClient: OkHttpClient = OkHttpClient.Builder()
    .connectTimeout(20, TimeUnit.SECONDS)
    .writeTimeout(20, TimeUnit.SECONDS).build()
val request =
    Request.Builder().url("https://www.wanandroid.com/article/list/0/json").build()
mOkHttpClient.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {

    }

    override fun onResponse(call: Call, response: Response) {
        val result = response.body()?.string()
        Log.d("goach", "result========$result")
    }

})

我们一般会在 OkHttpClient.Builder 配置请求读写超时时间,自己的应用拦截器和网络拦截器和同时连接的个数和时间等待,在 Request.Builder 配置请求的基本信息,比如请求 url,请求头信息等等。再加两个队列 runningAsyncCalls 和 readAsyncCalls 分别缓存执行中的任务和等待中的任务,然后用线程池执行任务,接着就来到了拦截器部分。大概流程如下:

image.png

前期工作准备好后,就来到了拦截器模块了。首先第一个就是 RetryAndFollowUpInterceptor 拦截器,它是干嘛的呢?

public class RetryAndFollowUpInterceptor implements Interceptor {
    @Nullable
    @Override
    public Response intercept(@NotNull Chain chain) {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        //这个 transmitter 在  newRealCall 时候创建的,它是应用层和网络层的桥梁,保存着 client 和 RealCall,connection,
        //连接,请求 <------> 响应,响应体流的读写都要经过它
        Transmitter transmitter = realChain.transmitter();

        int followUpCount = 0;
        Response priorResponse = null;
        while (true) {//通过 while(true) + try catch 方式尝试自动重连
            //这里面判断了请求 url 是不是和上一个请求 url 一样,如果一样并且还有路由可以尝试,也就是请求连接还存在,就不用再去创建了,否则创建一个
            //ExchangeFinder,它持有了 Transmitter(桥梁),连接池,
            // Address(请求url的信息),RealCall,eventListener 事件监听(默认空实现)
            transmitter.prepareToConnect(request);
            //当外部已经调用了 RealCall 的 cancel 方法,那就抛出异常取消
            if (transmitter.isCanceled()) {
                throw new IOException("Canceled");
            }

            Response response;
            boolean success = false;
            try {
                //这里走下一个拦截器,后面任何一个拦截器抛出异常,都会在这里捕获到,然后判断是否尝试重新请求
                response = realChain.proceed(request, transmitter, null);
                success = true;
            } catch (RouteException e) {
                // 路由异常,尝试再与服务器通信失败时候恢复,这时候还没发出去请求,所以直接传false,
                // 想要恢复需要满足几个条件,在 OkHttpClient.Builder 里面配置 OkHttpClient 时候没禁用重试,请求还没发出去,
                // 不是协议异常,IO流异常,SSL 证书问题,并且还有其他路由可以重试情况下,那就可以恢复过来
                if (!recover(e.getLastConnectException(), transmitter, false, request)) {
                    throw e.getFirstConnectException();
                }
                continue;
            } catch (IOException e) {
                // 尝试与服务器通信失败。出现了IO异常,请求可能已发送出去了。
                boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
                if (!recover(e, transmitter, requestSendStarted, request)) throw e;
                continue;
            } finally {
                // 网络调用引发了异常。释放任何资源。
                if (!success) {
                    transmitter.exchangeDoneDueToException();
                }
            }

            // 这里重定向过来时候才有值,设置上个响应体。并且它是不会有 body 体的。
            if (priorResponse != null) {
                response = response.newBuilder()
                        .priorResponse(priorResponse.newBuilder()
                                .body(null)
                                .build())
                        .build();
            }
            //这个 Exchange 在 ConnectInterceptor 的 intercept 里面会通过 transmitter 的 newExchange 创建出来,
            //然后赋值给 RealInterceptorChain,传给了后面的拦截器,这样后面的拦截器 exchange 就有值了
            //在 CallServerInterceptor.readResponseHeaders 通过 initExchange 赋值给了 response
            //这里拿的就是 response 的 exchange,它是把请求 Request 转换成 Response 的作用,
            // 里面持有的 ExchangeCodec 处理了响应体的 IO 流操作
            Exchange exchange = Internal.instance.exchange(response);
            Route route = exchange != null ? exchange.connection().route() : null;
            //处理重定向
            Request followUp = followUpRequest(response, route);

            if (followUp == null) {
                if (exchange != null && exchange.isDuplex()) {
                    transmitter.timeoutEarlyExit();
                }
                //没有重定向,一切状态都好,返回响应体处理
                return response;
            }

            RequestBody followUpBody = followUp.body();
            //如果有重定向,又只让请求一次,这个 isOneShot 可以自定义 RequestBody 体里面重写这个方法设置,设置为true,就代表着 body 体只能让请求一次。
            if (followUpBody != null && followUpBody.isOneShot()) {
                return response;
            }

            closeQuietly(response.body());
            if (transmitter.hasExchange()) {
                exchange.detachWithViolence();
            }
            //默认支持 20 次的重定向
            if (++followUpCount > MAX_FOLLOW_UPS) {
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
            }
            //赋值,重定向进入下一轮轮询进行请求
            request = followUp;
            priorResponse = response;
        }
    }

    //https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
    private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
        if (userResponse == null) throw new IllegalStateException();
        int responseCode = userResponse.code();

        final String method = userResponse.request().method();
        switch (responseCode) {
            case HTTP_PROXY_AUTH://407 客户端必须在代理服务器上进行身份验证(与之对应的是401)。
                // 代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头给服务器用以验证
                Proxy selectedProxy = route != null
                        ? route.proxy()
                        : client.proxy();
                if (selectedProxy.type() != Proxy.Type.HTTP) {
                    throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
                }
                return client.proxyAuthenticator().authenticate(route, userResponse);

            case HTTP_UNAUTHORIZED://401  未授权,或认证失败。对于需要登录的网页,服务器可能返回此响应
                return client.authenticator().authenticate(route, userResponse);

            case HTTP_PERM_REDIRECT://308,永久性重定向,方法和消息主体都不发生变化(与之对应的是 301),不能用于 GET 和 HEAD 方法重定向,
            case HTTP_TEMP_REDIRECT://307,临时重定向,方法和消息主体都不发生变化(与之对应的是302),通常不用于 GET 和 HEAD 方法。
                // "If the 307 or 308 status code is received in response to a request other than GET
                // or HEAD, the user agent MUST NOT automatically redirect the request"
                //非 GET 请求,和非 HEAD(只请求头部信息,比如使用在获取下载文件大小的情况) 请求
                if (!method.equals("GET") && !method.equals("HEAD")) {
                    return null;
                }
                // fall-through
            case HTTP_MULT_CHOICE://300 特殊重定向,手工设置重定向,包含了一个可能的重定向链接的列表,用户可以从中进行选择。
            case HTTP_MOVED_PERM://301,永久性重定向,GET方法不会被改变,但是其他方法(如POST)可以改变成GET方法
            case HTTP_MOVED_TEMP://302 临时性重定向,GET方法不会被改变,但是其他方法(如POST)可以改变成GET方法
            case HTTP_SEE_OTHER://303 临时性重定向,GET方法不会被改变,但是其他方法(如POST)会改变成GET方法
                // 配置是否允许重定向?
                if (!client.followRedirects()) return null;
                //Location 首部指定的是需要将页面重新定向至的地址
                String location = userResponse.header("Location");
                if (location == null) return null;
                HttpUrl url = userResponse.request().url().resolve(location);

                // 不支持重定向的协议.
                if (url == null) return null;

                // 如果协议不一样, 从 SSL(https) 到 non-SSL(http),不进行重定向
                boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
                if (!sameScheme && !client.followSslRedirects()) return null;

                // Most redirects don't include a request body.
                Request.Builder requestBuilder = userResponse.request().newBuilder();
                if (HttpMethod.permitsRequestBody(method)) {
                    final boolean maintainBody = HttpMethod.redirectsWithBody(method);
                    if (HttpMethod.redirectsToGet(method)) {//重定向到 GET 方法
                        requestBuilder.method("GET", null);
                    } else {
                        RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
                        requestBuilder.method(method, requestBody);
                    }
                    if (!maintainBody) {//GET  请求移除这些字段
                        requestBuilder.removeHeader("Transfer-Encoding");
                        requestBuilder.removeHeader("Content-Length");
                        requestBuilder.removeHeader("Content-Type");
                    }
                }

                // When redirecting across hosts, drop all authentication headers. This
                // is potentially annoying to the application layer since they have no
                // way to retain them.
                //跨主机重定向时,删除所有身份验证标头
                if (!sameConnection(userResponse.request().url(), url)) {
                    requestBuilder.removeHeader("Authorization");
                }

                return requestBuilder.url(url).build();

            case HTTP_CLIENT_TIMEOUT://408,请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。
                // 客户端可以随时再次提交这一请求而无需进行任何更改。
                // 408's are rare in practice, but some servers like HAProxy use this response code. The
                // spec says that we may repeat the request without modifications. Modern browsers also
                // repeat the request (even non-idempotent ones.)
                if (!client.retryOnConnectionFailure()) {
                    // The application layer has directed us not to retry the request.
                    return null;
                }

                RequestBody requestBody = userResponse.request().body();
                if (requestBody != null && requestBody.isOneShot()) {
                    return null;
                }

                if (userResponse.priorResponse() != null
                        && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
                    // We attempted to retry and got another timeout. Give up.
                    return null;
                }

                if (retryAfter(userResponse, 0) > 0) {
                    return null;
                }

                return userResponse.request();

            case HTTP_UNAVAILABLE://503,由于临时的服务器维护或者过载,服务器当前无法处理请求,如果能够预计延迟时间,那么响应中可以包含一个 Retry-After
                // 头用以标明这个延迟时间。如果没有给出这个 Retry-After 信息,那么客户端应当以处理500响应的方式处理它。500 一般是服务代码导致的
                if (userResponse.priorResponse() != null
                        && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
                    // We attempted to retry and got another timeout. Give up.
                    return null;
                }

                if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
                    // specifically received an instruction to retry without delay
                    return userResponse.request();
                }

                return null;

            default:
                return null;
        }
    }
}

这里面有个类特别重要 Transmitter ,它是从 OkHttp 配置的请求,连接到响应的一个桥梁,它里面持有了

  • OkHttpClient,用来拿到应用层请求的配置;
  • RealConnectionPool(连接池),用来为请求找到或创建一个连接,具体操作在 ExchangeFinder 类里面处理;
  • Exchange,获取响应体的一个桥梁,通过 HTTP1ExchangeCodec 或者 HTTP2ExchangeCodec 拿到响应体给 Response

通过上面代码注释总结下 RetryAndFollowUpInterceptor 做的事情

  • 将请求的地址信息保存到 ExchangeFinder 里面,为后面连接做准备
  • 通过一个 while(true) 加 try catch 方式尝试对未发出去的请求出现异常进行重连
  • 在 followUpRequest 方法里面处理重定向的响应码,默认支持 20 次重定向

接着就是 BridgeInteceptor 拦截器:

public final class BridgeInterceptor implements Interceptor {
    private final CookieJar cookieJar;

    public BridgeInterceptor(CookieJar cookieJar) {
        this.cookieJar = cookieJar;
    }

    @Override public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();

        RequestBody body = userRequest.body();
        if (body != null) {
            MediaType contentType = body.contentType();
            if (contentType != null) {
                //告诉服务器实际发送的数据类型,如application/json,application/x-www-form-urlencoded,multipart/form-data 等等
                requestBuilder.header("Content-Type", contentType.toString());
            }

            long contentLength = body.contentLength();
            if (contentLength != -1) {
                //HTTP消息长度,如果gzip压缩, 那 Content-Length 首部指的就是压缩后的大小而不是原始大小
                requestBuilder.header("Content-Length", Long.toString(contentLength));
                //不是 chunked 模式,即服务器一边产生数据,一边发送给客户端,移除 Transfer-Encoding
                requestBuilder.removeHeader("Transfer-Encoding");
            } else {
                //否则就是 chunked 模式
                requestBuilder.header("Transfer-Encoding", "chunked");
                requestBuilder.removeHeader("Content-Length");
            }
        }

        if (userRequest.header("Host") == null) {
            //host ,拼接域名+端口号
            requestBuilder.header("Host", hostHeader(userRequest.url(), false));
        }

        if (userRequest.header("Connection") == null) {
            //请求响应完后不关闭
            requestBuilder.header("Connection", "Keep-Alive");
        }

        // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
        // the transfer stream.
        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
            transparentGzip = true; //header 的编码格式,这里默认使用 gzip 压缩
            requestBuilder.header("Accept-Encoding", "gzip");
        }

        List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
        if (!cookies.isEmpty()) {
            //请求配置的 cookie
            requestBuilder.header("Cookie", cookieHeader(cookies));
        }

        if (userRequest.header("User-Agent") == null) {
            //访问网站提供你说使用的浏览器类型和版本,操作系统和版本 ,这里给的是 OkHttp 版本
            requestBuilder.header("User-Agent", Version.userAgent());
        }

        Response networkResponse = chain.proceed(requestBuilder.build());
        //把头部信息回传给 cookieJar
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

        Response.Builder responseBuilder = networkResponse.newBuilder()
                .request(userRequest);

        if (transparentGzip
                && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
                && HttpHeaders.hasBody(networkResponse)) {
            GzipSource responseBody = new GzipSource(networkResponse.body().source());
            Headers strippedHeaders = networkResponse.headers().newBuilder()
                    .removeAll("Content-Encoding")
                    .removeAll("Content-Length")
                    .build();
            responseBuilder.headers(strippedHeaders);
            String contentType = networkResponse.header("Content-Type");
            //对请求数据进行 gzip 压缩后给 response
            responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }

        return responseBuilder.build();
    }

    /** Returns a 'Cookie' HTTP request header with all cookies, like {@code a=b; c=d}. */
    private String cookieHeader(List<Cookie> cookies) {
        StringBuilder cookieHeader = new StringBuilder();
        for (int i = 0, size = cookies.size(); i < size; i++) {
            if (i > 0) {
                cookieHeader.append("; ");
            }
            Cookie cookie = cookies.get(i);
            cookieHeader.append(cookie.name()).append('=').append(cookie.value());
        }
        return cookieHeader.toString();
    }
}

它干的事情主要有:

  • 处理请求 header 字段,如 Content-Type,Content-Length,Connection 等等
  • header 编码格式采用 gzip 压缩,并且会对请求回来的数据进行 gzip 压缩
  • 通过 CookieJar 里面做的 cookie 操作就是在这个拦截器里面处理的,通过 loadForRequest 添加到头部 Cookie 字段,然后再 receiveHeaders 回调出去进行存储

再就是 CacheInteceptor :

public final class CacheInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;

        long now = System.currentTimeMillis();

        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;

        if (cache != null) {
            cache.trackResponse(strategy);
        }

        if (cacheCandidate != null && cacheResponse == null) {
            closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }

        // If we're forbidden from using the network and the cache is insufficient, fail.
        if (networkRequest == null && cacheResponse == null) {
            // only-if-cached 模式,如果缓存为空,就返回 504 。
            return new Response.Builder()
                    .request(chain.request())
                    .protocol(Protocol.HTTP_1_1)
                    .code(504)
                    .message("Unsatisfiable Request (only-if-cached)")
                    .body(Util.EMPTY_RESPONSE)
                    .sentRequestAtMillis(-1L)
                    .receivedResponseAtMillis(System.currentTimeMillis())
                    .build();
        }

        // If we don't need the network, we're done.
        if (networkRequest == null) {//有缓存时候,并且缓存有效,不需要网络,直接返回缓存
            return cacheResponse.newBuilder()
                    .cacheResponse(stripBody(cacheResponse))
                    .build();
        }

        Response networkResponse = null;
        try {
            networkResponse = chain.proceed(networkRequest);
        } finally {
            // If we're crashing on I/O or otherwise, don't leak the cache body.
            if (networkResponse == null && cacheCandidate != null) {
                closeQuietly(cacheCandidate.body());
            }
        }

        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
// 如果有缓存,并且 304,也就是请求数据没有发生改变,对缓存和网络请求进行合并
            if (networkResponse.code() == HTTP_NOT_MODIFIED) {
                Response response = cacheResponse.newBuilder()
                        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                        .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                        .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                        .cacheResponse(stripBody(cacheResponse))
                        .networkResponse(stripBody(networkResponse))
                        .build();
                networkResponse.body().close();

                // Update the cache after combining headers but before stripping the
                // Content-Encoding header (as performed by initContentStream()).
                cache.trackConditionalCacheHit();
                cache.update(cacheResponse, response);
                return response;
            } else {
                closeQuietly(cacheResponse.body());
            }
        }

        Response response = networkResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();

        if (cache != null) {//对有效的缓存存储,对无效的缓存删除
            if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                // Offer this request to the cache.
                CacheRequest cacheRequest = cache.put(response);
                return cacheWritingResponse(cacheRequest, response);
            }

            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                    // The cache cannot be written.
                }
            }
        }

        return response;
    }
}
  • 它只针对 GET 请求进行缓存,如果 only-If-cached 模式,没有缓存就会返回 504,
  • 有缓存,就会直接返回缓存的 Response,没有缓存则会进行网络请求,
  • 请求返回 304,那就请求数据没有发生改变,那会对请求的数据和缓存进行合并返回 Response
  • 请求返回的不是 304,那就更新缓存,对无效的缓存进行删除,并且返回 Response

再就是 ConnectInteceptor 拦截器:

public final class ConnectInterceptor implements Interceptor {
    public final OkHttpClient client;

    public ConnectInterceptor(OkHttpClient client) {
        this.client = client;
    }

    @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Request request = realChain.request();
        Transmitter transmitter = realChain.transmitter();

        // We need the network to satisfy this request. Possibly for validating a conditional GET.
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        //这里就需要用到桥了,并且会在 RetryAndFollowUpInterceptor 拦截器里面创建的 ExchangeFinder 里面在连接池里面找到一个连接,或者创建一个
        Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

        return realChain.proceed(request, transmitter, exchange);
    }
}

这个地方关键点在 newExchange 这里,这里会为请求在连接池里面找到或者创建一个连接,经过一系列方法调用,会来到 ExchangeFinder 里面的 findConnection 方法里面。

public class ExchangeFinder {
    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
                                          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        boolean foundPooledConnection = false;
        RealConnection result = null;
        Route selectedRoute = null;
        RealConnection releasedConnection;
        Socket toClose;
        synchronized (connectionPool) {
            if (transmitter.isCanceled()) throw new IOException("Canceled");
            hasStreamFailure = false; // This is a fresh attempt.

            // Attempt to use an already-allocated connection. We need to be careful here because our
            // already-allocated connection may have been restricted from creating new exchanges.
            releasedConnection = transmitter.connection;
            //transmitter.connection 同个路由上次使用的连接,noNewExchanges为 true,代表连接创建了,但是没用到,需要从连接池里面移除
            toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
                    ? transmitter.releaseConnectionNoEvents()
                    : null;
            //noNewExchanges 为 false 时候,这里 transmitter.connection 才不为空
            if (transmitter.connection != null) {
                //也就是这个连接还可以用,那就直接用这个连接
                result = transmitter.connection;
                releasedConnection = null;
            }

            if (result == null) {
                //第一次进来,result 为空,那就从线程池里面找
                if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
                    foundPooledConnection = true;
                    result = transmitter.connection;//找到了
                } else if (nextRouteToTry != null) {
                    //连接池没找到就赋值当前请求路由,为创建一个连接做准备,nextRouterToTry 在 RetryAndFollowUpInterceptor 拦截器里面的
                    // prepareToConnect 里面调用的 hasRouteToTry 赋值
                    selectedRoute = nextRouteToTry;
                    nextRouteToTry = null;
                } else if (retryCurrentRoute()) {
                    //走到这里noNewExchanges肯定为true,因为 transmitter.connection 不为空了,那只有为 true,result 才为 null
                    // 而且连接池没有并且 nextRouteToTry 为 null (也就是没有路由可试的时候),
                    // 拿到上次创建但没用到的连接的路由
                    selectedRoute = transmitter.connection.route();
                }
            }
        }
        closeQuietly(toClose);

        if (releasedConnection != null) {
            eventListener.connectionReleased(call, releasedConnection);
        }
        if (foundPooledConnection) {
            eventListener.connectionAcquired(call, result);
        }
        if (result != null) {//当前上次创建的连接可用或者连接池里面找到了的就直接返回
            // If we found an already-allocated or pooled connection, we're done.
            return result;
        }

        // If we need a route selection, make one. This is a blocking operation.
        boolean newRouteSelection = false;
        //如果上面 selectedRoute 有赋值了,那就不用再次拿所有路由去连接池里面查找了,直接创建连接就好了。
        if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
            newRouteSelection = true;
            routeSelection = routeSelector.next();
        }

        List<Route> routes = null;
        synchronized (connectionPool) {
            if (transmitter.isCanceled()) throw new IOException("Canceled");

            if (newRouteSelection) {
                // 这里已经拿到了请求的所有路由,尝试通过路由的方式去匹配连接
                routes = routeSelection.getAll();
                if (connectionPool.transmitterAcquirePooledConnection(
                        address, transmitter, routes, false)) {
                    foundPooledConnection = true;
                    result = transmitter.connection;
                }
            }

            if (!foundPooledConnection) {
                if (selectedRoute == null) {
                    selectedRoute = routeSelection.next();
                }

                // Create a connection and assign it to this allocation immediately. This makes it possible
                // for an asynchronous cancel() to interrupt the handshake we're about to do.
                //创建一个连接
                result = new RealConnection(connectionPool, selectedRoute);
                connectingConnection = result;
            }
        }

        // If we found a pooled connection on the 2nd time around, we're done.
        //如果从路由的方式在连接池里面找到了,那就直接返回
        if (foundPooledConnection) {
            eventListener.connectionAcquired(call, result);
            return result;
        }
        //为创建的连接做TCP 三次和 TLS 四次握手
        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
                connectionRetryEnabled, call, eventListener);
        connectionPool.routeDatabase.connected(result.route());

        Socket socket = null;
        synchronized (connectionPool) {
            connectingConnection = null;
            // Last attempt at connection coalescing, which only occurs if we attempted multiple
            // concurrent connections to the same host.
            //再一次拿IP地址去连接池找,多个连接并发的时候这个时候可能会成功找到
            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
                // We lost the race! Close the connection we created and return the pooled connection.
                //找到了,把创建的连接关闭了,复用连接池的
                result.noNewExchanges = true;
                socket = result.socket();
                result = transmitter.connection;

                // It's possible for us to obtain a coalesced connection that is immediately unhealthy. In
                // that case we will retry the route we just successfully connected with.
                nextRouteToTry = selectedRoute;
            } else {
                //还是找不到,创建的连接加入到连接池,用起来
                connectionPool.put(result);
                transmitter.acquireConnectionNoEvents(result);
            }
        }
        closeQuietly(socket);

        eventListener.connectionAcquired(call, result);
        return result;
    }
}

所以总结下来,连接复用有以下几个步骤

  • 如果当前连接正常,并且 noNewExchange 为 false,那就直接复用
  • 通过 address 的方式在连接池里面查找是否有匹配的连接
  • 拿到请求的所有路由 routers,在连接池里面查找是否有匹配的连接
  • 再创建一个连接后,并且TCP,TLS 握手后,再一次拿 address 和路由 routers 去连接池里面查找,由于多个连接并发问题,可能可以匹配上。如果匹配上了,那这个连接 noNewExchange 就会赋值为 true.
  • 如果以上方式都找不到,那就把连接加到连接池里面,给下一次做复用

连接创建好后,也就代表着 TCP,TLS 握手已经完成,也就代表着请求连接已经创建好了,这时候就会执行自定义的 networkInterceptor ,所以从这里我们也能发现 networkInterceptor 和 applicationInterceptor 的区别了,applicationInterceptor是在请求还没开始执行,也不参与到开始请求到连接的过程里面,而 networkInterceptor 正好相反。走完 networkInterceptor 拦截器后,接着就会来到 CallServiceInterceptor 拦截器了。下面来看看具体源码

public class CallServerInterceptor {
    @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Exchange exchange = realChain.exchange();
        Request request = realChain.request();

        long sentRequestMillis = System.currentTimeMillis();
        //发送请求的头部信息,这里分别对应着 Http1ExchangeCodec(HTTP/1.1) 和 Http2ExchangeCodec(Http/2)
        exchange.writeRequestHeaders(request);

        boolean responseHeadersStarted = false;
        Response.Builder responseBuilder = null;
        // 不是 Get 请求,需要发送 body 体
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
            // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
            // Continue" response before transmitting the request body. If we don't get that, return
            // what we did get (such as a 4xx response) without ever transmitting the request body.
            //如果请求头有 Expect :"100-continue",也就是问下服务器是否愿意接收发送的消息主体,刷新下,看是否在传输请求正文之前响应,
            if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
                exchange.flushRequest();
                responseHeadersStarted = true;
                exchange.responseHeadersStart();
                responseBuilder = exchange.readResponseHeaders(true);
            }

            if (responseBuilder == null) {
                if (request.body().isDuplex()) {
                    // Prepare a duplex body so that the application can send a request body later.
                    exchange.flushRequest();
                    BufferedSink bufferedRequestBody = Okio.buffer(
                            exchange.createRequestBody(request, true));
                    //发送 body 体
                    request.body().writeTo(bufferedRequestBody);
                } else {
                    //"Expect: 100-continue" 问题,编写请求正文,并且发送出去
                    // Write the request body if the "Expect: 100-continue" expectation was met.
                    BufferedSink bufferedRequestBody = Okio.buffer(
                            exchange.createRequestBody(request, false));
                    request.body().writeTo(bufferedRequestBody);
                    bufferedRequestBody.close();
                }
            } else {//"Expect: 100-continue" 问题,被阻止了,并且拿去下次复用
                exchange.noRequestBody();
                if (!exchange.connection().isMultiplexed()) {
                    // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
                    // from being reused. Otherwise we're still obligated to transmit the request body to
                    // leave the connection in a consistent state.
                    exchange.noNewExchangesOnConnection();
                }
            }
        } else {
            exchange.noRequestBody();
        }

        if (request.body() == null || !request.body().isDuplex()) {
            exchange.finishRequest();
        }
        //到这里,请求信息就发送出去了,下面就拿响应体了
        if (!responseHeadersStarted) {
            exchange.responseHeadersStart();
        }

        if (responseBuilder == null) {
            //拿 header 部分响应
            responseBuilder = exchange.readResponseHeaders(false);
        }

        Response response = responseBuilder
                .request(request)
                .handshake(exchange.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();

        int code = response.code();
        if (code == 100) {
            // server sent a 100-continue even though we did not request one.
            // try again to read the actual response
            response = exchange.readResponseHeaders(false)
                    .request(request)
                    .handshake(exchange.connection().handshake())
                    .sentRequestAtMillis(sentRequestMillis)
                    .receivedResponseAtMillis(System.currentTimeMillis())
                    .build();

            code = response.code();
        }

        exchange.responseHeadersEnd(response);

        if (forWebSocket && code == 101) {
            // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
            response = response.newBuilder()
                    .body(Util.EMPTY_RESPONSE)
                    .build();
        } else {
            //读取 body 体
            response = response.newBuilder()
                    .body(exchange.openResponseBody(response))
                    .build();
        }

        if ("close".equalsIgnoreCase(response.request().header("Connection"))
                || "close".equalsIgnoreCase(response.header("Connection"))) {
            exchange.noNewExchangesOnConnection();
        }

        if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
            throw new ProtocolException(
                    "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
        }

        return response;
    }
}

这个拦截器干嘛用呢,简而言之,就是把 Request 请求信息发出去,然后为 Response 赋值请求回来的 header 和 body 信息。到此,一个请求Request到得到响应体Response 就完成了。

最后,一路学习下来会发现,OkHttp 涵盖的网络知识真是太多太多了,以上只是自己学习的一些理解,应该也有一些理解错误的地方,需要后面不断学习,不断矫正。