前言:這篇網(wǎng)絡系列的初衷是分享下網(wǎng)絡相關的知識趟大,文章屬于個人的學習總結(jié)博客。部分內(nèi)容來源網(wǎng)絡铣焊,如有不適逊朽,請私聊。
Android之網(wǎng)絡—第一篇(Http原理)
Android之網(wǎng)絡—第二篇(Https原理)
Android之網(wǎng)絡—第三篇(解讀OkHttp)
Android之網(wǎng)絡—第四篇(解讀Retrofit)
結(jié)合Http原理解讀OkHttp庫
當前android最流行的網(wǎng)絡請求最火的框架應該就屬OkHttp曲伊。而且也被google官方收錄到android系統(tǒng)中叽讳,當做底層的網(wǎng)絡請求庫了。本文嘗試從Http原理的角度來解讀下OkHttp是如何實現(xiàn)的坟募。
okhttp源碼地址
簡單介紹下OkHttp的使用
可以看看官網(wǎng)的實例介紹:GET 和 POST使用岛蚤。鏈接地址:https://square.github.io/okhttp/
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
簡單總結(jié)來看:
- 1、創(chuàng)建OkHttpClient對象
- 2懈糯、拼接請求信息new Request.Builder()
- 3涤妒、發(fā)送請求client.newCall(request).execute()并解析返回結(jié)果
但是要結(jié)合之前Http原理注意下GET和POST拼接請求時的用法有些區(qū)別的。
可以參考這篇博客的一些使用說明加深下理解:Android OkHttp3簡介和使用詳解
第一部分:OkHttp網(wǎng)絡請求流程分析
通過上面的例子可以看出赚哗,OkHttpClient 和 Request都是配置和管理整個網(wǎng)絡請求的她紫,真正的請求開始是從client.newCall(request).execute()開始的。
(1)蜂奸、創(chuàng)建一個client.newCall(request)犁苏,但其實真正new出來的對象是Call的子類RealCall對象
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
.....
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
.....
}
final class RealCall implements Call {
......
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket); //創(chuàng)建一個RealCall對象
call.eventListener = client.eventListenerFactory().create(call); //創(chuàng)建網(wǎng)絡請求的監(jiān)聽對象
return call;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest; //初始的請求信息
this.forWebSocket = forWebSocket; //默認設置不支持websocket
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
this.timeout = new AsyncTimeout() {
@Override
protected void timedOut() { //設置超時
cancel();
}
};
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
......
}
這里簡單說明下兩個需要注意的地方
- 1、forWebSocket :代表當前請求是否是給WebSocket來用的扩所,默認設置不支持。WebSocket也是一種交互協(xié)議朴乖,特點是服務端是可以主動給客戶端發(fā)消息的祖屏。可以看看這篇文章理解下:看完讓你徹底搞懂Websocket原理
- 2买羞、retryAndFollowUpInterceptor:是OkHttp默認的第一個攔截器袁勺,用于處理請求錯誤重試和重定向的。后面會講解
(2)畜普、發(fā)送異步請求RealCall.enqueue(Callback) 或者 同步請求RealCall.execute()
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
//每個請求只能之執(zhí)行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this); //監(jiān)聽器開始
client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback)); //添加到異步隊列里
}
@Override
public Response execute() throws IOException {
synchronized (this) {
//每個請求只能之執(zhí)行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter(); //超時計時開始
eventListener.callStart(this); //監(jiān)聽器開始
try {
client.dispatcher().executed(this); //添加到同步隊列里
Response result = getResponseWithInterceptorChain(); //返回結(jié)果
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e); //監(jiān)聽結(jié)束
throw e;
} finally {
client.dispatcher().finished(this); //從隊列移除并執(zhí)行下一個任務
}
}
這兩段代碼前面是很相似的期丰,都是將Call添加到client.dispatcher().enqueue()里。差異在于new RealCall.AsyncCall(responseCallback),這里做了什么呢钝荡?其實這里繼承NamedRunnable 創(chuàng)建了一個線程去做異步請求了街立。先看看源碼實現(xiàn):
public abstract class NamedRunnable implements Runnable {
......
@Override
public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
//采用模板方法讓子類將具體的操作放到此execute()方法
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
......
}
final class AsyncCall extends NamedRunnable {
......
@Override
protected void execute() {
boolean signalledCallback = false; //這個標記為主要是避免異常時2次回調(diào)
timeout.enter();
try {
Response response = getResponseWithInterceptorChain(); //返回結(jié)果
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e); //監(jiān)聽結(jié)束
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);//從隊列移除并執(zhí)行下一個任務
}
}
...
}
(3)、Dispatcher 請求任務調(diào)度器
在剛才的分析中埠通,發(fā)現(xiàn)請求的開始都會調(diào)用client.dispatcher().executed() 和 請求的結(jié)束client.dispatcher().finished()赎离。思考下,那這個dispatcher()在扮演什么角色呢端辱?
public final class Dispatcher {
private int maxRequests = 64; //最大請求數(shù)量
private int maxRequestsPerHost = 5; //每臺主機最大的請求數(shù)量
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; //線程池
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //準備執(zhí)行的請求隊列
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); // 正在運行的異步請求隊列
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); // 正在運行的同步請求隊列
/** 初始化了一個線程池梁剔,核心線程的數(shù)量為0 ,最大的線程數(shù)量為Integer.MAX_VALUE(無限制)舞蔽,
空閑線程存在的最大時間為60秒荣病,空閑60s就會被回收*/
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
}
先看看,同步請求時渗柿,Dispatcher 做了什么众雷?其實就僅僅是將當期的同步請求做了一個隊列的管理而已。
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call); //添加到隊列中
}
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call); //從隊列中移除
}
再看看異步請求時做祝,Dispatcher 又做了什么砾省?
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call); //添加到準備執(zhí)行的請求隊列
}
promoteAndExecute(); //挑選合適的請求并執(zhí)行
}
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>(); //符合條件的請求
boolean isRunning;
synchronized (this) {
//遍歷沒執(zhí)行過的請求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//正在執(zhí)行的請求,沒有超負載:最大請求數(shù)不超過64個混槐,單個Host請求不超出5個编兄。
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
//將符合條件的請求拿出來
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
//將符合的請求執(zhí)行
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService()); //放到線程池執(zhí)行
}
return isRunning;
}
final class AsyncCall extends NamedRunnable {
......
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this); //線程池執(zhí)行
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) { //如果失敗了移除
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
...
}
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call); //從隊列中移除
}
最后來看看finished()具體做了什么事情。
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
//從隊列中移除當前的任務
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
//推動下一個任務的執(zhí)行
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
總結(jié)的來說声登,Dispatcher 就是個請求任務調(diào)度器狠鸳,其實準確的來說主要是做異步請求線程管理的,管理多個網(wǎng)絡請求悯嗓。
(4)件舵、獲取響應信息getResponseWithInterceptorChain()
其實從剛才的分析,到getResponseWithInterceptorChain()獲得請求返回信息脯厨,整體的網(wǎng)絡請求流程就分析完畢了铅祸。
但是,分析完也沒有發(fā)現(xiàn)有涉及到Http原理的內(nèi)容呀合武。你是不是又吹牛啦临梗。請看第二部分內(nèi)容
第二部分:OkHttp網(wǎng)絡請求與Http的原理結(jié)合
(1)、OkHttpClient 大雜燴解析
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......
final Dispatcher dispatcher; //請求任務調(diào)度器
final @Nullable Proxy proxy; //代理
final List<Protocol> protocols; //支持的協(xié)議
final List<ConnectionSpec> connectionSpecs; //連接配置稼跳,就是Https連接時的加密套件Cipher Suite
final List<Interceptor> interceptors; //攔截器集合盟庞,主要用于自定義攔截器
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory; //監(jiān)聽器,整個網(wǎng)絡請求過程的監(jiān)聽器
final ProxySelector proxySelector;
final CookieJar cookieJar; //Cookie機制,存放服務器的cookie
final @Nullable Cache cache; //緩存, 核心是DiskLruCache
final @Nullable InternalCache internalCache; //緩存接口汤善,配合Cache使用
final SocketFactory socketFactory; //Socket
final SSLSocketFactory sslSocketFactory; // TLS連接的Socket
final CertificateChainCleaner certificateChainCleaner; //證書清潔器什猖,可以理解為只包含證書重要信息
final HostnameVerifier hostnameVerifier; //主機名驗證器
final CertificatePinner certificatePinner; //證書固定器票彪,主要用于自簽名證書的驗證
final Authenticator proxyAuthenticator;
final Authenticator authenticator; //授權驗證,比如說登錄密碼錯誤訪問返回401不狮,需要重新授權降铸。
final ConnectionPool connectionPool; //連接池,
final Dns dns; //Dns解析荤傲,查找域名
final boolean followSslRedirects; //是否允許切換重定向垮耳,就是當訪問http后,返回https重定向是否支持
final boolean followRedirects; //是否允許重定向
final boolean retryOnConnectionFailure; //連接失敗是否重試
final int callTimeout; //
final int connectTimeout; //連接超時
final int readTimeout; //讀超時
final int writeTimeout; //寫超時
final int pingInterval; 和WebSocket有關遂黍。為了保持長連接终佛,必須間隔一段時間發(fā)送一個ping指令進行保活
public OkHttpClient() {
this(new Builder());
}
......
public Builder() {
dispatcher = new Dispatcher(); //默認創(chuàng)建一個任務調(diào)度器
protocols = DEFAULT_PROTOCOLS; //默認支持的協(xié)議為HTTP_1.1 和 HTTP_2
connectionSpecs = DEFAULT_CONNECTION_SPECS; //默認支持TLS1.0~1.3 雾家,加密方式SHA/AES/DES等
eventListenerFactory = EventListener.factory(EventListener.NONE); //默認創(chuàng)建一個監(jiān)聽器
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
cookieJar = CookieJar.NO_COOKIES; //默認沒有具體實現(xiàn)铃彰,需要自定義實現(xiàn)
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE; //默認OkHostnameVerifier()實現(xiàn)取證書的第一個host
certificatePinner = CertificatePinner.DEFAULT; //默認為空
proxyAuthenticator = Authenticator.NONE; //默認為空
authenticator = Authenticator.NONE; //默認為空
connectionPool = new ConnectionPool(); //連接池默認支持5個并發(fā)socket連接,默認keepalive時間為5分鐘
dns = Dns.SYSTEM; //默認使用系統(tǒng)的域名解析
followSslRedirects = true; //默認支持http 和 https 切換重定向
followRedirects = true; //默認支持重定向
retryOnConnectionFailure = true; // 默認支持失敗重試
callTimeout = 0;
connectTimeout = 10_000; //默認10秒
readTimeout = 10_000; //默認10秒
writeTimeout = 10_000; //默認10秒
pingInterval = 0;
}
......
}
通過簡單分析OkHttpClient 芯咧,發(fā)現(xiàn)OkHttp可配置的東西很多牙捉,涉及到的知識點也很復雜。結(jié)合前文解析的原理分析發(fā)現(xiàn)敬飒,OkHttp底層實現(xiàn)了網(wǎng)絡請求的線程調(diào)度邪铲,域名解析,緩存機制无拗,證書校驗带到,Socket連接等功能。而對應各個功能點的實現(xiàn)又是由不同的攔截器實現(xiàn)的英染。
(2)揽惹、OkHttp 攔截器 (最核心部分)
回頭看看之前分析,最終分析到getResponseWithInterceptorChain獲取到返回的信息就結(jié)束了四康。但是這里面到底發(fā)生了什么事呢搪搏?
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>(); //創(chuàng)建一個攔截器鏈列表
interceptors.addAll(client.interceptors()); // 1、添加自定義攔截器闪金,忽略
interceptors.add(retryAndFollowUpInterceptor); // 2疯溺、添加重試和重定向的攔截器
interceptors.add(new BridgeInterceptor(client.cookieJar())); // 3、添加處理請求頭和響應體的攔截器
interceptors.add(new CacheInterceptor(client.internalCache()));// 4毕泌、添加處理緩存邏輯的攔截器
interceptors.add(new ConnectInterceptor(client));// 5喝检、添加選擇請求連接的攔截器
if (!forWebSocket) { //忽略,不太涉及到WebSocket部分
interceptors.addAll(client.networkInterceptors());
}
// 6撼泛、添加向服務器發(fā)送請求報文、從服務器讀取并解析響應報文的攔截器
interceptors.add(new CallServerInterceptor(forWebSocket));
//將攔截器設置成鏈
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//攔截器鏈從第一個攔截器啟動執(zhí)行澡谭,并將結(jié)果返回
return chain.proceed(originalRequest);
}
通過上面的代碼簡單注釋愿题,發(fā)現(xiàn)這個代碼其實很簡單损俭,分三個部分,但是理解起來可不簡單潘酗,而且每一個攔截器里面做的核心邏輯也不簡單杆兵,請看下面的分析。
攔截器鏈整體結(jié)構(gòu)設計
攔截器的基本代碼結(jié)構(gòu)
public interface Interceptor {
//攔截器的核心邏輯方法
//在這里方法里面可以額外的前置動作處理
//也可以做額外的后置動作處理
Response intercept(Chain chain) throws IOException;
//攔截器鏈對象
interface Chain {
.....
//方法名直譯是“前進”仔夺。換句話說就是調(diào)用下一個攔截器
Response proceed(Request request) throws IOException;
......
}
}
先拋開攔截器列表里各個攔截器的具體實現(xiàn)琐脏,整體理解下攔截器鏈結(jié)構(gòu)是如果實現(xiàn)的。
public final class RealInterceptorChain implements Interceptor.Chain {
......
// 創(chuàng)建真正的攔截器鏈對象缸兔,傳入初始化信息
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
......
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
......
// Call the next interceptor in the chain.
//創(chuàng)建一個新鏈對象日裙,但可以理解為還是之前的攔截器鏈,只是這個鏈起始攔截器變成了下一個攔截器惰蜜,
//index + 1就是關鍵信息昂拂,而且getResponseWithInterceptorChain中的起始點為0, 這段代碼理解為
// 1抛猖、新建了一個攔截器鏈格侯,但起始還是之前的鏈,只是鏈起始點變成下一個
// 2财著、取出下一個攔截器联四,執(zhí)行
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next); //每一個攔截器都持有鏈對象,推動下一個攔截器啟動,同時將結(jié)果返回撑教。
......
return response;
}
}
再來看看具體攔截器的代碼結(jié)構(gòu)是如何實現(xiàn)的
public class CustomInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//前置動作,處理請求信息
Request newRequest = oldRequestToNew(oldRequest);
// 推動下一個攔截器執(zhí)行并返回結(jié)果
Response oldResponse = chain.proceed(newRequest);
//后置動作,處理返回信息
Response newResponse = oldResponseToNew(oldResponse);
return newResponse;
}
}
總結(jié)來說就是:
OkHttp通過定義很多攔截器一步一步地對Request進行攔截處理朝墩,直到真正地發(fā)起請求向族,并獲取數(shù)據(jù)悬嗓,然后有一步一步地對Response進行攔截處理桐智,最后攔截的結(jié)果就是回調(diào)的最終Response得糜。
好了诫肠,整體攔截器結(jié)構(gòu)就分析到這里躁锡,后面分解動作煤伟,詳細解析下OkHttp默認定義的攔截器都做了什么事瞭吃。
RetryAndFollowUpInterceptor詳解:重試和重定向攔截器
public final class RetryAndFollowUpInterceptor implements Interceptor {
......
@Override
public Response intercept(Chain chain) throws IOException {
//獲取鏈傳進來的信息
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0; //重試次數(shù)
Response priorResponse = null;
while (true) { //死循環(huán)
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true; //標記位
try {
response = realChain.proceed(request, streamAllocation, null, null); //推動下一個攔截器執(zhí)行
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
//路由異常恐似,嘗試恢復杜跷,如果再失敗就拋出異常
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue; //繼續(xù)重試
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue; //繼續(xù)重試
} 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.
if (priorResponse != null) { //前一個重試得到的Response,可能當前proceed返回空矫夷,以之前的繼續(xù)
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route()); //根據(jù)返回的請求碼進行處理得到新的Request
} catch (IOException e) {
streamAllocation.release();
throw e;
}
//注意不同的請求碼返回的結(jié)果不一樣葛闷,當為空時,就直接返回結(jié)果双藕。
//默認200直接將結(jié)果返回淑趾,但是當一些特殊情況導致請求失敗時,重試了也沒用忧陪,也直接將錯誤結(jié)果返回扣泊。
if (followUp == null) {
streamAllocation.release();
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) { //最多重試和重定向20次
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//后面的都是異常處理
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
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()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp; //將得到處理后的request賦值給
priorResponse = response; //記錄當前重試的結(jié)果
}
}
......
}
總結(jié)來說:
- 1近范、沒有前置動作,直接推動下一個攔截器獲取結(jié)果延蟹,如果下一個攔截器請求異常评矩,直接continue重試
- 2、針對返回的response 做后置動作阱飘,根據(jù)狀態(tài)碼處理獲取新的Request斥杜,如果為狀態(tài)碼200或其他特殊情況直接返回response ,如果是其他就繼續(xù)重試沥匈。
- 3蔗喂、重試的次數(shù)做多為20次。
BridgeInterceptor詳解:處理請求頭和響應頭攔截器
public final class BridgeInterceptor implements Interceptor {
......
@Override
public Response intercept(Chain chain) throws IOException {
//獲取鏈傳進來的信息
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//根據(jù)配置傳進來的RequestBody配置請求頭
RequestBody body = userRequest.body();
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"); //默認開啟Keep-Alive模式
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
//添加默認的編碼類型為gzip
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//設置Cookie緩存
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());
}
//推動下一個攔截器執(zhí)行
Response networkResponse = chain.proceed(requestBuilder.build());
//保存服務端返回的cookie
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//對數(shù)據(jù)解壓縮咐熙,并處理響應頭
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();
}
......
}
總結(jié)來說:
- 1弱恒、前置動作,處理設置請求頭棋恼,并設置默認的User-Agent,Host,Keep-alive報頭信息返弹,和cookie信息
- 2、針對返回的response 做后置動作爪飘,保存cookie并做gzip數(shù)據(jù)解壓處理义起。
CacheInterceptor詳解:處理緩存邏輯攔截器
public final class CacheInterceptor implements Interceptor {
......
@Override
public Response intercept(Chain chain) throws IOException {
//根據(jù)請求獲取緩存結(jié)果
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();//當前時間
//緩存策略類,該類決定了是使用緩存還是進行網(wǎng)絡請求
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy); //緩存統(tǒng)計
}
//cacheResponse 為null 表示緩存根據(jù)緩存策略不可用
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.
// 根據(jù)緩存策略返回的結(jié)果都為空师崎,說明當前的請求非法默终,直接返回504,Unsatisfiable Request
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 we don't need the network, we're done.
// networkRequest 為空犁罩,而cacheResponse 不為空齐蔽,直接使用緩存返回
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest); //推動下一個攔截器執(zhí)行
} 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) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) { //根據(jù)狀態(tài)碼,合并更新緩存信息
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());
}
}
//轉(zhuǎn)換下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);
}
//移除符合條件的method的Request緩存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
......
}
總結(jié)來說:
1床估、前置動作含滴,根據(jù)是否設置緩存來判斷緩存策略,然后根據(jù)緩存策略返回的兩個信息networkRequest 和cacheResponse 做判斷處理丐巫。
- 如果網(wǎng)絡不可用并且無可用的有效緩存谈况,則返回504錯誤,提示Unsatisfiable Request递胧;
- 如果網(wǎng)絡不可用碑韵,則直接使用緩存;
- 如果網(wǎng)絡可用,則進行網(wǎng)絡請求
2缎脾、針對返回的response 做后置動作祝闻,并跟cacheResponse 是否可用做邏輯判斷
- 如果根據(jù)狀態(tài)碼為HTTP_NOT_MODIFIED,說明緩存信息還有效遗菠,合并更新緩存信息治筒;
- 如果如果沒有緩存屉栓,則根據(jù)緩存策略寫入新的緩存舷蒲,并判斷該緩存是否要移除耸袜;
3、注意還有一個比較重要的CacheStrategy緩存策略類牲平,是緩存判斷邏輯的核心堤框。(PS:緩存讀寫使用DiskLruCache實現(xiàn)的)
public final class CacheStrategy {
.......
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) { //沒有緩存,直接網(wǎng)絡請求
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) { //請求為https但沒有握手纵柿,直接網(wǎng)絡請求
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) { //不可緩存蜈抓,直接網(wǎng)絡請求
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
//請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match
//請求頭包含If-Modified-Since或者If-None-Match意味著本地緩存過期,需要服務器驗證
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
//獲取一系列標記時間戳
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//responseCaching 可緩存昂儒,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
//這里對比時間后沟使,在響應頭添加warning。
//意味著雖過期渊跋,但可用腊嗡,只是會在響應頭添加warning
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
//根據(jù)請求頭設置的信息判斷etag 、lastModified 拾酝、servedDate 等是否為空燕少,
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
//請求頭添加If-None-Match、If-Modified-Since蒿囤、If-Modified-Since等信息
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
......
}
總結(jié)來說:緩存策略根據(jù)緩存是否存在客们、請求頭設置字段NoCache 、If-Modified-Since或者If-None-Match等信息及緩存時間來結(jié)合判斷是否使用緩存材诽。
講解后面兩個攔截器之前底挫,這里插播一些知識點方便理解。
keep-alive機制
在HTTP/1.0 之前正常發(fā)送一個請求都需要經(jīng)過三次握手建立一個TCP連接脸侥,然后進行數(shù)據(jù)交互建邓,最后再經(jīng)過四次握手釋放連接。但是在復雜的網(wǎng)絡請求中湿痢,重復的創(chuàng)建和釋放連接極大地影響了網(wǎng)絡效率涝缝,同時也增加了系統(tǒng)開銷。為了有效地解決這一問題譬重,HTTP/1.1提出了Keep-Alive機制:當一個HTTP請求的數(shù)據(jù)傳輸結(jié)束后拒逮,TCP連接不立即釋放,有一段存活時間臀规,如果此時有新的HTTP請求滩援,則可以直接復用TCP連接,從而省去了TCP的釋放和再次創(chuàng)建的開銷塔嬉,減少了網(wǎng)絡延時玩徊。http 1.0中默認是關閉的租悄,需要在http頭加入"Connection: Keep-Alive",才能啟用Keep-Alive恩袱;http 1.1中默認啟用Keep-Alive泣棋,如果加入"Connection: close ",才關閉畔塔。
管道機制(pipelining)
額外補充下潭辈,在HTTP/1.1的時候還引入了一個概念叫管道機制(pipelining),即在同一個TCP連接里面澈吨,客戶端可以同時發(fā)送多個請求把敢,只是服務端響應是按順序來響應數(shù)據(jù)。舉例來說谅辣,客戶端需要請求兩個資源修赞。以前的做法是,在同一個TCP連接里面桑阶,先發(fā)送A請求柏副,然后等待服務器做出回應,收到后再發(fā)出B請求联逻。管道機制則是允許瀏覽器同時發(fā)出A請求和B請求搓扯,但是服務器還是按照順序,先回應A請求包归,完成后再回應B請求锨推。
總結(jié)下兩者的區(qū)別,下面通過圖來加深下keep-alive機制 和 管道機制的理解:
當未開啟時公壤,每次請求都需要建立TCP和關閉TCP换可,而開啟之后,只需要開啟和關閉一次就好了厦幅。
微信圖片_20190507170849.jpg有了pipelining的支持后沾鳄,就可以一次性發(fā)送多次請求,而不需要一次一次的發(fā)送了确憨。
微信圖片_20190507171629.jpg總結(jié)來說就是译荞,Keep-Alive可以讓一個tcp連接保持不關閉,但是每次的請求都必須在上一次響應之后休弃,是為了解決每次http請求都要求建立和釋放的過程吞歼;pipelining是為了解決每次http請求必須一次請求一次響應(但是注意這里的隱形條件就是tcp是處于連接狀態(tài)的)。
但是管道機制有自身的很多限制,默認都是關閉的塔猾。參考:HTTP管線化
多路復用機制
通過上述概念理解篙骡,可以發(fā)現(xiàn)在http1.1中更多的解決了請求連接的問題,但是數(shù)據(jù)的傳輸和交互還是按順序的發(fā)送和響應。那這個傳輸數(shù)據(jù)的過程是否可以優(yōu)化呢糯俗?
舉個例子:客戶端要向服務器發(fā)送Hello尿褪、World兩個單詞,只能是先發(fā)送Hello再發(fā)送World得湘,沒辦法同時發(fā)送這兩個單詞杖玲。不然服務器收到的可能就是HWeolrllod(注意是穿插著發(fā)過去了,但是順序還是不會亂)忽刽。這樣服務器就懵b了,“發(fā)的這是啥玩意我不認識”跪帝。
假設一個場景:在同一個TCP連接里面缤谎,客戶端給服務端發(fā)送一個請求坷澡,但是數(shù)據(jù)不是按順序的來發(fā)送過去托呕,服務端能不能正確的解析出來呢?答案是肯定的频敛,發(fā)送數(shù)據(jù)時只要給每個數(shù)據(jù)打上順序標簽项郊,服務端接收后按順序拼接就可以了。同樣的斟赚,服務端給客戶端發(fā)送數(shù)據(jù)也可以類似的着降。
基于上述的結(jié)果再假設一個場景:在同一個TCP連接里面,客戶端給服務端發(fā)送多個請求拗军,多個請求同時發(fā)送的多個數(shù)據(jù)任洞,而數(shù)據(jù)也不是按順序的來發(fā)送過去,服務端能不能正確的解析出來呢发侵?答案也是肯定的交掏。只要給每一個請求打上標簽,比如請求1標記位A器紧,發(fā)送的數(shù)據(jù)包分a_1耀销、a_2、...、a_n熊尉。然后服務端就根據(jù)標簽就知道數(shù)哪個請求發(fā)送了并及時響應對應的數(shù)據(jù)罐柳。
而Http2.0的就是基于這樣場景引入二進制數(shù)據(jù)幀和流的概念,其中幀對數(shù)據(jù)進行順序標識狰住。進而基于這樣的概念就可以實現(xiàn)多路復用张吉,進而實現(xiàn)請求的并發(fā)處理。
詳細的說明可參考:HTTP 2.0 原理詳細分析
ConnectInterceptor詳解:請求連接攔截器(最核心最底層的地方)
public final class ConnectInterceptor implements Interceptor {
......
@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 和 connection
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection); //調(diào)用下一個攔截器
}
}
總結(jié)來說:
- 1催植、無后置動作肮蛹,只有前置動作,最核心的代碼就是streamAllocation.newStream()创南,返回Connection和HttpCodec對象伦忠。這兩個對象是什么含義呢?從字面的理解應該就是分配一個網(wǎng)絡請求的連接 和 數(shù)據(jù)的編碼解碼器稿辙,而且要注意HttpCodec有Http1Codec和Http2Codec兩個子類實現(xiàn)昆码,這有什么區(qū)別呢?
回頭再來看看streamAllocation.newStream()代碼
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
......
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
......
} catch (IOException e) {
throw new RouteException(e);
}
}
再來看看尋找連接過程的核心代碼
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) { //找到一個可用的連接(如果連接不可用邻储,這個過程會一直持續(xù)哦)
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(); //連接不好使的話,從移除連接池吨娜,并持續(xù)尋找
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.
// 通過releaseIfNoNewStreams()判斷該鏈接是否可以創(chuàng)建新的Stream脓匿,如果不能就關閉當前流
// 經(jīng)過判斷后如果connection不為null,則連接是可用的
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.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;
}
// 試圖從連接池中找到可用的連接宦赠,注意最后一個參數(shù)是null陪毡,代表沒有路由信息
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;
}
}
}
//根據(jù)上面的邏輯做后續(xù)的處理
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;
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.
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.
// 給新建的連接建立連接過程
//封裝socket袱瓮,建立TCP + TLS的過程
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;
}
總結(jié)來說:
- 查看當前streamAllocation是否有之前已經(jīng)分配過的連接,有則直接使用
- 從連接池中查找可用的連接燎斩,有則返回該連接
- 配置路由虱歪,配置后再次從連接池中查找是否有可用連接,有則直接返回
- 新建一個連接栅表,并修改其StreamAllocation標記計數(shù)笋鄙,將其放入連接池中
- 查看連接池是否有重復的多路復用連接,有則清除怪瓶。
接下來看看HttpCodec具體代碼實現(xiàn)
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection); //創(chuàng)建Http2Codec對象
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink); //創(chuàng)建Http1Codec對象
}
}
總結(jié)來說:HttpCodec只是針對不同的Http版本創(chuàng)建不同的對象萧落,而這個對應有什么區(qū)別呢?其實就是前面多路復用機制提及的,Http的數(shù)據(jù)格式的區(qū)別找岖,http1.x的數(shù)據(jù)格式還是文本格式陨倡,而http2則是基于數(shù)據(jù)幀的形式。
CallServerInterceptor詳解:負責向服務器發(fā)起真正的訪問請求许布,并接收服務器返回的響應
public final class CallServerInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//獲取鏈傳進來的信息
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request); //根據(jù)不同的http版本寫請求頭信息
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
//若請求方法允許傳輸請求體兴革,且request的請求體不為空
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"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
//接收服務器的返回請求杂曲,服務器若不接受請求體則會返回一個非空的響應
responseBuilder = httpCodec.readResponseHeaders(true);
}
//若responseBuilder為null,則Expect不為100-continue或服務器接收請求體,開始寫入請求體
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!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.
// 如果服務器拒絕接收請求體,且不是http2袁余,則禁止此連接被重新使用
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest(); //完成請求寫入
//通過httpCodec獲取響應頭
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//通過responseBuilder填入信息創(chuàng)建Response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code(); //獲取返回碼
if (code == 100) { //如果是101(升級到Http2協(xié)議)
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
//處理forWebSocket情況下的響應
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(httpCodec.openResponseBody(response))
.build();
}
//若請求或者服務器要求斷開連接擎勘,則斷開
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
//若返回204/205(服務器均未返回響應體)且響應體長度大于0)則拋出異常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
}
總結(jié)來說:整個請求的發(fā)送和數(shù)據(jù)響應都是通過HttpCodec 對象完成。而HttpCodec 實際上利用的是 Okio本質(zhì)還是通過Socket完成請求泌霍。
- 向服務器發(fā)送 request header
- 如果有 request body货抄,就向服務器發(fā)送
- 讀取 response header,先構(gòu)造一個 Response對象朱转;
- 如果有 response body,就在 3 的基礎上加上 body 構(gòu)造一個新的 Response對象积暖;
最后通過圖解來總結(jié)整個OkHttp的請求流程圖:
后序:這篇文章從源碼角度整體分析OkHttp藤为,攔截器的責任鏈模式設計非常優(yōu)雅,并且框架從底層一步一步封裝實現(xiàn)整個網(wǎng)絡請求夺刑,并針對Http的版本也做了完美的適配缅疟,期待Http2的推廣。
如果覺得我的文章對你有幫助遍愿,請隨意贊賞存淫。您的支持將鼓勵我繼續(xù)創(chuàng)作!