OkHttp 源码剖析系列(二)——拦截器整体流程分析

2,100 阅读10分钟

你好,我是 N0tExpectErr0r,一名热爱技术的 Android 开发

我的个人博客:blog.N0tExpectErr0r.cn

OkHttp 源码剖析系列文章目录:

OkHttp 源码剖析系列(一)——请求的发起及拦截器机制概述

OkHttp 源码剖析系列(二)——拦截器整体流程分析

OkHttp 源码剖析系列(三)——缓存机制

OkHttp 源码剖析系列(四)——连接建立概述

OkHttp 源码剖析系列(五)——代理路由选择

OkHttp 源码剖析系列(六)——连接复用机制及连接的建立

OkHttp 源码剖析系列(七)——请求的发起及响应的读取

之前的文章介绍到了 OkHttp 的拦截器机制的整体概述,现在让我们依次研究一下其拦截器的实现。

RetryAndFollowUpInterceptor

前面提到,RetryAndFollowUpInerceptor 负责了 HTTP 请求的重定向功能,那让我们先了解一下 HTTP 协议中的重定向。

HTTP 中的重定向

HTTP 协议提供了一种重定向的功能,它通过由服务器返回特定格式的响应从而触发客户端的重定向。其对应的 Response Code 格式为 3XX,并且会在 Response Header 的 Location 字段中放入新的 URL,这样我们客户端就可以根据该 Location 字段所指定的 URL 重新请求从而得到需要的数据。

其过程如下图所示:

img

其中重定向对应的状态码及含义如下表所示(摘自维基百科):

image-20190730211626735

重定向与服务器转发的区别

可以发现,重定向和服务器转发请求是有些相似的,它们有什么不同呢?

  1. 重定向是客户端行为,而服务器转发则是服务端行为

  2. 重定向我们的客户端发出了多次请求,而转发我们的客户端只发出了一次请求。

  3. 重定向的控制权在客户端,转发的控制权在服务端。

###代码分析

接下来让我们研究一下 RetryAndFollowUpInterceptor 的实现原理,我们看到 RetryAndFollowUpInterceptor.intercept 方法:

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    // 获取transmitter
    Transmitter transmitter = realChain.transmitter();
    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
    	// 进行一些连接前的准备工作
        transmitter.prepareToConnect(request);
        // 处理取消事件
        if (transmitter.isCanceled()) {
            throw new IOException("Canceled");
        }
        Response response;
        boolean success = false;
        try {
        	// 调用chain的proceed方法获取下层得到的结果
            response = realChain.proceed(request, transmitter, null);
            success = true;
        } catch (RouteException e) {
            // 若不满足重定向的条件,抛出异常
            if (!recover(e.getLastConnectException(), transmitter, false, request)) {
                throw e.getFirstConnectException();
            }
            // 满足重定向条件,重试
            continue;
        } catch (IOException e) {
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            // 不满足重定向条件,抛出异常
            if (!recover(e, transmitter, requestSendStarted, request)) throw e;
            // 满足重定向条件,重试
            continue;
        } finally {
            if (!success) {
            	// 若抛出了异常,释放资源
                transmitter.exchangeDoneDueToException();
            }
        }
        // 在本次response中设置上一次的response,其body为空
        if (priorResponse != null) {
            response = response.newBuilder()
                    .priorResponse(priorResponse.newBuilder()
                            .body(null)
                            .build())
                    .build();
        }
        Exchange exchange = Internal.instance.exchange(response);
        Route route = exchange != null ? exchange.connection().route() : null;
        // 根据response code获取重定向后的request
        Request followUp = followUpRequest(response, route);
        if (followUp == null) {
        	// 不再需要重定向,停止timeout计时并返回response
            if (exchange != null && exchange.isDuplex()) {
                transmitter.timeoutEarlyExit();
            }
            return response;
        }
        RequestBody followUpBody = followUp.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
        request = followUp;
        // 记录上一次的response
        priorResponse = response;
    }
}

可以看到,这里外部通过一个循环,实现不断重定向,可以看一下循环内主要做了什么:

  1. 进行一些预处理
  2. 调用 chain.proceed 方法进行请求获取 Response
  3. 过程中若下层抛出异常,则尝试重定向
  4. 若不满足重定向条件,则抛出异常
  5. 若出现其他未知的异常,则通过抛出异常释放资源
  6. 在本次 Response 中设置上一次的 Response priorResponse,且body为空
  7. 根据 Response 中的 response code 进行重定向,调用 followUpRequest 方法获取重定向后的 request followUp
  8. 若重定向后的 followUp 为 null,说明不再需要重定向,停止 timeout 计时并返回 Response
  9. 若重定向超过指定次数(默认 20 次),则抛出异常。
  10. 若仍未返回,则需要下一次重定向,对下一次的 request 等变量进行赋值。

让我们看看 followUpRequest 方法做了什么:

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
          	// ...
          	// 代理身份认证
        case HTTP_UNAUTHORIZED:		// 401
            // ...
            // 身份认证
        case HTTP_PERM_REDIRECT:	// 308
        case HTTP_TEMP_REDIRECT:	// 307
            // 307、308 两种状态码不对 GET、HEAD 以外的请求重定向
            if (!method.equals("GET") && !method.equals("HEAD")) {
                return null;
            }
        case HTTP_MULT_CHOICE:		// 300
        case HTTP_MOVED_PERM:		// 301
        case HTTP_MOVED_TEMP:		// 302
        case HTTP_SEE_OTHER:		// 303
            // 若客户端关闭了重定向,则直接返回 null
            if (!client.followRedirects()) return null;
            // 获取LocationHeader以获取重定向目标
            String location = userResponse.header("Location");
            if (location == null) return null;
            HttpUrl url = userResponse.request().url().resolve(location);
			// ...
            Request.Builder requestBuilder = userResponse.request().newBuilder();
            // 处理重定向使用的method
            if (HttpMethod.permitsRequestBody(method)) {
                final boolean maintainBody = HttpMethod.redirectsWithBody(method);
                if (HttpMethod.redirectsToGet(method)) {
                    requestBuilder.method("GET", null);
                } else {
                    RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
                    requestBuilder.method(method, requestBody);
                }
                if (!maintainBody) {
                    requestBuilder.removeHeader("Transfer-Encoding");
                    requestBuilder.removeHeader("Content-Length");
                    requestBuilder.removeHeader("Content-Type");
                }
            }
            // 重新构建request
            return requestBuilder.url(url).build();
        case HTTP_CLIENT_TIMEOUT:	// 408
            // 408 说明需要重新发送一次相同的请求
			// ...
			return userResponse.request();
        case HTTP_UNAVAILABLE:		// 503
            // ...
            return null;
        default:
            return null;
    }
}

可以看到,主要是针对重定向的几个状态码进行特殊处理,从中取出 Location 字段,构造重定向后的 request

BridgeInterceptor

BridgeInterceptor 的名字取的非常形象,它就像一座桥梁,连接了用户与服务器。在用户向服务器发送请求时,它会把用户所构建的请求转换为向服务器请求的真正的 Request,而在服务器返回了响应后,它又会将服务器所返回的响应转换为用户所能够使用的 Response

让我们看到 BridgeInterceptor.intercept 方法:

@Override
public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();
    // 将一些userRequest中的属性设置进builder中
    if (body != null) {
        MediaType contentType = body.contentType();
        if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
        }
        long contentLength = body.contentLength();
        if (contentLength != -1) {
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
        } else {
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
        }
    }
    if (userRequest.header("Host") == null) {
        requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    if (userRequest.header("Connection") == null) {
        requestBuilder.header("Connection", "Keep-Alive");
    }
    boolean transparentGzip = false;
    // 若未设置Accept-Encoding,自动设置gzip
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
        transparentGzip = true;
        requestBuilder.header("Accept-Encoding", "gzip");
    }
    // 将userRequest中的cookies设置进builder
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
        requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    // 设置user-agent
    if (userRequest.header("User-Agent") == null) {
        requestBuilder.header("User-Agent", Version.userAgent());
    }
    // 读取服务端响应
    Response networkResponse = chain.proceed(requestBuilder.build());
    // 对响应的header进行处理
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
   	// 根据服务端的响应构建新的Response,并将userRequest设置为其request
   	Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
    // 若之前设置了gzip压缩且response中也包含了gzip压缩,则进行gzip解压
    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");
        responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }
    return responseBuilder.build();
}

可以看到,这里主要对 Header 进行处理,将一些原来 request 中的 Header 进行处理后设置进了新 request,并用其进行请求。其中若调用者未设置 Accept-Encoding,则它会默认设置 gzip

而在对 response 处理时,若之前设置了 gzip,则进行 gzip 解压。这种自动解压会自动将 Content-LengthContent-Encoding 字段从 Header 中移除,因此上层可能会获取到 -1。

而这里关于 Cookie 的处理我们暂时不关心,后续文章中再对其作介绍。

CacheInterceptor

CacheInterceptor 主要负责了对缓存的读取以及更新,让我们看看其 intercept 方法:

@Override
public Response intercept(Chain chain) throws IOException {
	// 尝试获取缓存的cache
    Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    long now = System.currentTimeMillis();
    // 传入当前时间、request以及从缓存中取出的cache,构建缓存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    // 通过缓存策略获取新的request
    Request networkRequest = strategy.networkRequest;
	// 通过缓存策略获取缓存中取出的response
	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.
    }
    // 根据缓存策略若不能使用网络且没有缓存,则请求失败,构建一个请求失败的Response并返回
    if (networkRequest == null && cacheResponse == null) {
        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 (networkRequest == null) {
        return cacheResponse.newBuilder()
                .cacheResponse(stripBody(cacheResponse))
                .build();
    }
    Response networkResponse = null;
    try {
    	// 网络请求获取response
        networkResponse = chain.proceed(networkRequest);
    } finally {
        // 如果IO的过程中出现了crash,回收资源
        if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
        }
    }
    // 如果缓存中有缓存,并且请求的code为304,则结合缓存及网络请求结果后返回,并且更新缓存中的内容
    if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {	// 304
            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 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;
}

可以看到,这里主要是以下步骤

  1. 尝试从缓存中获取了缓存的 response
  2. 根据 当前时间、request、缓存的response 构建缓存策略。
  3. 若缓存策略不能使用网络(networkRequest == null),且无缓存(cacheResponse == null),则直接请求失败。
  4. 若缓存策略不能使用网络,由于前面有判断所以可以确定有缓存,直接构建缓存的 response 并返回。
  5. 调用 chain.proceed 网络请求获取 response
  6. 对 code 304 作出处理,结合本地及网络返回数据构建 response 并返回
  7. 构建网络请求的所获得的 response ,并且由于该网络请求并未进行过缓存,进行缓存并返回结果

而关于缓存相关的具体实现这里先不过多做介绍,后面会专门开一篇文章进行分析,这里主要以流程为主。

ConnectInterceptor

ConnectInterceptor 主要负责的是与服务器的连接的建立,它的代码非常短:

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    // 构建Exchange
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
    return realChain.proceed(request, transmitter, exchange);
}

这里主要是调用 transmitter.newExchange 构建一个 Exchange,之后调用了 realChain.proceed(request, transmitter, exchange) 方法。

这个 Exchange 类究竟是什么呢?我们看到它的 JavaDoc:

Transmits a single HTTP request and a response pair. This layers connection management and events on {@link ExchangeCodec}, which handles the actual I/O.

也就是说 Exchange 类可以将 ExchangeCodec 这个类的连接管理及事件进行分层,而 ExchangeCodec 是一个真正执行 I/O 的类,看来这个类主要是进行一些连接管理的事务。在 newExchange 的过程中可能就创建/复用了客户与服务器的连接。

这里具体的连接获取过程我们暂时先不做介绍,在后续文章中会详细进行介绍,此篇文章更偏向整体流程的讲解。

CallServerInterceptor

CallServerInterceptor 是整个网络请求链的最后一个拦截器,它真正实现了对服务器 Response 的读取,让我们看看它的实现:

@Override
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();
    long sentRequestMillis = System.currentTimeMillis();
    // 写入请求头
    exchange.writeRequestHeaders(request);
    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        // 对 100-continue 这一 header 做特殊处理
        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));
                request.body().writeTo(bufferedRequestBody);
            } else {
                // 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 {
            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) {
    	// 读取响应头
        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 {
        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;
}

这里代码量非常多,但其实核心是下面几步:

  1. 写入Request Header
  2. 写入Request Body
  3. 读取Response Header
  4. 读取Response Body

其具体实现我们后续文章再进行介绍,到了这里整个责任链的大体流程我们就分析完了。

参考资料

HTTP状态码

OkHttp之旅系列