0%

okhttp详解2

上一张分析了主体请求流程,接下来深入的分析流程中的细节,直接看代码。

1.HttpEngine.java

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
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (transport != null) throw new IllegalStateException();

Request request = networkRequest(userRequest);

InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;

long now = System.currentTimeMillis();
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
networkRequest = cacheStrategy.networkRequest;
cacheResponse = cacheStrategy.cacheResponse;

if (responseCache != null) {
responseCache.trackResponse(cacheStrategy);
}

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

if (networkRequest != null) {
// Open a connection unless we inherited one from a redirect.
if (connection == null) {
connect();
}

transport = Internal.instance.newTransport(connection, this);

//这里callerWritesRequestBody为false,在engine实例化的时候赋值
if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
....
}
} else {
if (connection != null) {
Internal.instance.recycle(client.getConnectionPool(), connection);
connection = null;
}

if (cacheResponse != null) {
// We have a valid cached response. Promote it to the user response immediately.
this.userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
} else {
// We're forbidden from using the network, and the cache is insufficient.
this.userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
}

userResponse = unzip(userResponse);
}
}

(1)这里首先对request的head部分进行基础的配置。
(2)获取本地缓存。(这一块也不做深入分析,有兴趣的可以深入了解,专门分析数据缓存,volley框架中也有比较清晰的逻辑)。
(3)如果本地有缓存赋值给存储变量,如果没有缓存直接连接服务器。
(4)创建transport。transport包括两种HttpTransport和FrameTransport。

2.HttpEngine.java

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
/** Connect to the origin server either directly or via a proxy. */
private void connect() throws RequestException, RouteException {
if (connection != null) throw new IllegalStateException();

if (routeSelector == null) {
address = createAddress(client, networkRequest);
try {
routeSelector = RouteSelector.get(address, networkRequest, client);
} catch (IOException e) {
throw new RequestException(e);
}
}

connection = createNextConnection();
Internal.instance.connectAndSetOwner(client, connection, this);
route = connection.getRoute();
}

private Connection createNextConnection() throws RouteException {
ConnectionPool pool = client.getConnectionPool();

// Always prefer pooled connections over new connections.
for (Connection pooled; (pooled = pool.get(address)) != null; ) {
if (networkRequest.method().equals("GET") || Internal.instance.isReadable(pooled)) {
return pooled;
}
closeQuietly(pooled.getSocket());
}

try {
Route route = routeSelector.next();
return new Connection(pool, route);
} catch (IOException e) {
throw new RouteException(e);
}
}

(1)根据网络请求创建网络地址。
(2)创建连接。首先从连接池总获取连接,如果没有连接则创建新的连接。

3.ConnectionPool.java

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
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
public synchronized Connection get(Address address) {
Connection foundConnection = null;
for (Iterator<Connection> i = connections.descendingIterator(); i.hasNext(); ) {
Connection connection = i.next();
if (!connection.getRoute().getAddress().equals(address)
|| !connection.isAlive()
|| System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {
continue;
}
i.remove();
if (!connection.isFramed()) {
try {
Platform.get().tagSocket(connection.getSocket());
} catch (SocketException e) {
Util.closeQuietly(connection.getSocket());
// When unable to tag, skip recycling and close
Platform.get().logW("Unable to tagSocket(): " + e);
continue;
}
}
foundConnection = connection;
break;
}

if (foundConnection != null && foundConnection.isFramed()) {
connections.addFirst(foundConnection); // Add it back after iteration.
}

return foundConnection;
}

从连接池中获取连接,判断条件包括地址、socket是否可用,闲置时间是否超时。对于FrameConnection直接重新到连接池中,对于HttpConnection则是请求完成后再添加到连接池中。

4.Connection.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Connects this connection if it isn't already. This creates tunnels, shares
* the connection with the connection pool, and configures timeouts.
*/
void connectAndSetOwner(OkHttpClient client, Object owner) throws RouteException {
setOwner(owner);

if (!isConnected()) {
List<ConnectionSpec> connectionSpecs = route.address.getConnectionSpecs();
connect(client.getConnectTimeout(), client.getReadTimeout(), client.getWriteTimeout(),
connectionSpecs, client.getRetryOnConnectionFailure());
if (isFramed()) {
client.getConnectionPool().share(this);
}
client.routeDatabase().connected(getRoute());
}

setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
}

(1)设置连接归属,只有对于HttpConnection才会设置。
(2)如果是新创建的连接,则连接状态为false,则会开启连接,对于新创建的FrameConnection则会缓存。
(3)如果是FrameConnection则放置的连接池中。

5.Connection.java

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
void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
if (protocol != null) throw new IllegalStateException("already connected");

RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
Proxy proxy = route.getProxy();
Address address = route.getAddress();

if (route.address.getSslSocketFactory() == null
&& !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not supported: " + connectionSpecs));
}

while (protocol == null) {
try {
socket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.getSocketFactory().createSocket()
: new Socket(proxy);
connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
} catch (IOException e) {
Util.closeQuietly(socket);
socket = null;
handshake = null;
protocol = null;
httpConnection = null;
framedConnection = null;

if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}

if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
}

proxy.type()为Proxy.Type.HTTP,所以走 socket = address.getSocketFactory().createSocket();

6.Connection.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
socket.setSoTimeout(readTimeout);
Platform.get().connectSocket(socket, route.getSocketAddress(), connectTimeout);

if (route.address.getSslSocketFactory() != null) {
connectTls(readTimeout, writeTimeout, connectionSpecSelector);
} else {
protocol = Protocol.HTTP_1_1;
}

if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
framedConnection = new FramedConnection.Builder(route.address.uriHost, true, socket)
.protocol(protocol).build();
framedConnection.sendConnectionPreface();
} else {
httpConnection = new HttpConnection(pool, this, socket);
}
}

(1)开启连接。Platform针对不同的平台进行适配,然后连接socket。
(2)判断是否存在SslSocketFactory(也可以说是否是htts的地址)。因为http类型只支持HTTP_1_1, https支持http2.0/spdy/http1.1。
(3)针对https类型连接Tls。主要的目的是获取服务器支持什么协议。
(4)创建连接Connection(HttpConnection/FramedConnection)

7.Connection.java

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
private void connectTls(int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
if (route.requiresTunnel()) {
createTunnel(readTimeout, writeTimeout);
}

Address address = route.getAddress();
SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
socket, address.getUriHost(), address.getUriPort(), true /* autoClose */);

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

// Force handshake. This can throw!
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

...

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

// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
...
} finally {
...
}
}

主要是通过tls握手获取服务器支持的协议和获取sslSocket。

###总结

  1. sendRequest部分分析完成,分析时候我思考几个问题 :
    (1)Connection的连接复用http1.1 keep-alive和http2.0的连接复用有什么区别呢?
    (2)sendRequest的流程部分怎么没有发送请求和body的代码?

2.由上述分析可知:
(1)每个请求都会开启一个HttpEngine。
(2)每个HttpEngine都会包含一个Connection。(新创建或者连接池中获取)
(3)每个HttpEngine在会创建Transport(构造函数包括Connection)。Transport的作为读取数据和发送请求的一个桥梁,下一章中分析它的作用。
(4)每个Connection包含一个FrameConnection或者HttpConnection,这两个对象才是真正的操作数据,也在下一章分析。