- 1.OkHttp源码解析(一):OKHttp初阶
- 2 OkHttp源码解析(二):OkHttp连接的"前戏"——HTTP的那些事
- 3 OkHttp源码解析(三):OKHttp中阶之线程池和消息队列
- 4 OkHttp源码解析(四):OKHttp中阶之拦截器及调用链
- 5 OkHttp源码解析(五):OKHttp中阶之OKio简介
- 6 OkHttp源码解析(六):OKHttp中阶之缓存基础
- 7 OkHttp源码解析(七):OKHttp中阶之缓存机制
- 8 OkHttp源码解析(八):OKHttp中阶之连接与请求值前奏
- 9 OkHttp源码解析(九):OKHTTP连接中三个"核心"RealConnection、ConnectionPool、StreamAllocation
- 10 OkHttp源码解析(十) OKHTTP中连接与请求
- 11 OkHttp的感谢
终于到了讲解OkHttp中的连接与请求了,这部分内容主要是在ConnectInterceptor与CallServerInterceptor中,所以本片文章主要分2部分
- 1、ConnectInterceptor
- 2、CallServerInterceptor
- 3、总结
一、ConnectInterceptor
顾名思义连接拦截器,这才是真行的开始向服务器发起器连接。
看下这个类的代码
/** Opens a connection to the target server and proceeds to the next interceptor. */
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);
}
}
主要看下ConnectInterceptor()方法,里面代码已经很简单了,受限了通过streamAllocation的newStream方法获取一个流(HttpCodec 是个接口,根据协议的不同,由具体的子类的去实现),第二步就是获取对应的RealConnection,由于在上一篇文章已经详细解释了RealConnection和streamAllocation类了,这里就不详细说了是大概聊一下
StreamAllocation的newStream()内部其实是通过findHealthyConnection()方法获取一个RealConnection,而在findHealthyConnection()里面通过一个while(true)死循环不断去调用findConnection()方法去找RealConnection.而在findConnection()里面其实是真正的寻找RealConnection,而上面提到的findHealthyConnection()里面主要就是调用findConnection()然后去验证是否是"健康"的。在findConnection()里面主要是通过3重判断:1如果有已知连接且可用,则直接返回,2如果在连接池有对应address的连接,则返回,3切换路由再在连接池里面找下,如果有则返回,如果上述三个条件都没有满足,则直接new一个RealConnection。然后开始握手,握手结束后,把连接加入连接池,如果在连接池有重复连接,和合并连接。
至此findHealthyConnection()就分析完毕,给大家看下大缩减后的代码,如果大家想详细了解,请看上一篇文章。
//StreamAllocation.java
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
// 省略代码
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
// 省略代码
}
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 {
//省略部分代码
//条件1如果有已知连接且可用,则直接返回
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
//条件2 如果在连接池有对应address的连接,则返回
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// 条件3切换路由再在连接池里面找下,如果有则返回
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) return connection;
route = selectedRoute;
refusedStreamCount = 0;
//以上条件都不满足则new一个
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;
}
这里再简单的说下RealConnection的connect()因为这个方法也很重要。不过大家要注意RealConnection的connect()是StreamAllocation调用的。在RealConnection的connect()的方法里面也是一个while(true)的循环,里面判断是隧道连接还是普通连接,如果是隧道连接就走connectTunnel(),如果是普通连接则走connectSocket(),最后建立协议。隧道连接这里就不介绍了,如果大家有兴趣就去上一篇文章去看。connectSocket()方噶里面就是通过okio获取source与sink。establishProtocol()方法建立连接咱们说下,里面判断是是HTTP/1.1还是HTTP/2.0。如果是HTTP/2.0则通过Builder来创建一个Http2Connection对象,并且调用Http2Connection对象的start()方法。所以判断一个RealConnection是否是HTTP/2.0其实很简单,判断RealConnection对象的http2Connection属性是否为null即可,因为只有HTTP/2的时候http2Connection才会被赋值。
代码如下:
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 connectSocket(int connectTimeout, int readTimeout) throws IOException {
//省略部分代码
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
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();
}
}
这时候我们在回来看下findConnection()方法里面的一行代码
acquire(result)
调用的是acquire()方法
public void acquire(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
代码简单,这里解释一下,每一个RealConnection对象都有一个字段即allocations
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
connections中维护了一张在一个连接上的流的链表。该链表保存的是StreamAllocation的引用。如果connections字段为空,则说明该连接可以被回收,如果不为空,说明被引用,不能被回收。所以OkHttp使用了类似计数法与标记擦出法的混合使用。当连接空闲或者释放的时候,StreamAllcocation的数量就会渐渐变成0。从而被线程池检测并回收。
至此StreamAllocation的findHealthyConnection()就分析完毕了。那我们来看下
//StreamAllocation.java
HttpCodec resultCodec = resultConnection.newCodec(client, this);
其实是调用RealConnection的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);
}
}
上面主要分了HTTP/2和HTTP/1.x,如果是HTTP/2(http2Connection不为null)则构建Http2Codec。如果是HTTP/1.x。则构建Http1Codec,大家注意一下在构建Http2Codec的时候并没有传入source和sink。这是为什么那?大家好好想一下,如果大家不知道为什么可以去看一下我前面的一篇介绍HTTP/2的文章,如果看了还不懂,请在下面留言,我给大家解释下。
至此关于ConnectInterceptor已经介绍完毕了。下面我们来介绍下CallServerInterceptor。最后一个Interceptor
二、CallServerInterceptor
上面我们已经成功连接到服务器了,那接下来要做什么那?相信你已经猜到了, 那就说发送数据了。
在OkHttp里面读取数据主要是通过以下四个步骤来实现的
- 1 写入请求头
- 2 写入请求体
- 3 读取响应头
- 4 读取响应体
OkHttp的流程是完全独立的。同样读写数据月是交给相关的类来处理,就是HttpCodec(解码器)来处理。
@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();
//写入请求头
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
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.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
//写入请求体
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} 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.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
//读取响应头
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//读取响应体
int code = response.code();
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();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
自此整个流程已经结束了。
那我们再来看下OkHttp网络请求的整体接口图(特别声明:这个图不是我画的)
关于OkHttp就的解析马上就要结束了,最后我们再来温习一下整体的流程图