之前的文章介绍到了 OkHttp 的拦截器机制的整体概述,现在让我们依次研究一下其拦截器的实现。
前面提到, RetryAndFollowUpInerceptor
负责了 HTTP 请求的重定向功能,那让我们先了解一下 HTTP 协议中的重定向。
HTTP 协议提供了一种重定向的功能,它通过由服务器返回特定格式的响应从而触发客户端的重定向。其对应的 Response Code 格式为 3XX,并且会在 Response Header 的 Location
字段中放入新的 URL,这样我们客户端就可以根据该 Location 字段所指定的 URL 重新请求从而得到需要的数据。
其过程如下图所示:
其中重定向对应的状态码及含义如下表所示(摘自 维基百科 ):
可以发现,重定向和服务器转发请求是有些相似的,它们有什么不同呢?
重定向是客户端行为,而服务器转发则是服务端行为
重定向我们的客户端发出了多次请求,而转发我们的客户端只发出了一次请求。
重定向的控制权在客户端,转发的控制权在服务端。
###代码分析
接下来让我们研究一下 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; } } 复制代码
可以看到,这里外部通过一个循环,实现不断重定向,可以看一下循环内主要做了什么:
chain.proceed
方法进行请求获取 Response
Response
中设置上一次的 Response priorResponse
,且body为空 Response
中的 response code 进行重定向,调用 followUpRequest
方法获取重定向后的 request followUp
followUp
为 null,说明不再需要重定向,停止 timeout 计时并返回 Response
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
的名字取的非常形象,它就像一座桥梁,连接了用户与服务器。在用户向服务器发送请求时,它会把用户所构建的请求转换为向服务器请求的真正的 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-Length
、 Content-Encoding
字段从 Header 中移除 ,因此上层可能会获取到 -1。
而这里关于 Cookie
的处理我们暂时不关心,后续文章中再对其作介绍。
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; } 复制代码
可以看到,这里主要是以下步骤
response
request
、缓存的 response
构建缓存策略。 networkRequest == null
),且无缓存( cacheResponse == null
),则直接请求失败。 response
并返回。 chain.proceed
网络请求获取 response
response
并返回 response
,并且由于该网络请求并未进行过缓存,进行缓存并返回结果 而关于缓存相关的具体实现这里先不过多做介绍,后面会专门开一篇文章进行分析,这里主要以流程为主。
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
是整个网络请求链的最后一个拦截器,它真正实现了对服务器 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; } 复制代码
这里代码量非常多,但其实核心是下面几步:
Request Header Request Body Response Header Response Body
其具体实现我们后续文章再进行介绍,到了这里整个责任链的大体流程我们就分析完了。