okhttp 其实就做了 3 个操作,分别是 请求(call) ,TCP 连接(Connection) ,数据流(okio),这 3 个操作都是通过 okhttp 的拦截器来完成的
okhttp 的拦截器到底是有些,我们可以在 opkhttp3/RealCall 里面的一个方法看到:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加开发者设置的拦截器,也就自定义的拦截器
interceptors.addAll(client.interceptors());
//添加失败重试的拦截器
interceptors.add(retryAndFollowUpInterceptor);
/**
* BridgeInterceptor 网络桥接拦截器
* 这里处理http的报文, Content-Length 的计算和添加、gzip的支持
* 等都是在这里面处理的
*/
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//添加缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//添加连接拦截器,在这里建立TCP或SSL连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加webSock长连接的拦截器
interceptors.addAll(client.networkInterceptors());
}
/**
* CallServerInterceptor 请求服务器响应拦截器,负责网络连接
* 它负责实质的网络请求与响应的 I/O 操作,即往 Socket 里写入请求数据
* 和从 Socket 里读取响应数据
*/
interceptors.add(new CallServerInterceptor(forWebSocket));
//RealInterceptorChain配置全部的拦截器以及拦截器的调度顺序
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener
, client.connectTimeoutMillis(),client.readTimeoutMillis()
, client.writeTimeoutMillis());
//启动拦截器去进行网络请求与响应
return chain.proceed(originalRequest);
}
这个方法就是把所有配置好的 Interceptor 放在一个 List 里,然后作为参数,创建一个 RealInterceptorChain 对象,它负责全部的拦截器的调度,它是通过调用 chain.proceed(request) 方法来切换到另外一个拦截器,这种叫做责任链模式
那么,一个完整的 http 请求过程,这些拦截器的调用如下图:
首先,先从发起一个 http 请求开始,从上到下,每级 Interceptor 做的事:
- client.interceptors():这里是开发者设置的自定义 Interceptor
- 在发起 http 请求报文时,在其他 Interceptor 处理之前,进行最早的预处理工作,比方说可以在这里添加统一的 header;
- 在接收 http 响应报文时,这里则是最后的善后操作了,比方将服务器返回的响应报文的内容全部打印出来
- RetryAndFollowUpInterceptor:该拦截器负责在请求失败时的重试,以及重定向的自动后续请求,也就是连接到新的 IP 地址;
- BridgeInterceptor:该拦截器负责把用户构造的 http 请求转换为 http 请求报文以及得到服务器返回的数据包转为 http 响应报文,例如 Content-Length 的计算和添加,gzip 的支持和 gzip 数据包的解压都是在这里处理的
- CacheInterceptor :该拦截器负责 Cache 的处理,通过 Expires,ETag,Last-Modified 等字段判断本地是否有了可用的 Cache,如果有那么就直接忽然接下来的网络交互,直接返回 Cache 中的数据,如果没有,那么继续执行网络请求并在得到 http 响应报文时更新本地的 Cache 数据
- ConnectInterceptor:该拦截器负责建立起 TCP 连接 或 SSL 连接,这里的代码其实非常少:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(@NonNull 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 http的编码解码器
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//RealConnection,和服务器搭建起的连接对象
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
上面代码的核心就是 HttpCodec 对象,它是一个 http 协议的接口,子类分别为 Http1Codec 和 Http2Codec ,它们的代码都很长,目前只要知道它们都是通过 okio 来拼接出 http 请求报文发给服务器以及解析服务器返回的 http 响应报文,也就是对 Socket 的读写操作进行封装(关于 okio 我会在晚些的分享中进行分析)
- CallServerInterceptor:该拦截器负责实质的请求与响应的 I/O 操作,也就是最后一个调用或最开始调用的拦截器了,简单看下它的代码:
Override
public Response intercept(@NonNull 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());
//向服务器发送request请求
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();
//发送request body
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();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
.........
return response;
大致还是可以看出上面到底干了什么,那就是向服务器发送请求报文和解析服务器返回的响应报文,同时也可以看到核心工作都是由 HttpCodec 完成,而 HttpCodec 实际上是 okio 的封装利用,也就是说 okhttp 其实就是对 okio 的进一步封装,让其具备网络请求的能力。
内容参考:https://blog.piasy.com/2016/07/11/Understand-OkHttp/