一 概述
Okhttp 作为安卓主流的网络加载框架,当然了在JVM 和 GraalVM 都可以使用,Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp,Android 6.0 移除 HttpClient, 同时现在流行的Retrofit框架底层同样是使用OKHttp的。我们需要了解一下他的内部实现以及学习一下他的架构以及思想,
1.1 优点
- 支持Http1、Http2、Quic以及WebSocket
- 连接池复用底层TCP(Socket),减少请求延时(如果不是http2.0的时候)
- http1.1 支持同一个Tcp链接,可以请求多次,但是同一个Tcp链接,只能同时支持一次请求,其他的请求需要排队,
- http2.0 做了改进,一个Tcp连接可以同时响应多个请求
- 透明的支持GZIP减少数据流量
- 缓存响应数据减少重复的网络请求
- 请求失败自动重试主机的其他ip,自动重定向
- OkHttp支持现代TLS特性(TLS 1.3, ALPN,证书固定),也就是Https的ssl/tls
二 请求流程图
主要的类
- OkHttpClient 通信的客户端的一些参数配置,比如超时时间呀,添加拦截器呀
- Call 发起请求的接口,主要是通过 RealCall 来执行的
- Request 封装的请求的一下参数,比如 url ,get请求
- RequestBody 请求体
- Dispatcher 请求分发器,主要是把新的请求分发到 running异步队列、 ready异步队列 或者 runningSyncCalls(同步队列)
- Interceptor 拦截器,这个最重要了,真正的请求就是在这些拦截器中执行的。
- RetryAndFollowUpInterceptor:主要是重试 或者 重定向的判断
- BridgeInterceptor:主要加一些http请求的所需的header,比如 编码呀 host呀 Connection呀,是这些拦截器中最简单的一个了
- CacheInterceptor: 一些网络缓存的判断,这个里面贼复杂
- ConnectInterceptor:这个里面就是Socket链接(TCP链接)里面是一个 Socket 连接池,相当于一个线程池似的
- CallServerInterceptor 就是把前面的几个拼接起来,做真正的网络请求
- AsyncCall 执行任务的 Runable
- ConnectionPool 复用的Socket连接池
- Streams: 维护HTTP的流,用来对Requset/Response进行IO操作
- StreamAllocation相当于是个管理类,就是协调 请求Calls、连接Connections与数据流Streams三者之间的关系,它负责为一次请求寻找Socket连接对象,然后获得流来实现网络通信。
二 源码分析
先看一下异步请求
// 创建 OkHttpClient ,当然也可以使用Build的方式
OkHttpClient client = new OkHttpClient();
//OkHttpClient clien2 = new OkHttpClient.Builder()
// .connectTimeout(10_000, TimeUnit.MILLISECONDS)
// .readTimeout(10_000, TimeUnit.MILLISECONDS)
// .build();
Request request = new Request.Builder()
.url(url)
.get()
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
2.1 Call 以及 RealCall
public interface Call extends Cloneable {
// 执行同步请求
Response execute() throws IOException;
// 执行异步请求
void enqueue(Callback responseCallback);
// 取消请求
void cancel();
// 是否在执行
boolean isExecuted();
// 是否取消
boolean isCanceled();
}
2.2 RealCall
看一下 client.newCall(request) 返回一个 RealCall 的实例 ,RealCall 是实现了 Call 接口, RealCall 的 enqueue(Callback responseCallback),
final class RealCall implements Call {
// 我们自己创建的 OkHttpClient
final OkHttpClient client;
// 重试以及重定向的拦截器
final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
// RealCall 的构造方法
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//我们自己传的OkHttpClient
this.client = client;
// 请求
this.originalRequest = originalRequest;
//是不是 WebSocket,我们忽略吧,如果需要长连接可以看看
this.forWebSocket = forWebSocket;
//创建RetryAndFollowUpInterceptor拦截器
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
// 创建RealCall 对象
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
// 异步执行请求
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
// 如果这个 Call 正在执行 抛异常
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
// 交给分发器,看看放到哪个队列里面
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
}
2.3 Dispatcher 分发器
public final class Dispatcher {
//异步请求同时存在的最大请求
private int maxRequests = 64;
//异步请求同一个Host的最大个数
private int maxRequestsPerHost = 5;
// 闲置的任务
private @Nullable Runnable idleCallback;
// 线程池,用来执行异步任务的
private @Nullable ExecutorService executorService;
//异步请求等待执行的队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步请求正在执行的队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步请求正在执行的队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
// 设置 同一个host最大的个数
public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {}
// 设置异步请求最多的个数
public synchronized void setMaxRequests(int maxRequests) {}
// 异步执行任务
synchronized void enqueue(AsyncCall call) {
// 当异步任务队列小于64 并且 同一个host请求小于5 的时候 放入 runningAsyncCalls 里面,并且直接执行
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
// 相反 放到准备的队列里面
readyAsyncCalls.add(call);
}
}
// 取消所有的请求,无论同步还是正在执行的异步,还是在等待队列里面的
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
// 当异步任务执行完了之后调用,这里最后一个默认参数是true ,下面会用到
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
// 当同步任务执行完了之后调用,这里最后一个默认参数是false
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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!");
// 如果是异步任务,去看看是不是总的个数大于64,如果小于64,就循环遍历 readyAsyncCalls
// 如果 readyAsyncCalls 中的里面 请求的host小于5 ,就直接执行里面的任务
if (promoteCalls) promoteCalls();
// 计算正在运行的任务,异步runningAsyncCalls.size() + 同步runningSyncCalls.size();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
// 如果没有运行的任务,并且闲置Runnable !=null ,执行 里面的run,默认 idleCallback 是null
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
// 当有个异步执行完成的时候,会调用到这里,主要就是为了从 readyAsyncCalls 等待队列里面拿任务执行
private void promoteCalls() {
// 如果正在执行的大小 >= 64 的时候 不往下走
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
// 如果 readyAsyncCalls 是空的,不往下走
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
// 遍历 readyAsyncCalls 异步等待队列,如果同一个Host小于5的 就去执行
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
// 查看是不是同一个Host小于55
if (runningCallsForHost(call) < maxRequestsPerHost) {
// 从 readyAsyncCalls 中移除
i.remove();
// 放到 runningAsyncCalls
runningAsyncCalls.add(call);
// 开始执行
executorService().execute(call);
}
// 如果当循环加入到runningAsyncCalls中时,如果总的请求大于64 直接return,不遍历了
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
// 创建了一个高并发的线程池,就是来一个任务,直接执行,相当于 Executors.newCacheThreadPool()
// 当一个任务来领时,先看最大线程数,一看是0,直接方法队列,但是队列里面方不了,直接开启其他线程来执行,因为这里线程是 Integer.MAX_VALUE,走不到 拒绝策略里面
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(
0,// 最大核心线程数
Integer.MAX_VALUE, // 最大线程数
60, // 限制时间回收
TimeUnit.SECONDS,// 限制时间回收单位
new SynchronousQueue<Runnable>(), // 队列,此队列 代表 一个也放不下,直接开启线程或者是复用线程来做任务
Util.threadFactory("OkHttp Dispatcher", false));// 自定义的线程工厂
}
return executorService;
}
2.4 AsyncCall 是 线程池执行的 Runable
AsyncCall 是 RealCall里面的一个内部类
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
// 这个就是Runable中的Run方法,在 NamedRunnable 中会调用
@Override protected void execute() {
boolean signalledCallback = false;
try {
// 拿到所有的拦截器,去真正的请求网络
Response response = getResponseWithInterceptorChain();
// 若果 RetryAndFollowUpInterceptor 是取消的状态
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
// 返回失败
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
// 返回请求回来的结果
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
// 上面有异常 返回失败
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// 最终调用到 Dispatcher 分发器的 完成方法
client.dispatcher().finished(this);
}
}
}
// 去调用拦截器 去执行网络
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
// 添加自己在OkHttpClient 中拦截器
interceptors.addAll(client.interceptors());
// 添加重试以及重定向的拦截器
interceptors.add(retryAndFollowUpInterceptor);
// 添加关于请求头,http协议的请求的一些参数添加
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
// Socket 连接池的拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 如果不是websocket ,就在添加自定义的 networkInterceptors()
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);
}
三 拦截器怎样去执行网络请求的
拦截器是一个典型的责任链模式。先添加的拦截器,最先拿到 Request,最后拿到 Response的
3.1 RetryAndFollowUpInterceptor (重试或者重定向的拦截器)
主要负责重定向或者重试的拦截器,是整个责任链中的第一个,第一个拿到请求的,最后一个拿到结果Response的,主要是请求返回回来的处理。
3.1.1 如果是以下就不用重试:
- 如果我们配置了OkHttpClient 不允许重试,
- 协议异常:根本不符合 Http 请求协议,重试也没用,所以此时不重试
- 超时异常: 可能由于网络波动造成了Socket连接的超时,可以使用不同路线重试
- SSL握手未授权异常 都不会去重试, 证书异常 证书都有问题了,当然不用重试了
3.1.2 以下就重试
- 路由异常,连接未成功,请求还没发出去
- 请求发出去了,但是和服务器通信失败了
- 其次就是当服务器返回重定向的code码是,此时去重定向请求,最多重定向20次
public final class RetryAndFollowUpInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
// 拿到请求的参数以及请求方法
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 拿到 RealCall的对象
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//构建一个StreamAllocation对象,StreamAllocation相当于是个管理类,维护了
//Connections、Streams和Calls之间的管理,该类初始化一个Socket连接对象,获取输入/输出流对象
// 真正的使用地方是在 ConnectInterceptor
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 {
// 继续往下一个拦截器请求BridgeInterceptor
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// 路由异常,连接未成功,请求还没发出去,此时需要重试
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// 请求发出去了,但是和服务器通信失败了。(socket流正在读写数据的时候断开连接),此时也重试
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// 有异常 ,把这次的请求给资源释放掉
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 = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
// 最多重定向20次
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;
//把重定向的请求赋值给request,以便再次进入循环执行
priorResponse = response;
}
}
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// 当我们配置okhttpclient的时候禁止重试的时候,就不用去重试了
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 判断是不是属于重试的异常
if (!isRecoverable(e, requestSendStarted)) return false;
// 有没有可以用来连接的路由路线
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
}
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 协议异常,不重试
if (e instanceof ProtocolException) {
return false;
}
// 不是超时 不重试
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted;
}
// 证书问题不重试
if (e instanceof SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// ssl 握手问题不重试
if (e instanceof SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
return false;
}
// 是否满足重定向需求
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 客户端使用了HTTP代理服务器,在请求头中添加 “Proxy-Authorization”,让代理服务器授权
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);
// 需要在请求头中加入.header("Authorization", credential),和 token cookie 差不多,用来验证神风的
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
// 308 永久重定向
// 307 临时重定向
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// 如果请求方式不是GET或者HEAD,不会自动重定向请求
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// 300 301 302 303
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// 如果咱们在OkhttpClient中 不允许重定向,那就返回null
if (!client.followRedirects()) return null;
// 取出重定向的 地址
String location = userResponse.header("Location");
if (location == null) return null;
// 解析地址,重新拼接成HttpUrl
HttpUrl url = userResponse.request().url().resolve(location);
// 如果为null,说明协议有问题,取不出来HttpUrl,那就返回null,不进行重定向
if (url == null) return null;
// 如果重定向在http到https之间切换,需要检查用户是不是允许(默认允许)
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// 大多数重定向的请求不能含有 RequestBody,只有 PROPFIND 请求才能有请求体
Request.Builder requestBuilder = userResponse.request().newBuilder();
//不是get与head 的请求
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
// 只要不是 PROPFIND 请求,就改成get请求
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
// 只有 PROPFIND 才能有请求体
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
// 不是 PROPFIND 请求,就移除下面的东西
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
//当跨主机重定向时,删除所有认证头
//
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
// 408 请求超时 408 是非常少见的
case HTTP_CLIENT_TIMEOUT:
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
//如果是本身这次的响应就是重新请求的产物同时上一次之所以重请求还是因为408,那我们这次不再重请求了
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;
}
}
3.2 BridgeInterceptor(桥接拦截器)
这个是几个拦截器中最简单的一个,添加一些服务端需要的header信息 以及 一些配置信息,一个http请求信息包括(请求行、请求头、请求体),比如设置请求内容长度,编码,gzip压缩,cookie,保持长连接 keep-alive 等等。
- gzip压缩:返回response 进行压缩,减少流量
- keep-alive:代表一个请求完成了,不关闭 TCP 链接,下次请求还可以复用这个TCP,
@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) {
// 添加 Content-Type
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
//添加 Content-Length
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
// 添加Host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 默认开启 Keep-Alive
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) {
// 使用 gzip压缩
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 设置 User-Agent
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 给下一个拦截器CacheInterceptor
Response networkResponse = chain.proceed(requestBuilder.build());
//解析服务器返回的Header,如果没有这事cookie,则不进行解析
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//判断服务器是否支持gzip压缩,如果支持,则将压缩提交给Okio库来处理
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)));
}
return responseBuilder.build();
}
3.3 CacheInterceptor(缓存拦截器)
一般情况下都是get缓存,因为get请求不会对服务器有啥影响,POST 请求可能会导致新的资源的建立和/或已有资源的修改。当缓存满足条件的时候,就不会往下一个拦截器走了,直接返回给 BridgeInterceptor 了 。
networkRequest | cacheResponse | 说明 |
---|---|---|
Null | Null | okhttp直接返回504 |
Not Null | Null | 向服务器发送请求 |
Null | Not Null | 直接使用缓存 |
Not Null | Not Null | 发起请求,若得到响应为304(服务端无修改),则更新缓存(这要更新缓存的新鲜度)响应并返回 |
public final class CacheInterceptor implements Interceptor {
final @Nullable InternalCache cache;
public CacheInterceptor(@Nullable InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 如果配置了缓存,则通过request获取缓存,默认cache为空
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 根据 CacheStrategy 拿到 networkRequest 和 cacheResponse
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.
}
// 如果是指 networkRequest 为null cacheResponse也为null 直接返回504
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 (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 前面都没返回,走下一个拦截器ConnectInterceptor
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());
}
}
// 如果有缓存,且http返回码是304,证明资源没有变动,直接更新缓存的新鲜度,使用缓存
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;
}
}
3.4 ConnectInterceptor (连接拦截器)
这里面封装这Socket建立TCP连接的连接池,这里我们看到有个 StreamAllocation 对象,这个是在 RetryAndFollowUpInterceptor 里面创建的,当时没有用,在这个拦截器中才会使用
- HttpCodec 中包含了 输入输出流,并且封装了对HTTP请求报文的编码与解码,直接使用它就能够与请求主机完成HTTP通信。
- RealConnection 封装了Socket与一个Socket连接池
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();
// 拿到 RetryAndFollowUpInterceptor 时创建的 StreamAllocation
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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
// 创建连接
RealConnection connection = streamAllocation.connection();
// 给下一个拦截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
3.5 CallServerInterceptor (请求服务拦截器)
完成HTTP协议报文的封装与解析,利用 HttpCodec 发出请求到服务器并且解析生成 Response,
public final class CallServerInterceptor implements Interceptor {
private final boolean forWebSocket;
public CallServerInterceptor(boolean forWebSocket) {
this.forWebSocket = forWebSocket;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 拿到上一步的 HttpCodec
HttpCodec httpCodec = realChain.httpStream();
// 拿到 StreamAllocation
StreamAllocation streamAllocation = realChain.streamAllocation();
// 拿到上一步的RealConnection
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 ("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()) {
//HTTP2多路复用,不需要关闭socket,不管!
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();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
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;
}
static final class CountingSink extends ForwardingSink {
long successfulCount;
CountingSink(Sink delegate) {
super(delegate);
}
@Override public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
successfulCount += byteCount;
}
}
}