阅读 OKHttp 源码

2018.2.16最新的git代码.
OkHttpClient,这是外部调用的入口.
final Dispatcher dispatcher;
final @Nullable Proxy proxy;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
final SocketFactory socketFactory;
final @Nullable SSLSocketFactory sslSocketFactory;
final @Nullable CertificateChainCleaner certificateChainCleaner;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
final ConnectionPool connectionPool;
final Dns dns;
Dispatcher 用于派发请求.Interceptor两个拦截器列表,存放着拦截器.Cache 用于缓存.还有一些认证,代理ssl相关的.
先来说下 Cache,里面具体用的DiskLruCache来存储,这是jw大神的代码.
缓存的内容是根据请求产生key:
String contentType = responseHeaders.get("Content-Type");
String contentLength = responseHeaders.get("Content-Length");
Request cacheRequest = new Request.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();还有返回值,等信息存储.
缓存是通过Request request = new Request.Builder().cacheControl(new CacheControl.Builder()处理的.同时还需要服务器配合.可以通过HTTP/1.1 (RFC 7234)了解更多. http://tools.ietf.org/html/rfc7234

Dispatcher,这是一个派发类,实现同步或异步的call的调用,都从这里开始.里面的三个队列:
/** Ready async calls in the order they'll be run. */ 准备中的,等待被调用的队列.
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
异步的调用,使用的是AsyncCall
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
这是同步的调用它的对象是RealCall
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
,再配合两个入口方法:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//当前的请求数量还未超过限制就执行.否则加入队列等待
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
synchronized void executed(RealCall call) {
//直接进入运行队列.
runningSyncCalls.add(call);
}
AsyncCall在执行完以后,就调用finished方法.这个方法处理了两件事,一是,promoteCalls(),对当前的队列做一些修改,把准备好的放入运行中的队列,然后执行.二是如果没有可运行的请求了,就执行空闲方法.
这个设计很巧妙,空闲方法是可以从外部定义的,扩展性强.
AsyncCall与RealCall的区别,RealCall里面包含了AsyncCall,AsyncCall是实现了Runnable接口,可以异步执行,放到线程池中.
RealCall,它并不是实现了Runnable接口,里有两个enqueue,executed方法.异步的就产生一个AsyncCall,同步的就将RealCall加到上面的runningSyncCalls里面.Dispatcher只对异步的请求有派发操作,在线程池中运行,对同步的call,是回到了RealCall中,添加到队列后,直接执行.
到这里,call已经准备好了,至于执行它,不管是同步还是异步,都是是通过责任链getResponseWithInterceptorChain()来处理.所以这两个call中,并不没有具体的执行过程,而是在拦截器中.
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors()); //客户端设置的
interceptors.add(retryAndFollowUpInterceptor); //重试
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client)); //连接
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket)); //最终调用服务器接口的

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}
一部分是客户端设置的,可以获取相关的数据,自定义操作,一部分拦截器是内置的,用于连接,获取数据等具体联网操作.这不同于像volley,它的联网操作被分离了.
来看下 CallServerInterceptor,这是最终获取数据的拦截器.
Response intercept(Chain chain){
//获取协议编码器,http1,http2.
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
//先写头
httpCodec.writeRequestHeaders(request);
然后写入body,如果有的话
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();

//然后构造返回数据体,并处理状态码.
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();

然后处理具体返回值,
httpCodec.openResponseBody();
这里就涉及到了http1与http2两种.
}
先看连接的建立:
RealConnection连接,分了两种连接,从connect()开始.
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
而connectTunnel最终也是调用connectSocket,但它因为是http,无状态,所以有了重试机制,最多重试21次
for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
connectSocket(connectTimeout, readTimeout, call, eventListener);
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

if (tunnelRequest == null) break; // Tunnel successfully created.
}

connectSocket():
//创建socket.
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);

eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
...
}
//得到输入,输出流,这是okio.
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
到这里,连接已经建立,可以处理数据了.source是输出流,sink是输入流.

产生不同连接的是在 ConnectInterceptor拦截器中,上面的连接建立成功后,就会有http2Connection的确认,这是在确认协议方法中得到的.
再到RealConnection中,这时就可以处理连接相关的协议了.
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
...
return new Http1Codec(client, streamAllocation, source, sink);
}
}
回到拦截器中:
Response intercept(Chain chain){
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
//这里就建立完成连接,然后,就把事情交给下一个拦截器了.比如 CallServerInterceptor.
}

这整个过程,最重要的是责任链模式.要理解它,就先要理解责任链模式.
主要部分都是围绕这个责任链模式进行的,至于其它的,请求体,握手,cookie,也是在这中心展开的.
比如缓存,使用的是缓存拦截器,联网使用的是连接拦截器等.
中心在于 RealInterceptorChain这个,proceed()方法里面执行当前的拦截器,并构造下一个链.
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);
这个责任链模式不是纯的,因为它处理了一部分,然后又转到下一个链,纯的责任链是要么自己处理,要么转为下一链.模式类图:
getResponseWithInterceptorChain()前面已经说过,责任链的传输,从这里开始了.
再来看它的拦截器顺序,先是客户端设置的拦截器.
interceptors.addAll(client.interceptors()); //客户端设置的
再后来是设置客户端设置的网络拦截器.
interceptors.addAll(client.networkInterceptors());
因为责任链是反向调用的,所以这些拦截器是倒序执行的.但是分两种,一种是非网络的,一种是网络相关的.当然还跟realChain.proceed(request, streamAllocation, httpCodec, connection);方法调用的顺序有关.a拦截器在b之前添加的,但a的拦截功能是调用proceed()前就处理了,a就在b之前,如果是在proceed()之后调用的,a就是在b之后的.
也就是说,proceed()方法,如果你的拦截功能在这之前调用,你是想在处理前做一些事.但如果你在它后调用,是等其它的拦截器处理完了再按顺序轮到它.

Platform,okhttp支持不同的平台,这些平台分别处理ssl与日志.目前我只看到这两个功能,日志还包括异常栈.

X509TrustManager,证书管理类,处理证书的相关问题.

StreamAllocation,从它开始,处理连接,获取数据:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...
try { //查找连接,
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
...
} catch (IOException e) {
throw new RouteException(e);
}
}
->
findHealthyConnection(){}: //从连接池中查找连接.
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
...
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;
}
}

最后到: ConnectionPool
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) { //判断是否可用.
streamAllocation.acquire(connection, true); //可用的话就请求使用它.
return connection;
}
}
return null;
}

连接是否可用,也比较多步骤:
public boolean isEligible(Address address, @Nullable Route route) {
// If this connection is not accepting new streams, we're done. //不接受新的流
if (allocations.size() >= allocationLimit || noNewStreams) return false;

// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

// If the host exactly matches, we're done: this connection can carry the address.
if (address.url().host().equals(this.route().address().url().host())) { //主机必须一样
return true; // This connection is a perfect match.
}

// At this point we don't have a hostname match. But we still be able to carry the request if
// our connection coalescing requirements are met. See also:
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

// 1. This connection must be HTTP/2. 只有http2,支持多链路.
if (http2Connection == null) return false;

// 2. The routes must share an IP address. This requires us to have a DNS address for both
// hosts, which only happens after route planning. We can't coalesce connections that use a
// proxy, since proxies don't tell us the origin server's IP address.
if (route == null) return false;
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
//如果是代理,没法判断原始服务器的ip地址,不能共用.

// 3. This connection's server certificate's must cover the new host.
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;

// 4. Certificate pinning must match the host.
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}

return true; // The caller's address can be carried by this connection.
}

这是一个双端队列:Deque<RealConnection> connections = new ArrayDeque<>();
然后又回到findConnection()继续执行:
如果没有从连接池中拿到连接,就重新选择路由.
如果最终还没找到就新建一个连接:
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);

// Do TCP + TLS handshakes. This is a blocking operation. 开始tcp+tls握手协议.
result.connect(connectTimeout, readTimeout, writeTimeout, 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.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
选择过程比较复杂:先查池中有对应可用的,再重新使用路由选择一次.没有就新建一个.
共用链路:
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();

this.connection = connection;
this.reportedAcquired = reportedAcquired;
//在这共用,一个连接里面有List<Reference<StreamAllocation>> allocations = new ArrayList<>();
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}

CallServerInterceptor在这里获取 HttpCodec httpCodec,根据前面得到的.到这个拦截器,就有了连接,http解码器.
其中最重要的是Http2Codec的keep-alive属性头,这个属性,客户端设置了,它会忽略.
不管是1,还是2codec,它都用了它自己的okio,而非java里的io类来处理.okio是在nio的基础上的优化后的一个io库.

BridgeInterceptor,这个拦截器在连接拦截器前面,它主要处理连接前,对请求头做一些处理.比如请求编码,连接属性,gzip压缩等.在返回链的时候,对gzip流处理,它的透明gzip压缩就在这实现的.但我用3.8的时候,实现不了,可能是请求头设置的问题.
理解了这些拦截器的功能和它们的实现,已经理解了okhttp的核心内容了.

RequestBody, ResponseBody,一个请求,一个返回,请求body还有多个子类,实现了各种不同的需求,可以传表单,传json,传文件等.这些的封装,相比urlconnection要简单的多了,它分的更细了,也清晰.
RequestBody还提供了默认的构造请求体的方法.

关于http1,都比较熟悉了,就是读流与其它数据.http2就比较复杂了.具体的操作类都在okhttp3.internal.http2包中.
Http2Connection,Http2Codec,Http2Writer,Http2Reader,Http2Stream 这些类为主要操作类.主要是针对http2协议作的一些事.可以通过了解http2的内容来了解okhttp的这部分做了什么事情.

除了支持普通的socket,还支持websocket.

到这,差不多核心功能都分析完了,可以简单地对比下其它的框架,它支持高并发,支持多种协议,特别是http2.容易扩展(添加拦截器就可以完成多数扩展了),api使用简单(关于请求方式,这里没有详细分析).
源码中samples有着非常详细的各种使用方式,okhttp的流行,也得益于文档非常详细.squareup在3.0以后包名都改了,它为开源界作了很多优秀的贡献,非常值得学习的精神,不像某讯,只会抄.

参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值