Android OkHttp3 源码解析

前言

总觉得网络这一块不是那么的熟悉,也找不到窍门,索性看一个网络请求框架,来加深自己对网络请求的认识。这个系列应该会很长,毕竟这个库也不简单,里面包含了很多知识,我会先从使用,再到简单 API源码 的分析,再到框架内部各个模块的仔细研读这样一个顺序去分析。

OkHttp3

这个库可以说是很优秀,使用起来也很简单,关键是,你可以对它进行各种定制,来做到各种各样的功能,在Android 中,Retrofit 底层就是用 OkHttpClient 去实现的。OkHttp 虽然没有浏览器那么强大,但他内部实现了很多请求服务器数据的场景,能处理很多不同的结果,比如常见的 100 101 Continue 301 302 重定向等等,遇到301,则会触发重定向,从Response 里边取出 Location ,然后再封装 Request,重新访问。这些它在内部帮我们直接去实现,里面还有请求的缓存,应该是根据服务端的Cache-Control 来做的,还有 HTTP 的连接复用,它支持 HTTP1.1 HTTP2 也支持 HTTPS,底层分别使用 Socket 和 sslSocket 写的。另外,它提供了同步,异步的请求方式,可以很方便使用者进行调用。说了很多特性,但我目前仅仅是一个抽象,很难说清楚里面的实现,所以就想好好去看一下里面的实现原理,提升一下内功

使用篇

官方的 GET Sample

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
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class GetExample {
// 首先构建一个 OkHttpClient ,当然你也可以使用 Builder 去构建
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
// 构建一个 Request 默认是 GET 请求
Request request = new Request.Builder()
.url(url)
.build();
// 同步的方式去执行
// 返回一个 Response 对象
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}

public static void main(String[] args) throws IOException {
GetExample example = new GetExample();
String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
System.out.println(response);
}
}

POST Sample

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
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class PostExample {
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

// 在构建一个 Request 的时候,提交一个 body
// 通过 RequestBody.create() 来创建一个 Json 格式的 body
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
// 一个小小的API,会在请求结束后调用
client.dispatcher().setIdleCallback(new Runnable() {
@Override
public void run() {
System.out.println("Request is Over");
}
});

try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}

String bowlingJson(String player1, String player2) {
return "{'winCondition':'HIGH_SCORE',"
+ "'name':'Bowling',"
+ "'round':4,"
+ "'lastSaved':1367702411696,"
+ "'dateStarted':1367702378785,"
+ "'players':["
+ "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39},"
+ "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}"
+ "]}";
}

public static void main(String[] args) throws IOException {
PostExample example = new PostExample();
String json = example.bowlingJson("Jesse", "Jake");
String response = example.post("http://www.roundsapp.com/post", json);
System.out.println(response);
}
}

可以看到,GET,POST 还是挺简单的,为了满足好奇心,我们看一个方法的源码,OkHttpClient 的 newCall 方法,类似工厂的方法,构建一个 RealCall 的,然后调用 RealCall 的 execute 方法执行

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
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}

@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
// 只是放入同步队列
client.dispatcher().executed(this);
// 真正的网络调用在这个地方
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
// 结束那个任务
client.dispatcher().finished(this);
}
}

// 从队列里面移除一个 call
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
// 移除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
// 触发任务
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
// dispatcher 的api,通过 client 获取然后设置
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}

下集预告,OkHttp 的核心,请求链都是在这个这个方法里,调用是从上往下,又从下传到最上,如果到了 CallServerInterceptor,它把数据发到服务端,然后获取结果,再返回给上层处理

  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor
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);
}

小结

我们大致知道了流程,就是先创建一个 OkHttpClient,然后再创建一个 Request,然后再调用 newCall 执行,暂时我们看了同步的方式

前言

之前我们掌握了 OkHttpClient 的基本使用,在最后面我们抛出了很多 Interceptor,现在我们看一下 Interceptor 到底是什么东西,英文是拦截器的意思,当我看了源码后,我觉得我更容易理解的是,处理器

定义

如果定义自己的 interceptor,要实现 intercept,里面有一个内部类 Chain ,链

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
public interface Interceptor {
Response intercept(Chain chain) throws IOException;

interface Chain {
Request request();

Response proceed(Request request) throws IOException;

/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();

Call call();

int connectTimeoutMillis();

Chain withConnectTimeout(int timeout, TimeUnit unit);

int readTimeoutMillis();

Chain withReadTimeout(int timeout, TimeUnit unit);

int writeTimeoutMillis();

Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}

再看一下这个函数,如果我们没有自定义的 interceptor,最先的是RetryAndFollowUpInterceptor,他是一个 重试和重定向的拦截器

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

我们看一下 RealInterceptorChain, 他实现了 Interceptor.Chain

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}

@Override public Connection connection() {
return connection;
}

@Override public int connectTimeoutMillis() {
return connectTimeout;
}

@Override public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, millis, readTimeout, writeTimeout);
}

@Override public int readTimeoutMillis() {
return readTimeout;
}

@Override public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, millis, writeTimeout);
}

@Override public int writeTimeoutMillis() {
return writeTimeout;
}

@Override public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, readTimeout, millis);
}

public StreamAllocation streamAllocation() {
return streamAllocation;
}

public HttpCodec httpStream() {
return httpCodec;
}

@Override public Call call() {
return call;
}

public EventListener eventListener() {
return eventListener;
}

@Override public Request request() {
return request;
}

@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
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 we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.
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");
}

// Confirm that the intercepted response isn't null.
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;
}
}

我把最关键的代码贴出来,Interceptors 获取实例Interceptor,比如第一个的话,是RetryAndFollowUpInterceptor,然后执行 intercept方法,将下一个RealInterceptorChain 传进去,所以其实这里是一个递归,会不断调用,知道最后一层返回 Response

1
2
3
4
5
6
7
// Call the next interceptor in the chain.
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);

RetryAndFollowUpInterceptor 实现机制,里面有个重要的东西,重定向次数,最大为20,还有 OkHttpClient 的实例,还有一个volatile 的 canceled ,标记是否取消,还有 forWebSocket,这个等到用到了再解释,StreamAllocation也是

1
public final class RetryAndFollowUpInterceptor implements Interceptor {}
1
2
3
4
5
6
7
8
9
10
11
/**
* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
*/
private static final int MAX_FOLLOW_UPS = 20;

private final OkHttpClient client;
private final boolean forWebSocket;
private volatile StreamAllocation streamAllocation;
private Object callStackTrace;
private volatile boolean canceled;

我们看一下他的 intercept 方法,第一个 interceptor 是RetryAndFollowUpInterceptor,Chain 包含了当前是第几个拦截器的信息

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
92
93
94
95
96
97
98
99
100
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 构建一个 StreamAllocation对象
// client连接池,Address 地址,call对象,监听对象
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 {
// 调用 Chain 对象的 proceed,这里最后也
// 会进到下一个 Interceptor 的 intercept
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;
}
}

判断是否需要跟踪的代码,可以好好补一补状态码了

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();

final String method = userResponse.request().method();
switch (responseCode) {
// 407 代理服务器需要认证
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
// 401 未授权,根据Header Authenticate 进行授权
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
// 如果是非GET,HEAD,则不管,注意 307,308 和 301 302 的区别
// 308
case HTTP_PERM_REDIRECT:
// 307
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
// 根据Location 重定向,构建一条新的 Request
// 300
case HTTP_MULT_CHOICE:
// 301
case HTTP_MOVED_PERM:
// 302
case HTTP_MOVED_TEMP:
// 303
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;

String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);

// Don't follow redirects to unsupported protocols.
if (url == null) return null;

// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;

// Most redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}

// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}

return requestBuilder.url(url).build();
// 408 客户端连接超时
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}

if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}

if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
// 尝试重试
if (retryAfter(userResponse, 0) > 0) {
return null;
}

return userResponse.request();
// 503
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null;
}

if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request();
}

return null;

default:
return null;
}
}

解析重试的时间,在客户端请求超时,服务器负载过高造成的都有可能进行重试,这时候会根据服务器给的 retry-after 属性进行重试,默认的重试时间为0,立刻重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private int retryAfter(Response userResponse, int defaultDelay) {
String header = userResponse.header("Retry-After");

if (header == null) {
return defaultDelay;
}

// https://tools.ietf.org/html/rfc7231#section-7.1.3
// currently ignores a HTTP-date, and assumes any non int 0 is a delay
if (header.matches("\\d+")) {
return Integer.valueOf(header);
}

return Integer.MAX_VALUE;
}

判断是否是同一个连接的方法,比较host 和 port,以及scheme(http 或 https),至于为什么要这样写,我们在之后会看到原理。

1
2
3
4
5
6
7
8
9
10
/**
* Returns true if an HTTP request for {@code followUp} can reuse the connection used by this
* engine.
*/
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}

代理类型,三种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Type {
/**
* Represents a direct connection, or the absence of a proxy.
*/
DIRECT,
/**
* Represents proxy for high level protocols such as HTTP or FTP.
*/
HTTP,
/**
* Represents a SOCKS (V4 or V5) proxy.
*/
SOCKS
};

小结

我们详细的看了这个 RetryAndFollowUpInterceptor,可能你现在还不是很清晰,但相信之后看了其他的一些Interceptor,你就会十分了解这个东西

前言

之前我们学习了 OkHttp3 的使用以及 RetryAndFollowUpInterceptor 的源码,至今还记忆尤新,今天我给大家带来一个 BridgeInterceptor,这个东西也很重要,那它到底是个什么东西呢?

是什么东西

Bridge,桥接,是不是说起着承上启下的作用,的确如此,原文是这么说的

Bridges from application code to network code. First it builds a network request from a user request. Then it proceeds to call the network. Finally it builds a user response from the network response

翻译过来,就是说将请求或者响应在应用层和网络层相互转化

源码

里面的 Intercept 方法,首先,获取到用过刚开始构建的 request 对象,因为用户仅仅配置了几个简单的属性,比如 url,method等,但这远远不够,我们传给服务器的数据不仅仅如此,如果看过 http 协议,我们应该比较了解明文模式下的数据格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();

RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
// 如果body 的长度为 -1 那么将使用
// Transfer-Encoding 设置为 chunked
// 意思是分块传输
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}

给 request 添加 Host,以及Connection 为 Keep-Alive,以及使用 Accept-Encoding 为 gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}

if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}

封装Cookie,以及User-Agent 为 okhttp
关于 Cookie 的格式如下,其中 Name = Value 必须要有

1
Set-Cookie: Name = Value; Comment = value; Domain = value; Max-Age = value; Path = Value;Secure; Version = 1 * DIGIT;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}

if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}

public final class Version {
public static String userAgent() {
return "okhttp/3.11.0";
}

private Version() {
}
}

之后就是转发给下一个 Interceptor 了,在拿到数据以后,处理一下 cookie 的数据,具体就是更新Cookie,之后就是处理数据了,如果服务端使用了gzip,我们就需要解压,在构建 response,之后再返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Response networkResponse = chain.proceed(requestBuilder.build());

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);

if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}

差不多到这就完了

小结

今天的bridgeInterceptor主要做的工作便是封装 request 和 处理 response 这个东西,中间我们学习了 content-encoding accept-encoding 等属性,另外详细的了解了cookie,终于搞明白了这东西的格式,收获不小

欢迎讨论

前言

桥接 Interceptor 之后就是缓存 Interceptor 了,缓存在浏览器中运用十分广泛,如果在加载网页时使用了缓存,主要是二次加载,那么一些不变的内容就可以不用去重新请求,这样数据就不用来回传送,性能便有所提高,通常我们遇到的 304 响应码便是缓存的象征,关于缓存还需要掌握很多请求头的知识,比如ETag,If-None-Match,If-Modified-Since,Last-Modified-Time 等等,这些属性是客户端,服务端缓存消息交互的关键。因此我们要学习缓存的话,这些知识必不可少。

是什么东西?

用于处理客户端的缓存,缓存的内容实在客户端的,但通常与服务器是脱不了关系的,这里我们需要明白的是,关于网络缓存有很多很多中,可以在很多地方做缓存,而这里是在客户端做缓存。

源码学习

缓存的内核,所有的数据存取都与这家伙有关,内部是使用 Cache,如果我们需要使用缓存,那需要我们自己配置,默认是没有本地缓存的

1
2
3
final InternalCache cache;

public final class Cache implements Closeable, Flushable {}

构造函数,我们可以看到,内部使用了 DiskLruCache,如果不了解 DiskLruCache的同学,可以参考我的另外一篇文章 DiskLruCache 源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final DiskLruCache cache;

/* read and write statistics, all guarded by 'this' */
int writeSuccessCount;
int writeAbortCount;
private int networkCount;
private int hitCount;
private int requestCount;

public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}

Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}

先来看看如何缓存放进去,当然,能缓存的请求类型只有 GET,HEAD,即首先判断 Response 类型,眼看这里代码写得好烂,估计作者可能睡着了,有空去 PR,

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
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();

if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}

if (HttpHeaders.hasVaryAll(response)) {
return null;
}

Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}

根据 url 的 md5 值的16进制来获取key,为什么要这么做呢?别想了,就这样吧

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

验证请求能否缓存

1
2
3
4
5
6
7
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}

下面的代码是获取缓存的代码,同样先获取 key,然后获取缓存

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

@Nullable Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}

try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}

Response response = entry.response(snapshot);

if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}

return response;
}

我们在回过头来看,这里将获取一个 CacheStrategy,通常,cacheResponse 为空,所以这一步几乎没有任何缓存,只是封装了一下 request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

private CacheStrategy getCandidate() {
// No cached response.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}

// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}

比较重要的逻辑,大致的思路为:首先检查缓存,如果存在,则根据缓存封装一个Request,带上 ETag,If-Modify-Since 标签,访问服务器,否则直接访问服务器

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
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;

long now = System.currentTimeMillis();

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

if (cache != null) {
cache.trackResponse(strategy);
}

if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}

// If we're forbidden from using the network and the cache is insufficient, fail.
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();
}

// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}

Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}

访问完以后,如果是304返回码,说明可以从缓存中拿数据,那我们就可以从缓存中拿数据,构建一个新的 Response 就行了
另一个操作就是,存储数据,即根据服务端的返回码把可以缓存的数据保存在 Cache 里,主要是根据 Cache-control Pragma ,只要能缓存,尽量缓存

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
 // If we have a cache response too, then we're doing a conditional get.
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();

// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}

Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();

if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}

if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}

return response;
}

可以欣赏一下这个方法,可以加深我们对缓存的认识

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
92
93
94
95
96
97
98
99
100
public static CacheControl parse(Headers headers) {
boolean noCache = false;
boolean noStore = false;
int maxAgeSeconds = -1;
int sMaxAgeSeconds = -1;
boolean isPrivate = false;
boolean isPublic = false;
boolean mustRevalidate = false;
int maxStaleSeconds = -1;
int minFreshSeconds = -1;
boolean onlyIfCached = false;
boolean noTransform = false;
boolean immutable = false;

boolean canUseHeaderValue = true;
String headerValue = null;

for (int i = 0, size = headers.size(); i < size; i++) {
String name = headers.name(i);
String value = headers.value(i);

if (name.equalsIgnoreCase("Cache-Control")) {
if (headerValue != null) {
// Multiple cache-control headers means we can't use the raw value.
canUseHeaderValue = false;
} else {
headerValue = value;
}
} else if (name.equalsIgnoreCase("Pragma")) {
// Might specify additional cache-control params. We invalidate just in case.
canUseHeaderValue = false;
} else {
continue;
}

int pos = 0;
while (pos < value.length()) {
int tokenStart = pos;
pos = HttpHeaders.skipUntil(value, pos, "=,;");
String directive = value.substring(tokenStart, pos).trim();
String parameter;

if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
pos++; // consume ',' or ';' (if necessary)
parameter = null;
} else {
pos++; // consume '='
pos = HttpHeaders.skipWhitespace(value, pos);

// quoted string
if (pos < value.length() && value.charAt(pos) == '\"') {
pos++; // consume '"' open quote
int parameterStart = pos;
pos = HttpHeaders.skipUntil(value, pos, "\"");
parameter = value.substring(parameterStart, pos);
pos++; // consume '"' close quote (if necessary)

// unquoted string
} else {
int parameterStart = pos;
pos = HttpHeaders.skipUntil(value, pos, ",;");
parameter = value.substring(parameterStart, pos).trim();
}
}

if ("no-cache".equalsIgnoreCase(directive)) {
noCache = true;
} else if ("no-store".equalsIgnoreCase(directive)) {
noStore = true;
} else if ("max-age".equalsIgnoreCase(directive)) {
maxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("s-maxage".equalsIgnoreCase(directive)) {
sMaxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("private".equalsIgnoreCase(directive)) {
isPrivate = true;
} else if ("public".equalsIgnoreCase(directive)) {
isPublic = true;
} else if ("must-revalidate".equalsIgnoreCase(directive)) {
mustRevalidate = true;
} else if ("max-stale".equalsIgnoreCase(directive)) {
maxStaleSeconds = HttpHeaders.parseSeconds(parameter, Integer.MAX_VALUE);
} else if ("min-fresh".equalsIgnoreCase(directive)) {
minFreshSeconds = HttpHeaders.parseSeconds(parameter, -1);
} else if ("only-if-cached".equalsIgnoreCase(directive)) {
onlyIfCached = true;
} else if ("no-transform".equalsIgnoreCase(directive)) {
noTransform = true;
} else if ("immutable".equalsIgnoreCase(directive)) {
immutable = true;
}
}
}

if (!canUseHeaderValue) {
headerValue = null;
}
return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, immutable,
headerValue);
}

大致的缓存就差不多了

小结

我们今天研究了一下缓存策略,到底什么时候可以缓存,什么时候不能缓存,缓存到底是什么操作,我们今天都弄清楚了,总的来说,缓存并不是说不访问服务器了,而是说访问服务器看是否有没有数据的变化,另外缓存针对于 GET HEAD 请求,另外 DiskLruCache 的使用

欢迎讨论~

前言

几天过去了,之前我们依次学习了 RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor 看到了我们的 Request 怎么一步一步转化成 NetworkRequest,亦或是 Response 怎么处理 Response,重试跟踪,转化,缓存,但我们至今还未看到数据是怎么传送出去,今天,我们将要了解的 ConnectInterceptor 仍然没有传数据过去,但是他在建立连接,复用连接起着重要的作用,内部维护着一个连接池,连接使用的是 socket,sslsocket,分别对应HTTP1 HTTP2 的 socket 和 HTTPS 的 sslSocket 连接,关于 sslSocket,又涉及到安全问题,比如说,ssl 的建立,证书的认证,数据的加密传输,解密等等。

是什么东西?

关于连接的拦截器,里面管理着 socket 的连接,这样方便我们之后的请求做连接复用,提高效率

源码解析

首先获取 streamAllocation,然后新建一个 stream,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

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

通过 findHealthyConnection 获取一个连接,在根据 RealConnection 建立 HttpCodec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();

try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}

方法有点长,主要的思路是先从连接池里查找,若找不到就创建一个 socket 连接

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");

// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}

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

if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}

// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}

synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");

if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
List<Route> routes = routeSelection.getAll();
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;
}
}
}

if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}

// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}

// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}

// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
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;
}
}
closeQuietly(socket);

eventListener.connectionAcquired(call, result);
return result;
}

建立protocol连接,此处如果是 TLS ,则建立 sslsocket 连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
if (route.address().sslSocketFactory() == null) {
if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = rawSocket;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
startHttp2(pingIntervalMillis);
return;
}

socket = rawSocket;
protocol = Protocol.HTTP_1_1;
return;
}

eventListener.secureConnectStart(call);
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);

if (protocol == Protocol.HTTP_2) {
startHttp2(pingIntervalMillis);
}
}

建立 TSL,在 rawSocket 的基础上建立 sslSocket 连接,然后握手,检测证书,返回连接

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
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

// Configure the socket's ciphers, TLS versions, and extensions.
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}

// Force handshake. This can throw!
sslSocket.startHandshake();
// block for session establishment
SSLSession sslSocketSession = sslSocket.getSession();
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
}

// Check that the certificate pinner is satisfied by the certificates presented.
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());

// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}

关于 SSL 更多的内容,可自行 google

小结

ConnectInterceptor 的作用我们现在大致有个轮廓了,但总感觉还不够清晰,需要多加巩固,其中有关于 X509TrustManager,自定义证书的信任,其他的由 sslContext 帮我们做了处理

前言

激动人心的时刻将要来了,我们将会看到数据是怎么在客户端服务端之间进行交互的,但是再此之前,我们有必要了解一下 socket 纠结是什么东西,与 HTTP 协议,TCP/IP 协议之间的关系又是如何,这些理论暂且自行查找,我在这暂且简单说一说,socket 并不是协议,它实现了 TCP/UDP 的语义,而HTTP 是属于应用层的协议,我们并不能直接使用 TCP、UDP 来传输数据,而这时候我们可以使用 socket 来实现。当我们使用 socket 建立 TCP 连接,之后便可以使用这个连接用来收发数据。当你对 HTTP 协议了解深入了,就会体会到这中间的妙处,建议是看一下 HTTP 协议,可以看 RFC 官方文档,下次在简历上可以说精通 TCP/IP、HTTP 协议,这可比项目吸引眼球多了。又想去改简历了

1
2
                   |      SOCKET      |
IP -> TCP -> HTTP

HTTP 协议

若要理解 HTTP 协议,莫过于使用它,安利一下 wireshark 这个工具,还是超级好用的,可以清楚地看到包的内容,各层协议封装的内容,看多了就会很清楚包里面的数据格式,之所以然而知其所以然。
我们在使用 socket 的时候,通常就是往 socket 里写入 HTTP 消息,下面给出两个简单的例子,我们只需要把这些文本写入流中便可,即可完成一次简单的 HTTP 通信,当然,实际的协议要比这复杂得多,很多协议需要浏览器与服务器自行实现。
Request

1
2
3
4
5
GET URL HTTP/1.1\r\n   // 请求行
Host: www.cnblogs.com\r\n // 请求头
Connection: keep-alive\r\n
Accept: application/json, text/javascript, */*; q=0.01\r\n
\r\n

Response

1
2
3
4
5
HTTP/1.1 200 OK\r\n     // 响应行
Date: Wed, 22 Aug 2018 02:10:58 GMT\r\n // 响应头
...
\r\n // 空行
File Data: 5 bytes // 请求数据

源码解析

1
public final class CallServerInterceptor implements Interceptor {}

通常,是由 httpCodec.writeRequestHeaders(request) 写入 socket 流中,然后通过 flushRequest 刷新缓存,请求便发给服务端了,中间可能遇到很多情况

  • 100 如果客户端使用了 Expect:100-continue,那么客户端会先发送 headers,获取响应,如果服务器能够接收并且发送了 100 返回码,那么客户端会继续发送数据构建 Response
  • 101 协议升级,重新构建 Response
  • 204 205 检查是否有异常
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
@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();

realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), 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();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}

if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} 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();

当然,其中也包括 https的传输,而使用 sslSocket 惊喜加密通信。

小结

基本上我们看到了 OkHttp 必备的 Interceptor,大致的流程我们已经清楚了,接下来就是细节推导,我们时常更新细节