1.OkHttp源碼
首先來一張okhttp源碼的完整流程圖
1.1.RealCall.getResponseWithInterceptorChain方法解析
首先看一個典型的同步請求過程
public String get(String url) throws IOException {
//新建OKHttpClient客戶端
OkHttpClient client = new OkHttpClient();
//新建一個Request對象
Request request = new Request.Builder()
.url(url)
.build();
//Response爲OKHttp中的響應
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
}else{
throw new IOException("Unexpected code " + response);
}
}
/**
* Prepares the {@code request} to be executed at some point in the future.
* 準備將要被執行的request
*/
@Override
public Call newCall(Request request) {
return new RealCall(this, request);
}
上面的client.newCall(request)返回的是RealCall,RealCall纔是真正的請求執行者,來看下RealCall的構造方法和execute方法。
protected RealCall(OkHttpClient client, Request originalRequest) {
this.client = client;
this.originalRequest = originalRequest;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
}
@Override
public Response execute() throws IOException {
synchronized (this) {
//檢查這個 call是否已經被執行了,每個 call 只能被執行一次,如果想要一個完全一樣的 call,可以利用 all#clone 方法進行克隆
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//不過度關注
client.dispatcher.executed(this);
//真正發出網絡請求,解析返回結果的地方--需要關注的地方
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
}finally {
//不過度關注,返回執行狀態
client.dispatcher.finished(this);
}
}
真正發出網絡請求,解析返回結果的,還是 getResponseWithInterceptorChain:
//攔截器的責任鏈。
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//在配置 OkHttpClient 時設置的 interceptors;
interceptors.addAll(client.interceptors());
//負責失敗重試以及重定向的 RetryAndFollowUpInterceptor;
interceptors.add(retryAndFollowUpInterceptor);
//負責把用戶構造的請求轉換爲發送到服務器的請求、把服務器返回的響應轉換爲用戶友好的響應的 BridgeInterceptor
//其實也就是給request和response添加header
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//負責讀取緩存直接返回、更新或者寫入緩存的 CacheInterceptor
interceptors.add(new CacheInterceptor(client.internalCache()));
//負責和服務器建立連接的 ConnectInterceptor,比較複雜
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
//配置 OkHttpClient 時設置的 networkInterceptors
interceptors.addAll(client.networkInterceptors());
}
//負責向服務器發送請求數據、從服務器讀取響應數據的 CallServerInterceptor,也不深入研究和okio相關
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//開始鏈式調用
return chain.proceed(originalRequest);
}
在chain.proceed(originalRequest);中開啓鏈式調用
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//***************略***************
// Call the next interceptor in the chain.、
//這裏是責任鏈模式調用
//實例化下一個攔截器對應的RealIterceptorChain對象
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//得到當前的攔截器
Interceptor interceptor = interceptors.get(index);
//調用當前攔截器的intercept()方法,並將下一個攔截器的RealIterceptorChain對象傳遞下去
Response response = interceptor.intercept(next);
//***************略***************
return response;
}
看看interceptor.intercept(next)幹了啥,又調用了chain.proceed地櫃迭代啊
//核心代碼,調用下一個攔截器
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
1.2自定義一個仿okhttp的不純的責任鏈demo
上面攔截器責任鏈模式我們可以自己寫個demo加深理解,就以我們平常的請假申請流程爲例,員工提交申請(request)--主管審批--經理審批--總監審批到此流程完成給你resposne。
public interface ModelOkHttpInterceptor {
//處理實際的任務
public void intercept(Chain chain);
interface Chain {
//構建責任鏈
void proceed(String user);
}
}
public class RealChain implements ModelOkHttpInterceptor.Chain {
private List<ModelOkHttpInterceptor> mInterceptors;
private int mIndex;
private String mUser;
public RealChain(List<ModelOkHttpInterceptor> interceptors, int index, String user) {
mInterceptors = interceptors;
mIndex = index;
mUser = user;
}
@Override
public void proceed(String user) {
mUser = user;
if (mIndex > mInterceptors.size()) {
System.out.print("錯誤,沒有人能夠審批了");
}
RealChain chain = new RealChain(mInterceptors, mIndex + 1, mUser);
ModelOkHttpInterceptor interceptor = mInterceptors.get(mIndex);
interceptor.intercept(chain);
}
public String getUser() {
return mUser;
}
}
public class StaffInterceptor implements ModelOkHttpInterceptor {
@Override
public void intercept(Chain chain) {
String name = ((RealChain) chain).getUser();
System.out.println(name + "提交加班申請");
//員工提交申請,電子流責任鏈轉到主管那裏
chain.proceed("主管");
}
}
public class SupervisorInterceptor implements ModelOkHttpInterceptor {
@Override
public void intercept(Chain chain) {
String name = ((RealChain) chain).getUser();
System.out.println(name + "審批通過");
//主管神品通過申請,電子流責任鏈轉到部門經理那裏
chain.proceed("經理");
}
}
public class ManagerInterceptor implements ModelOkHttpInterceptor {
@Override
public void intercept(Chain chain) {
String name = ((RealChain) chain).getUser();
System.out.println(name + "審批通過");
//部門經理提交申請,電子流責任鏈轉到總監那裏
chain.proceed("總監");
}
}
public class ChiefInterceptor implements ModelOkHttpInterceptor {
@Override
public void intercept(Chain chain) {
String name = ((RealChain) chain).getUser();
//一般總監審批通過流程就結束了,這裏就相當於request結束有response返回
System.out.println(name + "審批通過");
}
}
public class MainTest {
public static void main(String[] args ){
List<ModelOkHttpInterceptor> interceptorList = new ArrayList<>();
interceptorList.add(new StaffInterceptor());
interceptorList.add(new SupervisorInterceptor());
interceptorList.add(new ManagerInterceptor());
interceptorList.add(new ChiefInterceptor());
//構建加班申請責任鏈
ModelOkHttpInterceptor.Chain realChain = new RealChain(interceptorList, 0, "老三");
//員工開始提交申請
realChain.proceed("老三");
}
}
2.幾個主要的攔截器解析
2.1RetryAndFollowUpInterceptor:負責失敗重試以及重定向攔截器
這個攔截器負責:當一個請求由於某種原因獲取響應數據失敗了,就回去嘗試重連恢復,並且重新封裝request(followUpRequest)最大重試次數20次。
/**
* This interceptor recovers from failures and follows redirects as necessary.
* 負責失敗重試以及重定向的
* It may throw an {@link IOException} if the call was canceled.
*/
public final class RetryAndFollowUpInterceptor implements Interceptor {
/**
* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
* 重試的最大嘗試次數,超過將拋出異常
*/
private static final int MAX_FOLLOW_UPS = 20;
//OkHttpClient成員
private final OkHttpClient client;
//這個類暫時先放過
private StreamAllocation streamAllocation;
//這個也先放過
private boolean forWebSocket;
//請求被取消
private volatile boolean canceled;
public RetryAndFollowUpInterceptor(OkHttpClient client) {
this.client = client;
}
/**
* 同步的立即取消socket連接請求
*/
public void cancel() {
canceled = true;
StreamAllocation streamAllocation = this.streamAllocation;
if (streamAllocation != null) streamAllocation.cancel();
}
//*******************省略了一些set方法**********************
/**
* 主要愛分析這個方法
* @param chain
* @return
* @throws IOException
*/
@Override public Response intercept(Chain chain) throws IOException {
//拿到攔截鏈中的請求
Request request = chain.request();
//連接池,獲取一個正確的連接
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
//重定向次數初始化爲0
int followUpCount = 0;
Response priorResponse = null;
//開始循環
while (true) {
//請求被取消直接拋異常
if (canceled) {
//連接被釋放
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
//核心代碼,調用下一個攔截器
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 連接路由嘗試失敗,請求將不會被髮送
if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
releaseConnection = false;
//重試啊
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
//和服務器交互失敗,請求也不會被髮送
if (!recover(e, false, request)) throw e;
releaseConnection = false;
//重試啊
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
// 拋出未知異常,釋放資源
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
//如果上一次重試獲取的response存在,將其boby置爲空(沒看明白乾嘛)
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//新的重試request封裝:添加請求頭,添加連接超時
Request followUp = followUpRequest(response);
//followUp爲空(看下面followUpRequest這個方法的註釋如果follow-up不必要或者不可用就返回空)
//重試流程結束,在這裏跳出。
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
//超過最大重試次數,釋放連接,拋出異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//異常處理
if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()));
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
//request重新賦值
request = followUp;
priorResponse = response;
}
}
//*******************省略一些方法**********************
/**
* Figures out the HTTP request to make in response to receiving {@code userResponse}. This will
* either add authentication headers, follow redirects or handle a client request timeout. If a
* follow-up is either unnecessary or not applicable, this returns null.
* 對重試請求做了下封裝:添加header,連接timeout
*/
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
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:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "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"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
requestBuilder.method(method, null);
}
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, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
// 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 (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
return userResponse.request();
default:
return null;
}
}
/**
* Returns true if an HTTP request for {@code followUp} can reuse the connection used by this
* engine.
* 判斷request請求是否可以複用
*/
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}
}
2.2BridgeInterceptor:
BridgeInterceptor的註釋意思是根據用戶請求構建網絡請求,使得它繼續去訪問網絡,最後根據網絡響應構建用戶響應, 其實就是衛request添加請求頭,爲response添加響應頭。
/**
* Bridges from application code to network code. First it builds a network request from a user
* request. Then it proceeds to call the network. Finally it builds a user response from the network
* response.
* 上面意思是根據用戶請求構建網絡請求,使得它繼續去訪問網絡,最後根據網絡響應構建用戶響應。
* 其實就是衛request添加請求頭,爲response添加響應頭。
*/
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();
//添加contenttype
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
//如果body有值,請求頭設置body的內容長度,並且移除傳輸編碼
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//否則,設置傳輸編碼(這裏是分塊傳輸編碼),移除內容長度值
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
//添加Host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//添加Connection
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) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//開始給resposne添加header
Response networkResponse = chain.proceed(requestBuilder.build());
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, 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();
}
}
2.3ConnectInterceptor
ConnectInterceptor的intercept內部代碼很少,關內容主要是咋在streamAllocation.newStream這裏比較複雜。newStream內部findHealthyConnection是找到一個可用連接的意思重點就在這,通過找到的可用連接newCodec()返回了HttpCodec的實現類Http1Codec對象。
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();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
主要看看findHealthyConnection()的實現
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//findHealthyConnection這個方法是重點
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec;
if (resultConnection.http2Connection != null) {
resultCodec = new Http2Codec(client, this, resultConnection.http2Connection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultCodec = new Http1Codec(
client, this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
流程:先檢測鏈接是否可用:
a 可用的話直接返回,結束流程。
b 不可用,先去連接池中查找:
連接池找到:結束流程。
未找到:提供address,再次去連接池中查找。如果找到了直接結束流程,如果沒找到:生成一個新的連接,並且將這個新的鏈接加入到連接池,然後返回這個新鏈接。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {//判斷連接是否可用
noNewStreams();//連接不可用,移除
continue;//不可用,就一直持續
}
return candidate;
}
}
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
//異常情況,直接拋出
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 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 streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) { //
//經過releaseIfNoNewStreams,connection不爲null,則連接是可用的
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
//無可用連接,去連接池connectionPool中獲取
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {//上面通過去連接池中找,如果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;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
//提供address,再次從連接池中獲取連接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
//提供路路由信息,然後進行查找可用鏈接,還是沒有找到可用鏈接,就需要生成一個新的連接
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.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
//如果連接是從連接池中找到的,直接拿出來使用
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
//將新生成的連接放入連接池中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
//如果是一個http2連接,http2連接應具有多路複用特性,
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
2.4CacheInterceptor
CacheInterceptor核心還是調用下一個攔截器,從網絡獲取響應數據,流程:
- 1.首先根據requst來判斷cache中是否存在緩存,如有存在取出這個response
- 2.根據request獲取緩存策略:網絡,緩存,網絡+緩存
- 3.一些異常情況判斷:
(1)既無網絡請求訪問,也沒有緩存,直接返回504
(2)沒有網絡請求訪問,但是有緩存直接返回緩存
- 4.調用下一個攔截器,從網絡獲取響應數據
- 5.如果本地存在cacheResponse,網絡response和cacheResponse作比較,判斷是否更新cacheResponse
- 6.沒有cacheResponse,直接寫入網絡response到緩存
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緩存策略類,管理request和response的緩存----第二步
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.
//如果禁止訪問網絡,並且無緩存,返回504----第三步
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(EMPTY_BODY)
.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.
//如果因爲io或者其他原因崩潰,不泄露緩存並關閉
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
//如果已經存在緩存response,並且正在做條件get請求
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.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 (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
//寫入緩存----第六步
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
}
2.5CallServerInterceptor:發送和接收數據
3.請求過程
3.1同步請求流程
1.1有提到過,不說了
3.2異步請求流程
典型流程
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
});
}
和同步不同的地方主要是enqueue,看這個方法:
//異步任務使用
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//調用了上面我們沒有詳細說的Dispatcher類中的enqueue(Call )方法.接着繼續看--分析1
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //正在準備中的異步請求隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //運行中的異步請求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); //同步請求
/**
**分析1
**/
synchronized void enqueue(AsyncCall call) {
//如果中的runningAsynCalls不滿,且call佔用的host小於最大數量,則將call加入到runningAsyncCalls中執行,同時利用線程池//執行call;否者將call加入到readyAsyncCalls中
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
//call加入到線程池中執行了。現在再看AsynCall的代碼,它是RealCall中的內部類--分析2
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
//異步請求
/**
**分析2
**/
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
//破案了,異步請求也是調用getResponseWithInterceptorChain處理來獲得response,
//異步任務同樣也是會經過一系列的攔截器
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
4.自定義一個攔截器(LogInterceptor)
就以最常見的LoggingInterceptor爲例子
class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
//1.請求前--打印請求信息
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
//2.網絡請求
Response response = chain.proceed(request);
//3.網絡響應後--打印響應信息
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
自定義攔截的添加也分兩種方式Application Intercetor 和 NetworkInterceptor,他們的區別如下圖:
4.1添加 Application Interceptor,request是最初的request,response 就是最終的響應,得到日誌相對較少
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
響應數據:
INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example
INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
4.2添加 NetworkInterceptor,可以得到的是更多的信息,重定向的信息也會得到。
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
Response response = client.newCall(request).execute();
response.body().close();
INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt
INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip
INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive