OKHTTP使用及原理

okhttp

通过Builder创建OkHttpClient

可以设置的东西

超时时间 0无超时,默认10秒

超时时间TCP超时时间

  • connectTimeout(long timeout, TimeUnit unit)

真实超时时间包括TCP socket 和IO操作,包括response

  • readTimeout(long timeout, TimeUnit unit)

IO操作时间

  • writeTimeout(long timeout, TimeUnit unit)

设置代理,用于创建Clinet的连接

  • proxy(@Nullable Proxy proxy)

设置代理选择器,默认是没有的创造一个默认的选择器

  • proxySelector(ProxySelector proxySelector)

设置可以接收发送cookie的操作,如果不设置,将不会提供

  • cookieJar(CookieJar cookieJar)

设置response的cache读写cache

  • setInternalCache(@Nullable InternalCache internalCache)

设置response的cache读写cache

  • cache(@Nullable Cache cache)

设置DNS服务查找主机的IP

  • dns(Dns dns)

设置socket用于创建链接,可以定义这个socket 比如接收本地地址等
如果没有则自动创建

  • socketFactory(SocketFactory socketFactory)

顾名思义ssl的socket链接

  • sslSocketFactory(SSLSocketFactory sslSocketFactory)

添加hostname认证,来确认通过请求的域名返回的response认证
如果没设置,用默认的

  • hostnameVerifier(HostnameVerifier hostnameVerifier)

设置固定证书,避免证书认证

  • certificatePinner(CertificatePinner certificatePinner)

设置响应服务器质疑的验证器,如果没有不会设置默认的

  • authenticator(Authenticator authenticator)

设置代理服务器质疑的验证器

  • proxyAuthenticator(Authenticator proxyAuthenticator)

设置http连接池

  • connectionPool(ConnectionPool connectionPool)

设置http到https或者反过来的重定向,如果没有设置,将遵循重定向协议

  • followSslRedirects(boolean followProtocolRedirects)

设置客户端的重定向,如果没有设置遵循重定向协议

  • followRedirects(boolean followRedirects)

设置客户端失败是否重试

  • retryOnConnectionFailure(boolean retryOnConnectionFailure)

设置执行异步请求和设置策略的dispather,必须不为空

  • dispatcher(Dispatcher dispatcher)

设置客户端和远程服务器的通信协议,一般不需要设置,设置主要是用于兼容性HTTP/2等

在OkHttp中的其他重要成员

为了okhttp3适应okhttp的接口,特增加了以下接口

1
2
3
4
5
6
Internal.instance = new Internal() {
.......
@Override public void addLenient(Headers.Builder builder, String line) {
builder.addLenient(line);
}
.....

简单的请求操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var clinet = OkHttpClient()
val request = Request.Builder()
.url("http://www.baidu.com/")
.build()
clinet.newCall(request).enqueue(object:Callback{
override fun onFailure(call: Call?, e: IOException?) {
Logger.d("faile")
}

override fun onResponse(call: Call?, response: Response?) {
Logger.d("success")
}

})

一行关键代码

1
clinet.newCall(request).enqueue()

newcall里面创建了一个Realcall,和一个eventlistener

1
2
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);

首先先看RealCall里面做了什么事情

1
2
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);

创建了一个call和一个对象
RealCall里面创建了一个拦截器

1
new RetryAndFollowUpInterceptor(client, forWebSocket);

execute()

1
2
client.dispatcher().executed(this);
eventListener.callStart(this);

EventListener

其作用是在请求的生命周期中回调各种okhttp的回调信息,可以在OhHttpClient创建的时候通过build传入,其伴随了Okhttp的整条链,哪都有它,方便回调

Dispather

这里调用了Dispather的execute,这个类用ExecutorService执行网络请求
可以定制最大请求数量,获取当前同步异步请求的数量

还可以设置每次请求执行后的操作。
在finished中调用

1
setIdleCallback(@Nullable Runnable idleCallback)

这里把创建的call请求传入了同步队列里

1
2
3
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}

然后在RealCall里面进行了操作来获取返回值

1
getResponseWithInterceptorChain()

拦截器

这里是okhttp的又一核心,拦截器
这里注意,首先OkHttp会把自己的拦截器加入到了拦截器队列当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
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);
}

我们看到这里创建了一系列的拦截器。我们首先先看在最后创建了一个Chain将所有拦截器传入并且,调用了Chain的proceed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;
//确认接收的流是同一个流
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// 确认这里只调用过一次
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

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);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}

if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}

if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}

return response;
}

这里为什么不用递归?因为在每个拦截器中执行后还有一些自己的操作,如果用了递归难免不灵活,比如异常的抛出等问题。这样可以在request和response的操作合并在一起

下面介绍一下调用的几个拦截器

最开始的拦截器retryAndFollowUpInterceptor

intercep()方法
首先从Chian中拿到了一些东西比如Call,比如eventListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@Override public Response intercept(Chain chain) throws IOException {
........
........
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;

int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}

Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
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, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}

// 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();
}

Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}

if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}

closeQuietly(response.body());

if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}

if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}

request = followUp;
priorResponse = response;
}
}

然后创建了一个StreamAllocation,此类会协调三者之间的关系

  • Connections:
  • Streams:
  • Calls

这里将创建一个StreamAllocation然后传递给下一个拦截器

1
2
3
4
5
6
7
  @Override public Response intercept(Chain chain) throws IOException {
......
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
response = realChain.proceed(request, streamAllocation, null, null);

......

在这之后对response的返回值进行了判断,是否需要SSL验证,重定向等一系列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}

Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}

在这之后进行了auth challengs的数量验证是否超过各个浏览器的最大上限

1
2
3
4
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

总结:RetryAndrFollowUpInterceptor主要是开启流,并且处理SSL认证,跟踪重定向等一些列操作。在这里没有进行重试操作,只不过判断了是否需要进行重试。

BridgeInterceptor

这个拦截器主要的作用是往header里面放东西然后如:

1
2
3
4
5
6
7
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}

if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}

进行下一个拦截器:

1
Response networkResponse = chain.proceed(requestBuilder.build());

拿到response的尾巴操作:
设置Response的cookie,并且对Response的header进行操作

CacheInterceptor

首先了解一些Cache

Cache里面有一个InternalCache接口,也是为了兼容okhttp而存在的

Cache的存储算法是Lru,存储是存在内存当中
存储的key是MD5后转换为16进制

1
2
3
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}

取操作先拿到key,然后从cache中拿到一个snapshot,构建一个response再拿到

1
2
3
4
5
6
7
......
snapshot = cache.get(key);
......
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
......
Response response = entry.response(snapshot);
.....

之后通过传递来的url和cache的response来创建一个缓存策略,
此策略在网络不可用或者是缓存不足的时候不可用.
之后会对网络或者缓存可不可用的情况做相应的处理

首先在cache缓存中判断

1
2
3
4
5
6
7
8
9
10
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();

if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}

return candidate;
}

如果缓存中有东西的话,那么就不用创建了,也不用请求网络,直接返回

1
2
3
4
5
6
7
8
9
10
11
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}

1
2
3
4
5
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}

上面的条件都不成立,那么就需要进行网络请求的拦截器了:

1
networkResponse = chain.proceed(networkRequest);

response回来之后的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();

cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}

最后将请求到的response来放入到缓存中

1
CacheRequest cacheRequest = cache.put(response);

ConnectInterceptor

区区几行代码

1
2
3
4
5
6
7
8
9
10
11
12
@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, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

这里主要是StreamAllocation在操作,StreamAllocation是在RetryAndFollowInterceptor中创建的

执行了其的newStream()方法
关键代码

1
2
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);

1
2
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);

StreamAllocation中创建了一个连接池connectionPool
首先请求会从连接池里面找http请求

1
Internal.instance.get(connectionPool, address, this, null);

然后进行路由选择

1
2
3
4
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}

从不同的路由中继续连接

1
2
3
4
5
6
7
8
9
10
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}

如果路由查找后还没有找到的话进行TPC+TLS握手,并从失败路由列表中删除此路由

1
2
3
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());

最终,将connection放入到connectionPool中

1
socket = Internal.instance.deduplicate(connectionPool, address, this);

CallServerInterceptor

这也是此流程最后一个拦截器

这里面做了HTTP1和HTTP2的选择并且写入相应的header以及
responseBody,在最后将其response返回

谢谢您的鼓励~