OkHttp的请求过程
在上一篇中我们大概了解了OkHttp大体的执行过程,本篇将进一步介绍OkHttp请求的具体过程,这设计到部分拦截器的具体细节。
####建立连接
关于内置的拦截器将会在其他篇章中分别做介绍,这里只介绍涉及到具体的Http请求过程的拦截器,它们是ConnectInterceptor和CallServerInterceptor分别负责连接的建立和Http请求的发起。我们先看ConnectInterceptor
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();//得到一个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, doExtensiveHealthChecks);//分配一个Stream
RealConnection connection = streamAllocation.connection();//建立一个连接
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
ConnectInterceptor的最终目的是和服务器建立连接,它首先从拦截器链中得到的StreamAllocation,但是拦截器链在初始时streamAllocation并未创建,那么它是何时创建,在哪里创建呢?可以确定的是它一定是在拦截器链ConnectInterceptor之前的拦截器中进行初始化的,通过寻找发现它是在RetryAndFollowUpInterceptor中创建的。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
……
}
这个StreamAllocation到底是做什么的呢?我们看看它的具体实现
/**
* This class coordinates the relationship between three entities:
*
* <ul>
* <li><strong>Connections:</strong> physical socket connections to remote servers. These are
* potentially slow to establish so it is necessary to be able to cancel a connection
* currently being connected.
* <li><strong>Streams:</strong> logical HTTP request/response pairs that are layered on
* connections. Each connection has its own allocation limit, which defines how many
* concurrent streams that connection can carry. HTTP/1.x connections can carry 1 stream
* at a time, HTTP/2 typically carry multiple.
* <li><strong>Calls:</strong> a logical sequence of streams, typically an initial request and
* its follow up requests. We prefer to keep all streams of a single call on the same
* connection for better behavior and locality.
* </ul>
...
*/
public final class StreamAllocation {
public final Address address;
private Route route;
private final ConnectionPool connectionPool;
private final Object callStackTrace;
// State guarded by connectionPool.
private final RouteSelector routeSelector;
private int refusedStreamCount;
private RealConnection connection;
private boolean released;
private boolean canceled;
private HttpCodec codec;
public StreamAllocation(ConnectionPool connectionPool, Address address, Object callStackTrace) {
this.connectionPool = connectionPool;
this.address = address;
this.routeSelector = new RouteSelector(address, routeDatabase());
this.callStackTrace = callStackTrace;
}
……
}
从注释中我们可以了解到StreamAllocation的作用,它负责协调Connections,Streams以及Calls三者的关系,Connnections是代表了连接远程服务的物理socket连接,Streams是依赖于Connnections逻辑上的Http Request/Response请求对。每个connections都有它的分配上限,这个决定了connections可以并发支持的streams数目,Calls是streams逻辑上的序列,它实际上是一组Request。清楚了这些,再看其构造方法,其中ConnectionPool想必就是负责物理连接的,Address描述了我们需要连接的远端服务,实际上它是通过url来得到的。
接下来要为一次请求创建一个Stream,这个Stream是逻辑上的Request/Response对,通过StreamAllocation的newStream方法创建。
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();//超时连接时间
int readTimeout = client.readTimeoutMillis();//读超时时间
int writeTimeout = client.writeTimeoutMillis();//写超时时间
boolean connectionRetryEnabled = client.retryOnConnectionFailure();//是否支持重连
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);//为Stream找到一个可用的Connection
HttpCodec resultCodec = resultConnection.newCodec(client, this);//取到HttpCodec
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
newStream方法为stream找到一个可用的连接,然后通过连接的newCodec得到一个HttpCodec,它负责对Http的Request进行编码以及对Response进行解码。接着再看findHealthyConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
通过一个while循环直到找到一个health RealConnection.
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {//对连接池加锁
...
// Attempt to use an already-allocated connection.
//重用已经分配的连接
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
//尝试从连接池中获取一个连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
//需要选择一个路由
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// Now that we have an IP address, make another attempt at getting a connection from the pool.
// This could match due to connection coalescing.
Internal.instance.get(connectionPool, address, this, selectedRoute);//从连接池中取到一个匹配路由的连接
if (connection != null) return connection;
// 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);//保存在connection中
}
// Do TCP + TLS handshakes. This is a blocking operation.
//建立TCP和TCL的连接,这是一个阻塞操作
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// 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);
return result;//返回连接
}
findConnection负责从连接池中取出一个匹配的RealConnection,如果未找到就创建一个新的,并通过connect建立和远端服务的连接。这个是通过RealConnection的connect方法来实现的。
public final class RealConnection extends Http2Connection.Listener implements Connection {
private static final String NPE_THROW_WITH_NULL = "throw with null exception";
private final ConnectionPool connectionPool;
private final Route route;
// The fields below are initialized by connect() and never reassigned.
/** The low-level TCP socket. */
private Socket rawSocket;//底层 TCP的socket
/**
* The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
* {@link #rawSocket} itself if this connection does not use SSL.
*/
private Socket socket;//应用层的socket要么是基于rawSocket之上的SSLSocket,要么是rawSocket自己,表示没有用到SSL
private Handshake handshake;
private Protocol protocol;
private Http2Connection http2Connection;
private BufferedSource source;
private BufferedSink sink;//sink和source是让这个Connection可以以流的形式和服务器进行交互
public boolean noNewStreams;//如果为true,则不能在该连接上创建新的Stream
public int allocationLimit = 1;//分配流的数量上限,默认为1
}
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
}
while (true) {
try {
if (route.requiresTunnel()) {//需要建立隧道连接
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {//创建Socket
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);//建立协议
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
connect负责连接的建立过程,这里通过while循环来创建连接,保证连接能够创建成功,在循环中首先根据requiresTunnel判读是否创建隧道连接,它需要满足
public boolean requiresTunnel() {
return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
}
即对于设置了HTTP代理,且安全的连接。这时候需要请求HTTP代理建立一个到目标HTTP服务器的隧道连接,客户端和HTTP代理建立TCP连接。这里我们只关心不设置代理的情况,即创建连接通过connectSocket来完成。
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);//创建socket
rawSocket.setSoTimeout(readTimeout);
try {
//建立连接,这里我们关注AndroidPlatform下的connectSocket
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
//得到输入输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
这里我们看AndroidPlatform的connectSocket
@Override public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
try {
socket.connect(address, connectTimeout);//创建socket TCP连接
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} catch (SecurityException e) {
// Before android 4.3, socket.connect could throw a SecurityException
// if opening a socket resulted in an EACCES error.
IOException ioException = new IOException("Exception in connect");
ioException.initCause(e);
throw ioException;
}
}
connectSocket实际就是通过创建的rawSocket来建立TCP连接,连接建立成功后,需要进一步建立协议,它主要为数据的加密传输做一些初始化,比如TLS握手,HTTP/2的协议协商等。
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
if (route.address().sslSocketFactory() == null) {//http/1.1
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
return;
}
connectTls(connectionSpecSelector);//连接到TLS
if (protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.build();
http2Connection.start();
}
}
未配置sslScoketFactor就默认是HTTP/1.1,这时候socket和rawSocket是同一个,否则需要为安全请求建立TLS连接。
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 */);//基于rawSocket创建SSLSocket
// Configure the socket's ciphers, TLS versions, and extensions.
//配置SSLSocket
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
//配置扩展参数
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
// Force handshake. This can throw!
sslSocket.startHandshake();//开始握手
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());//获取握手信息
// Verify that the socket's certificates are acceptable for the target host.
//验证socket证书是否适用于目标主机
if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
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
//验证证书pinner
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);
}
}
}
TLS连接是对原始的TCP连接的一个封装,以提供TLS握手,及数据收发过程中的加密解密等功能。它通过SSLSocket来描述,建立一个TLS连接的步骤为:
- 配置SSLSocket
- 配置扩展参数
- 开始握手
- 获取握手信息
- 验证证书信息
- 创建用于执行IO的BufferedSource和BufferedSink等,并保存握手信息及所选择的协议
建立好连接后,在StreamAllocation的newStream中接下来就是创建HttpCodec返回,HttpCodec负责Request和Response的编解码。这里我们看看它的创建过程
public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {//对于HTTP/2.0
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
newCodec会根据HTTP的版本来创建Http2Codec或者Http1Codec对象。
###发起请求
建立好连接后,就需要发起http请求了,这个是在CallServerInterceptor这个拦截器中进行的,我们看看它的实现。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();//获取HttpCodec,它用来描述一个http流
StreamAllocation streamAllocation = realChain.streamAllocation();//获取StreamAllocation
RealConnection connection = (RealConnection) realChain.connection();//获取到RealConnection,它代表了到服务器的连接
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);//发起Http请求
Response.Builder responseBuilder = null;
//检测是否有请求Body
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();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} 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();
//创建负责请求的Builder
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
//得到响应Response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
可以看到整个Http请求和响应的过程是通过HttpCodec来进行操作的,首先通过writeRequestHeaders发起http请求,然后创建Response.Builder对象,负责接收响应。