接上篇。
(主要從網絡拉取響應分析)從sendRequest方法中可以看到
httpStream = connect();
httpStream.setHttpEngine(this);
接下來,我們就看看connect()方法:
HttpEngine#connect
private HttpStream connect() throws RouteException, RequestException, IOException {
boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
return streamAllocation.newStream(client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis(),
client.retryOnConnectionFailure(), doExtensiveHealthChecks);
}
繼續跟蹤
StreamAllocation#newStream
public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws RouteException, IOException {
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpStream resultStream;
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(this, resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
可以看到這裏創建了RealConnection resultConnection 對象。
具體創建的代碼
StreamAllocation#findHealthyConnection
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException, RouteException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate;
}
connectionFailed(new IOException());
}
}
繼續跟蹤
StreamAllocation#findConnection
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException, RouteException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (stream != null) throw new IllegalStateException("stream != null");
if (canceled) throw new IOException("Canceled");
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
//如果連接池中已經存在連接,就從中取出(get)RealConnection
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}
selectedRoute = route;
}
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
synchronized (connectionPool) {
route = selectedRoute;
}
}
RealConnection newConnection = new RealConnection(selectedRoute);
acquire(newConnection);
//將建立成功的RealConnection放入(put)連接池緩存
synchronized (connectionPool) {
Internal.instance.put(connectionPool, newConnection);
this.connection = newConnection;
if (canceled) throw new IOException("Canceled");
}
//根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);
routeDatabase().connected(newConnection.route());
return newConnection;
}
到這裏我們先介紹Socket管理(StreamAllocation)。
StreamAllocation
進行HTTP連接需要進行Socket握手,Socket握手的前提是根據域名或代理確定Socket的ip與端口。這個環節主要講了http的握手過程與連接池的管理,分析的對象主要是StreamAllocation
選擇路線與自動重連(RouteSelector)
此步驟用於獲取socket的ip與端口,各位請欣賞源碼中next()的迷之縮進與遞歸,代碼進行了如下事情:
如果Proxy爲null:
- 在構造函數中設置代理爲Proxy.NO_PROXY
- 如果緩存中的lastInetSocketAddress爲空,就通過DNS(默認是Dns.SYSTEM,包裝了jdk自帶的lookup函數)查詢,並保存結果,注意結果是數組,即一個域名有多個IP,這就是自動重連的來源
- 如果還沒有查詢到就遞歸調用next查詢,直到查到爲止
- 一切next都沒有枚舉到,拋出NoSuchElementException,退出(這個幾乎見不到)
如果Proxy爲HTTP:
- 設置socket的ip爲代理地址的ip
- 設置socket的端口爲代理地址的端口
- 一切next都沒有枚舉到,拋出NoSuchElementException,退出
連接socket鏈路(RealConnection)
當地址,端口準備好了,就可以進行TCP連接了(也就是我們常說的TCP三次握手),步驟如下:
- 如果連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命中就進入下一步
- 根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
- 將建立成功的RealConnection放入(put)連接池緩存
- 如果存在TLS,就根據SSL版本與證書進行安全握手
- 構造HttpStream並維護剛剛的socket連接,管道建立完成
釋放socket鏈路(release)
如果不再需要(比如通信完成,連接失敗等)此鏈路後,釋放連接(也就是TCP斷開的握手)
- 嘗試從緩存的連接池中刪除(remove)
- 如果沒有命中緩存,就直接調用jdk的socket關閉
經過上述分析,相信大家都有了一定的概念,對上面的那段源碼也就看的很自然。如果連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命就根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手。
RealConnection#connect
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
Proxy proxy = route.proxy();
Address address = route.address();
if (route.address().sslSocketFactory() == null
&& !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not supported: " + connectionSpecs));
}
while (protocol == null) {
try {
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
}
如果存在TLS,就根據SSL版本與證書進行安全握手
RealConnection#connectSocket
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
if (route.address().sslSocketFactory() != null) {
connectTls(readTimeout, writeTimeout, connectionSpecSelector);
} else {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
}
if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
FramedConnection framedConnection = new FramedConnection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.protocol(protocol)
.listener(this)
.build();
framedConnection.sendConnectionPreface();
// Only assign the framed connection once the preface has been sent successfully.
this.allocationLimit = framedConnection.maxConcurrentStreams();
this.framedConnection = framedConnection;
} else {
this.allocationLimit = 1;
}
}
上面這段代碼寫的就是根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手。
這裏又引入了一概念。HTTP請求序列化/反序列化
下面我們就來分析分析。
HTTP請求序列化/反序列化
分析的對象是HttpStream接口,在HTTP/1.1下是Http1xStream實現的。
獲得HTTP流(httpStream)
以下爲無緩存,無多次302跳轉,網絡良好,HTTP/1.1下的GET訪問實例分析。
我們已經在上文的RealConnection通過connectSocket()構造HttpStream對象並建立套接字連接(完成三次握手)
在connect()有非常重要的一步,它通過okio庫與遠程socket建立了I/O連接,爲了更好的理解,我們可以把它看成管道
//source 用於獲取response
source = Okio.buffer(Okio.source(rawSocket));
//sink 用於write buffer 到server
sink = Okio.buffer(Okio.sink(rawSocket));
拼裝Raw請求與Headers(writeRequestHeaders)
我們通過Request.Builder構建了簡陋的請求後,可能需要進行一些修飾,這時需要使用Interceptors對Request進行進一步的拼裝了。
攔截器是okhttp中強大的流程裝置,它可以用來監控log,修改請求,修改結果,甚至是對用戶透明的GZIP壓縮。類似於函數式編程中的flatmap操作。在okhttp中,內部維護了一個Interceptors的List,通過InterceptorChain進行多次攔截修改操作。
源代碼中是自增遞歸(recursive)調用Chain.process(),直到interceptors().size()中的攔截器全部調用完。主要做了兩件事:
- 遞歸調用Interceptors,依次入棧對response進行處理
- 當全部遞歸出棧完成後,移交給網絡模塊(getResponse)
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
//遞歸調用Chain.process()
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
接下來是正式的網絡請求getResponse(),此步驟通過http協議規範將對象中的數據信息序列化爲Raw文本:
- 在okhttp中,通過RequestLine,Requst,HttpEngine,Header等參數進行序列化操作,也就是拼裝參數爲socketRaw數據。拼裝方法也比較暴力,直接按照RFC協議要求的格式進行concat輸出就實現了
- 通過sink寫入write到socket連接。
獲得響應(readResponseHeaders/Body)
此步驟根據獲取到的Socket純文本,解析爲Response對象,我們可以看成是一個反序列化(通過http協議將Raw文本轉成對象)的過程:
攔截器的設計:
- 自定義網絡攔截器請求進行遞歸入棧
- 在自定義網絡攔截器的intercept中,調用NetworkInterceptorChain的proceed(request),進行真正的網絡請求(readNetworkResponse)
- 接自定義請求遞歸出棧
網絡讀取(readNetworkResponse)分析:
- 讀取Raw的第一行,並反序列化爲StatusLine對象
- 以Transfer-Encoding: chunked的模式傳輸並組裝Body
接下來進行釋放socket連接,上文已經介紹過了。現在我們就獲得到response對象,可以進行進一步的Gson等操作了。
到目前,基本框架也算是介紹完了。還是那句話,共同學習與進步。