因為OkHttp能講的東西太多了依啰,上一篇文章只是講到了他的設(shè)計架構(gòu)即責(zé)任鏈模式和異步多線程網(wǎng)絡(luò)訪問油狂,這對于OkHttp只是冰山一角站超,對于一個網(wǎng)絡(luò)請求框架斤程,最重要的就是網(wǎng)絡(luò)訪問了角寸,為此我們來說一下Okttp網(wǎng)絡(luò)訪問的一些細節(jié)。
這個訪問分為兩個部分忿墅,一個部分是與服務(wù)器形成連接扁藕,另一個部分是與服務(wù)器進行交互。與服務(wù)器連接的是ConnectInterceptor攔截器疚脐,而與服務(wù)器交互的是CallServerInterceptor攔截器亿柑。我們就來講一下這兩個攔截器吧。
ConnectInterceptor
先看源碼:
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();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
這里看起來很簡單棍弄,就是給首先StreamAllocation賦值望薄,然后調(diào)用newStream()方法,那streamAllocation是什么東西呢呼畸?它是整個連接的中心,協(xié)調(diào)著幾個重要的類痕支。后面都會說。
我們看一下newStream()方法:
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
通過看代碼我們有追溯findHealthyConnection()方法,源碼如下:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) {
route = selectedRoute;
return connection;
}
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
Internal.instance.put(connectionPool, result);
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
在findHealthyConnection()里通過while(true)不斷調(diào)用findConnection()去獲取健康可用的RealConnection蛮原。RealConnection是與服務(wù)器連接的一個socket的連接卧须,有和這個就可以進行三次握手的tcp連接,所以上面的result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);這一行很關(guān)鍵儒陨,就是socket連接服務(wù)器的關(guān)鍵代碼花嘶,具體里面的代碼如下:
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
//省略代碼
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
//省略代碼
}
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout)
throws IOException {
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
int attemptedConnections = 0;
int maxAttempts = 21;
while (true) {
if (++attemptedConnections > maxAttempts) {
throw new ProtocolException("Too many tunnel connections attempted: " + maxAttempts);
}
connectSocket(connectTimeout, readTimeout);
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
if (tunnelRequest == null) break;
closeQuietly(rawSocket);
rawSocket = null;
sink = null;
source = null;
}
}
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
try {
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
if (route.address().sslSocketFactory() == null) {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
return;
}
connectTls(connectionSpecSelector);
if (protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.build();
http2Connection.start();
}
}
不管是直接調(diào)用connectSocket()還是connectTunnel(),最終都會調(diào)connectSocket()方法然后通過 Platform.get().connectSocket()(rawSocket, route.socketAddress(), connectTimeout);進行Socket連接。Platform.get().connectSocket()對應(yīng)代碼如下:
public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
socket.connect(address, connectTimeout);
}
然后有調(diào)用了establishProtocol(),起始最主要的就是初始化Http2Connection對象.接著返回StreamAllocation蹦漠,下一步就是HttpCodec resultCodec = resultConnection.newCodec(client, this);newCodec()源碼如下:
public HttpCodec newCodec(OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
其實沒什么椭员,也就是初始化Http1Codec或Http2Codec對象,這兩個類都集成接口類HttpCodec津辩,典型的面向接口編程拆撼,我們看一下接口類是什么:
public interface HttpCodec {
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
//寫入請求體
Sink createRequestBody(Request request, long contentLength);
//寫入請求頭
void writeRequestHeaders(Request request) throws IOException;
//相當(dāng)于flush,把請求刷入底層socket
void flushRequest() throws IOException;
//相當(dāng)于flush容劳,把請求輸入底層socket并不在發(fā)出請求
void finishRequest() throws IOException;
//讀取響應(yīng)頭
Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
//讀取響應(yīng)體
ResponseBody openResponseBody(Response response) throws IOException;
//取消請求
void cancel();
}
有方法知道HttpCodec是網(wǎng)絡(luò)讀寫的管理類喘沿,而Http1Codec和Http2Codec分別對應(yīng)Http1和Http2,在后面的CallServerInterceptor就主要用這個類進行操作。最后ConnectInterceptor的RealConnection connection = streamAllocation.connection();只是獲取了前面生成的RealConnection竭贩,然后通過前一篇介紹的責(zé)任鏈模式傳給CallServerInterceptor蚜印。
CallServerInterceptor
前面的ConnectInterceptor只是socket連接了服務(wù)器,而連接后怎么操作就是CallServerInterceptor了,接著我們看一下其實現(xiàn)方法:
@Override
public Response intercept(Chain chain) throws IOException {
//省略代碼。留量。蓬戚。
//寫入請求頭
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
//寫入請求體
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
//讀取響應(yīng)頭
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//讀取響應(yīng)體
int code = response.code();
if (forWebSocket && code == 101) {
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();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
這里有一個東西需要講,就是Socket連接了服務(wù)器之后伺帘,是通過Okio向服務(wù)器發(fā)送請求的与帆。再次列取writeRequestHeaders()方法,源碼如下:
@Override
public void writeRequestHeaders(Request request) throws IOException {
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
writeRequest(request.headers(), requestLine);
}
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
而sink就是在ConnectInterceptor已經(jīng)初始化完成了仇奶,就在上面的connectSocket()連接服務(wù)器的方法里
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
其中sink是向服務(wù)器寫數(shù)據(jù),而source是獲取服務(wù)器數(shù)據(jù),rawSocket就是我們與服務(wù)器保持連接socker,okio我只會點到為止翰灾,不然又要開新的一篇講解了。createRequestBody()的原理和writeRequestHeaders()是一樣的稚茅。
接著就是接收數(shù)據(jù)了纸淮,讀取響應(yīng)頭readResponseHeaders()和上面原理一樣的,值得講的是讀取響應(yīng)體
httpCodec.openResponseBody(response)亚享,里面的源碼如下:
@Override
public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}
private Source getTransferStream(Response response) throws IOException {
if (!HttpHeaders.hasBody(response)) {
return newFixedLengthSource(0);
}
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return newChunkedSource(response.request().url());
}
long contentLength = HttpHeaders.contentLength(response);
if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
return newUnknownLengthSource();
}
其實沒有做任何的讀取操作咽块,只是ResponseBody封裝了headers()獲取響應(yīng)頭和Source對象,而Source就可以獲取響應(yīng)體欺税,只是沒有馬上獲取而是封裝好傳遞給上一個攔截器侈沪。最后在哪里獲取響應(yīng)體呢,
回到上一篇剛開始最簡單的訪問網(wǎng)絡(luò)demo
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
return response.body().string();
我們看一下body().toString()這個方法
public @Nullable ResponseBody body() {
return body;
}
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
source就是Okio的讀對象對應(yīng)上面的source = Okio.buffer(Okio.source(rawSocket));,bomAwareCharset是獲取字符類型晚凿,默認utf-8,調(diào)用source.readString(charset);就可以獲取他的請求體了峭竣,也就是請求內(nèi)容字符串。closeQuietly()最終這個讀對象晃虫,整個訪問流程也基本結(jié)束了皆撩。
內(nèi)容有點多,自身感覺講解的也僅講了最主要的部分哲银,很多東西還可以擴展卻因為篇幅沒說扛吞,請見諒。