OkHttp的优点
- 支持HTTP/2 协议,允许连接到同一个主机地址的所有请求共享Socket。
- 在HTTP/2协议不可用的情况下,通过连接池减少请求的延迟。
- GZip透明压缩减少传输的数据包大小。
- 响应缓存,避免同一个重复的网络请求。
用法
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();
}
}
这就是OkHttp 的简单用法,我们看到只要有 HttpClient、Request、Response
当我们
execute
的时候,我们是委托的 Dispatcher
的 execute
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
也就是添加到 Dispatcher
的 runningSyncCalls
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
这三个都是双向队列,添加到队列后,RealCall 就要 getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加开发者应用层自定义的Interceptor
interceptors.addAll(client.interceptors());
//这个Interceptor是处理请求失败的重试,重定向
interceptors.add(retryAndFollowUpInterceptor);
//这个Interceptor工作是添加一些请求的头部或其他信息
//并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
interceptors.add(new CacheInterceptor(client.internalCache()));
//这个Interceptor的职责是建立客户端和服务器的连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加开发者自定义的网络层拦截器
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
//一个包裹这request的chain
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//把chain传递到第一个Interceptor手中
return chain.proceed(originalRequest);
}
因为每一个interceptor的intercept方法里面都会调用chain.proceed()从而调用下一个interceptor的intercept(next)方法,这样就可以实现遍历getResponseWithInterceptorChain里面interceptors的item,实现遍历循环.
HTTP简介
OSI 七层模型 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP 头部样式
三次握手和四次挥手
HTTPS
八次握手
- 发起请求。需要提供 协议版本、随机数(第一个随机数)、支持的加密算法、支持的压缩算法
- 服务器确定加密协议的版本,以及加密的算法,然后也生成一个随机数。
- 服务器把加密协议的版本,加密的算法,随机数(第二个随机数)以及自己的证书一起发送给客户端
- 客户端收到服务器回应以后,首先验证服务器证书。
如果证书不是可信任机构颁布、或者证书中的域名和实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
验证证书主要根据服务端发过来的证书名称,在本地寻找其低级证书,并一级一级直到根证书,验证各级证书的合法性。 - 客户端验证证书后,再次产生一个随机数(第三个随机数),然后使用证书中的公钥加密这个随机值,再放一个ChangCipherSpec消息(用于告知服务器,客户端已经切换到之前协商好的加密套件(Cipher Suite)的状态,准备使用之前协商好的加密套件加密数据并传输了),还有整个前面所有消息的hash值,进行服务器验证,然后用新密钥加密一段数据一并发送到服务器,确保正式通信前无误。
- 服务器根据证书解密第三个随机数,这样就生成了 "会话密钥"
- 服务器像客户端发送 “ChangCipherSpec消息” 、 握手结束通知
- 客户端用之前生成的私钥解密服务器传过来的信息。
至此,整个握手阶段全部结束了。接下来,客户端与服务器进入加密通信,就完全是使用普通HTTP协议,只不过用"会话密钥"加密内容。
HTTP2.0
- 新的二进制格式。HTTP 1.x的解析是基于文本。
-
多路复用。一个request对应一个id,这样一个连接上可以有多个requst,每个连接的request可以随机的混杂在一起,接受方可以根据request的id将request再归属到各自不同的服务端请求里面
- 请求优先级。把HTTP消息分解为很多独立的帧后,就可以通过优化这些帧的交错和传输顺序,进一步提供性能。
- header压缩。HTTP1.x的header带有大量的信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一个header field表,既避免了重复的header传输,又减少了需要传输的大小。
- 服务端推送
-
新增的二进制分帧层。它定义了如何封装HTTP消息并在客户端与服务器之间传输
所有HTTP 2.0通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流以消息的形式发送。而消息由一或多个帧组成,而这些帧可以乱序发送,然后再根据每个帧首部流标识符重新组装。
异步请求
如果当前还可以执行异步任务,则入队,并立即执行,否则加入readyAsyncCalls队列,当一个请求执行完毕后,会调用promoteCalls(),来把readyAsyncCalls队列中的Async移出来并加入到runningAsyncCalls,并开始执行。然后在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response.
对比同步和异步任务,我们会发现:同步请求和异步请求原理都是一样的,都是在getResponseWithInterceptorChain()函数通过Interceptor链条来实现网络请求逻辑,而异步任务则通过ExecutorService来实现的。PS:在Dispatcher中添加一个封装了Callback的Call的匿名内部类AsyncCall来执行当前 的Call。这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。
连接
Address
在 newRealCall
的时候配置了 transmitter
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
Transmitter
中有 connectionPool
还有 exchangeFinder
,RetryAndFollowUpInterceptor
会在intercept()
中调用 prepareToConnect()
, 它的参数之一 Address 则是通过 createAddress()
产生的.
Address
的url字段仅仅包含HTTP请求的url的schema+host+port三部分的信息,而不包含path和query等信息。它还有一个重要的方法 equalsNonHost ()
, 这个方法会在连接池复用的时候调用,如果返回 true, 那么就可以使用 RealConnection
的复用
RouteSelector
在 ExchangeFinder
初始化的时候 new 一个 RouteSelector
。
这个类主要是选择连接到服务器的路由,选择的连接需要是代理服务器、IP地址、TLS模式 三者中的一种。这个选择的连接是可以被回收的。
因为HTTP请求连接到服务器的时候,需要找到一个Route,然后依据代理协议规则与特定目标建立TCP连接。如果是无代理的情况,是与HTTP服务器建立TCP连接,对于SOCKS代理和http代理,是与代理服务器建立tcp连接,虽然都是与代理服务器建立tcp连接,但是SOCKS代理协议和http代理协议又有一定的区别。
有的网站会借助于域名做负均衡,常常会有域名对应不同IP地址的情况。在OKHTTP中,对Route连接有一定的错误处理机制。OKHTTP会逐个尝试找到Route建立TCP连接,直到找到可用的哪一个。这样对Route信息有良好的管理。OKHTTP中借助RouteSelector类管理所有路由信息,并帮助选择路由。
public final class Route {
final Address address;
final Proxy proxy;
final InetSocketAddress inetSocketAddress;
}
在构造函数中就会调用 resetNextProxy()
来收集路由,分为两种情况:1.收集所有的代理;2.收集特定的代理服务器的目标地址。
它们的实现也是通过两种方式:
- 通过外部address传入代理。因为是来自 OkHttpClient,我们可以指定代理
- 借助于ProxySelectory获得多个代理。默认收集的所有代理保存在列表proxies中
RouteSelector
有两个重要的成员函数 hasNext()
和 next()
hasNext()
表明是否还有可用的路由
public boolean hasNext() {
return hasNextInetSocketAddress()
|| hasNextProxy()
|| hasNextPostponed();
}
//是否还有代理
private boolean hasNextProxy() {
return nextProxyIndex < proxies.size();
}
//是否还有socket地址
private boolean hasNextInetSocketAddress() {
return nextInetSocketAddressIndex < inetSocketAddresses.size();
}
//是否还有延迟路由
private boolean hasNextPostponed() {
return !postponedRoutes.isEmpty();
}
next()
方法就是用来获取可能的连接地址
- 对于没有配置代理的情况,会对HTTP服务器的域名进行DNS域名解析,并为每个解析到的IP地址创建连接的目标地址
- 对于SOCKS代理,直接以HTTP的服务器的域名以及协议端口创建连接目标地址
- 对于HTTP代理,则会对HTTP代理服务器的域名进行DNS域名解析,并为每个解析到的IP地址创建 连接的目标地址
public Route next() throws IOException {
// Compute the next route to attempt.
if (!hasNextInetSocketAddress()) {
if (!hasNextProxy()) {
if (!hasNextPostponed()) {
throw new NoSuchElementException();
}
return nextPostponed();
}
lastProxy = nextProxy();
}
lastInetSocketAddress = nextInetSocketAddress();
Route route = new Route(address, lastProxy, lastInetSocketAddress);
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
// We will only recurse in order to skip previously failed routes. They will be tried last.
return next();
}
return route;
}
对应的是 hasNextPostponed()
, hasNextProxy()
, hasNextInetSocketAddress()
RetryAndFollowUpInterceptor
上面我们分析了 Address
、RouteSelector
,现在我们看它们是怎么用的
public final class RetryAndFollowUpInterceptor implements Interceptor {
private static final int MAX_FOLLOW_UPS = 20;
private final OkHttpClient client;
public RetryAndFollowUpInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
// 根据连接池、Address,构建出了 exchangeFinder
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
// 将配置好的 transmitter 传递个下一个拦截器,即 BridgeIntercepter
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} 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, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
}
执行过程如下:
- 先是获取 Call 的
transmitter
, transmitter中是有 connectionPool 的 - 开启 while 循环
- 执行
prepareToConnect
,判断是否是相同连接、是否需要maybeReleaseConnection()
,并重置exchangeFinder
,这个finder
就是用来寻找可用Connection
- 执行下一个拦截器
- 如果 priorResponse 不为空,说明得到了 response
- 获取从
RouteSelector
中得到的 Route - 执行
followUpRequest()
查看响应是否需要重定向,如果不需要重定向则返回当前请求 - 重定向次数+1,同时判断是否达到最大限制数量。是:退出
- 重置request,并把当前的Response保存到priorResponse,进入下一次的while循环
总的来说:
就是不停的循环来获取response,每循环一次都会获取下一个request,如果没有request,则返回response,退出循环。而获取的request 是根据上一个response 的状态码确定的。
BridgeInterceptor
主要负责对Request和Response报文进行加工
- 在发送阶段补全了一些header,如Content-Type、Content-Length、Transfer-Encoding、Host、Connection、Accept-Encoding、User-Agent 等。
- 如果需要gzip压缩则进行gzip压缩
- 加载Cookie
- 随后创建新的request并交付给后续的interceptor来处理,以获取响应。
- 保存Cookie
- 如果服务器返回的响应content是以gzip压缩过的,则会先进行解压缩,移除响应中的header Content-Encoding和Content-Length,构造新的响应返回。
- 否则直接返回 response