一、OKHTTP的基本使用
OkHttpClient client = new OkHttpClient();
void get(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
// 执行同步请求
Call call = client.newCall(request);
Response response = call.execute();
// 获得响应
ResponseBody responseBody = response.body();
System.out.println(responseBody.toString());
}
// 其中post请求比get请求多了一个请求体的配置
OKHttp的设计采用的是门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端
OkHttpClient统一暴露出来:OkHttpClient 中全是一些配置,比如代理的配置、ssl证书的配置等。
OKHttp发一次请求的主要角色有:OKHttpClient、Request、Call,其中OKHttpClient可以通过OkHttpClient.Builder()设置相关属性,Reques也一样可通过Request.Builder()设置相关属性(建造者模式);
Call 则是把 Request 交给 OkHttpClient 之后返回的一个已准备好执行的请求, Call 本身是一个接口,我们获得的实现为: RealCall
OkHttpClient.java
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall.java
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.transmitter = new Transmitter(client, call);
return call;
}
Call 的 execute 代表了同步请求,而 enqueue 则代表异步请求。两者唯一区别在于一个会直接发起网络请求,而另一个使用OkHttp内置的线程池来进行。这就涉及到OkHttp的任务分发器。
二、OKHTTP的分发器Dispatcher
分发器的作用是内部维护队列与线程池,完成请求调配。在这之前先看下Dispatcher.java源码,首先要知道类中定义的变量的含义:
private int maxRequests = 64; //异步请求同时存在的最大请求,参考了个浏览器的值得出的
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<>(); //同步请求正在执行队列
同步请求
RealCall的execute方法
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
// 调用分发器
client.dispatcher().executed(this);
// 执行请求
return getResponseWithInterceptorChain();
} finally {
// 请求完成
client.dispatcher().finished(this);
}
}
Dispatcher.java
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
以上为同步请求的所有代码,很简单,因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录。
异步请求(基于okhttp:3.12.3版本)
RealCall.java
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
如果该 RealCall 已经执行过了,再次执行是不允许的。异步请求会把一个 AsyncCall 提交给分发器。
AsyncCall 实际上是一个 Runnable 的子类,使用线程启动一个 Runnable 时会执行 run 方法,在 AsyncCall 中被重定向到 execute 方法;同时 AsyncCall 也是 RealCall 的普通内部类,这意味着它是持有外部类 RealCall 的引用,可以获得直接调用外部类的方法。
Dispatcher.java
void enqueue(AsyncCall call) {
synchronized (this) {
// 第一步:将请求添加到ready队列
readyAsyncCalls.add(call);
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
// 此方法返回的值用于结束时判断是否有正在运行的请求
promoteAndExecute();
}
如何决定将请求放入ready还是running?这个逻辑和版本3.10的有点区别
版本 | ready队列和running队列处理逻辑 |
---|---|
3.10 | 若满足总任务数不超过64,同一服务器连接任务数不超过5则将请求放入running队列,否则放置ready队列 |
3.12 | 首先将所有的请求任务放置到ready队列中,后续再判断处理添加到running队列中——判断条件和3.10版本一样 |
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
// 第二步:遍历ready队列 将满足条件的添加到running队列
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
// break是跳出当前循环:若正在运行的请求任务超过64则结束
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
// 同一host请求同时只能有5个,若大于5个则继续迭代处理下一个循环
// continue是停止执行后面的语句,进入下一次迭代
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
// 将ready队列中的当前请求任务移除并添加到可运行队列中
i.remove();
// 此处自加1用于计算同一Host请求不能超过5个
asyncCall.callsPerHost().incrementAndGet();
// 若以上条件都满足,将当前请求任务调价到可执行队列和正在运行队列
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
// 此处计算的是正在运行的异步请求和同步请求数量的总和
isRunning = runningCallsCount() > 0;
}
// 第三步:创建线程池执行可运行的请求任务
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
// 重点
asyncCall.executeOn(executorService());
}
return isRunning;
}
Dispatcher中创建线程池用于执行请求,加入线程池直接执行
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, //核心线程
Integer.MAX_VALUE, //最大线程
60, //空闲线程闲置时间
TimeUnit.SECONDS, //闲置时间单位
new SynchronousQueue<>(), //线程等待队列
Util.threadFactory("OkHttp Dispatcher", false)); //线程创建工厂
}
return executorService;
}
-
核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。
-
最大线程 Integer.MAX_VALUE 与等待队列 SynchronousQueue
的组合能够得到最大的吞吐量,高并发。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!其中,等待队列 BlockingQueue 有: ArrayBlockingQueue 、 LinkedBlockingQueue 与 SynchronousQueue,其区别如下:
BlockingQueue类型 | 区别 |
---|---|
ArrayBlockingQueue | 基于数组的阻塞队列,初始化需要指定固定大小:创建时需给队列容量(比如1),多个任务处理时(任务1中设置while-true),两个任务时只执行任务1(需等任务执行完才能执行任务2);三个任务执行时,执行顺序是1-3-2,其中任务3和任务2是同一个线程处理 |
LinkedBlockingQueue | 基于链表实现的阻塞队列,容量可传可不传:传容量和ArrayBlockingQueue的处理一样,不传的话用的是默认容量( Integer.MAX_VALUE),能一直添加任务,但后续队列遇到while-true的情况只能等待 |
SynchronousQueue | 无容量的队列:不会发生阻塞情况,都会立即执行,所以是高并发的并发吞吐量,比新起线程的好处是线程执行完上一任务后能复用 |
补充:线程池处理流程
RealCall内部类AsyncCall(实际上是Runnable),重写的run方法执行的是execute
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 执行成功
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
// 真正执行请求返回数据的方法
Response response = getResponseWithInterceptorChain();
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 {
responseCallback.onFailure(RealCall.this, e);
}
} catch (Throwable t) {
cancel();
if (!signalledCallback) {
IOException canceledException = new IOException("canceled due to " + t);
canceledException.addSuppressed(t);
responseCallback.onFailure(RealCall.this, canceledException);
}
throw t;
} finally {
// 执行完一个请求后调finish方法
client.dispatcher().finished(this);
}
}
其中发起网络请求的核心代码是,包含拦截器的处理,最终得到的Response响应,不管请求是成功还是失败finally中的代码都会执行
如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完一个请求后,都会调用分发器的 finished 方法
// 同步请求调用
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
// 异步请求调用
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
//不管异步还是同步,执行完后都要从队列移除(runningSyncCalls/runningAsyncCalls)
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
// 和初始化ready队列和running队列时调用的方法处理一样,拿到此值用于结束时判断是否有正在运行的请求
boolean isRunning = promoteAndExecute();
// 没有任务执行执行闲置任务
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
ready任务什么时候执行?从以上finish代码来看,每次有任务结束后都会重新分配
总结分发器
1、如何决定将请求放入ready还是running,从ready移到running的条件是什么?
——首先将请求全部加入ready队列
——总任务数不超过64,同一服务器连接任务数不超过5则将ready队列请求移除并放入running队列
2、分发器线程池的行为?
——无等待,最大并发
三、OKHTTP的拦截器
上面分发器代码中知道getResponseWithInterceptorChain() 是真正执行请求返回数据的方法,五大默认拦截器完成整个请求过程:单一职责原则
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加自定义拦截器
interceptors.addAll(client.interceptors());
// 添加重试及重定向拦截器
interceptors.add(new RetryAndFollowUpInterceptor(client));
// 添加桥接连接器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 添加缓存连接器
interceptors.add(new CacheInterceptor(client.internalCache()));
// 添加连接拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 添加自定义network拦截器
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
概括以上五大拦截器的功能如下
拦截器 | 功能概括 |
---|---|
RetryAndFollowUpInterceptor | 第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求 |
BridgeInterceptor | 补全请求,并对响应进行额外处理 |
CacheInterceptor | 请求前查询缓存,获得响应并判断是否需要缓存 |
ConnectInterceptor | 与服务器完成TCP连接 |
CallServerInterceptor | 与服务器通信;封装请求数据与解析响应数据(如:HTTP报文) |
以上代码中addInterceptor 和 addNetworkIntercepter的区别(webSocket与HTTP同级协议)
——前者一定执行,后者不一定执行
——前者第一个拿到Request,最后一个拿到response,后者相反
责任链模式
为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的 情况下动态的重新组织链和分配责任。处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。
重试及重定向拦截器:默认允许重试,请求交到下一个拦截器之前未做任何处理
while-true—continue: 满足重试条件则继续循环(RouterException/IOException)
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
// 将请求交给下一个拦截
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 异常判断1——四个判断条件++
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
// 异常判断2
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 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();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
// 请求成功后对得到的响应做处理++
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
// 最大重试次数不超过20
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
两个异常都是根据 recover 方法判断是否能够进行重试,如果返回 true ,则表示允许重试。
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
//失败后是否允许重试,初始化client时可设置,默认允许
if (!client.retryOnConnectionFailure()) return false;
// 判断是否有对某次请求不能重试——post请求需特殊处理,请求体需实现?
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
//判断是不是属于重试的异常:协议异常、网络异常、证书异常的情况不再重试
if (!isRecoverable(e, requestSendStarted)) return false;
// 不存在更多路由则不重试
if (!transmitter.canRetry()) return false;
return true;
}
所以首先使用者在不禁止重试的前提下,如果出现了某些异常,并且存在更多的路由线路,则会尝试换条线路进行请求的重试。其中某些异常是在 isRecoverable 中进行判断:
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 出现协议异常,不能重试
if (e instanceof ProtocolException) {
return false;
}
// 如果不是超时异常,不能重试
if (e instanceof InterruptedIOException) {
return e instanceof SocketTimeoutException && !requestSendStarted;
}
// SSL握手异常中,证书出现问题,不能重试
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
// SSL握手未授权异常 不能重试
if (e instanceof SSLPeerUnverifiedException) {
return false;
}
return true;
}
- 1、协议异常,如果是那么直接判定不能重试;(你的请求或者服务器的响应本身就存在问题,没有按照http协议来定义数据,再重试也没用)
- 2、超时异常,可能由于网络波动造成了Socket连接的超时,可以使用不同路线重试。
- 3、SSL证书异常/SSL验证失败异常,前者是证书验证失败,后者可能就是压根就没证书,或者证书数据不正确,那还怎么重试?
- 4、经过了异常的判定之后,如果仍然允许进行重试,就会再检查当前有没有可用路由路线来进行连接。简单来说,比如 DNS
对域名解析后可能会返回多个 IP,在一个IP失败后,尝试另一个IP进行重试。
上面都是是否可以重试的判断处理,如果请求结束后没有发生异常并不代表当前获得的响应就是最终需要交给用户的,还需要进一步来判断是否需要重定向的判断。重定向的判断位于 followUpRequest 方法
private Request followUpRequest(Response userResponse, @Nullable 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);
// 401 需要身份验证 有些服务器接口需要验证使用者身份 在请求头中添加 “Authorization
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:
// 如果用户不允许重定向,那就返回null
if (!client.followRedirects()) return null;
// 根据location 配置新的请求 url
String location = userResponse.header("Location");
// 如果为null,说明协议有问题,取不出来HttpUrl,那就返回null,不进行重定向
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// 如果重定向在http到https之间切换,需要检查用户是不是允许(默认允许)
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
/*** 重定向请求中 只要不是 PROPFIND 请求,无论是POST还是其他的方法都要改为GET请求方式, * 即只有 PROPFIND 请求才能有请求体 */
//请求不是get与head
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
// 除了 PROPFIND 请求之外都改成GET请求
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
// 不是 PROPFIND 的请求,把请求头中关于请求体的数据删掉
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// 在跨主机重定向时,删除身份验证请求头
if (!sameConnection(userResponse.request().url(), url)) {
requestBuilder.removeHeader("Authorization");
}
return requestBuilder.url(url).build();
// 408 客户端请求超时
case HTTP_CLIENT_TIMEOUT:
// 408 算是连接失败了,所以判断用户是不是允许重试
if (!client.retryOnConnectionFailure()) {
return null;
}
RequestBody requestBody = userResponse.request().body();
if (requestBody != null && requestBody.isOneShot()) {
return null;
}
// 如果是本身这次的响应就是重新请求的产物同时上一次之所以重请求还是因为408,那我们这次不再重请求
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
return null;
}
// 如果服务器告诉我们了 Retry-After 多久后重试,那框架不管了。
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
// 503 服务不可用 和408差不多,但是只在服务器告诉你 Retry-After:0(意思就是立即重试) 才重请求
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
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;
}
}
总结
本拦截器是整个责任链中的第一个,这意味着它会是首次接触到 Request 与最后接收到 Response 的角色,在这个拦截器中主要功能就是判断是否需要重试与重定向。
- 重试的前提是出现了 RouteException 或者 IOException 。一但在后续的拦截器执行过程中出现这两个异常,就会通过 recover 方法进行判断是否进行连接重试。
- 重定向发生在重试的判定之后,如果不满足重试的条件,还需要进一步调用 followUpRequest 根据 Response 的响应码(当然,如果直接请求失败, Response 都不存在就会抛出异常)。
- followup 最大发生20次。
桥接拦截器:处理请求和响应(五大拦截器中最简单的)
连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,比如设置请求内容长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作
@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());
}
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");
}
}
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");
}
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());
}
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)));
}
return responseBuilder.build();
}
补全了请求头后交给下一个拦截器处理,得到响应后,主要做两件事:
- 1、读取Set-Cookie响应头并调用接口告知用户,在下次请求则会读取对应的数据设置进入请求头, 默认CookieJar无实现;
- 2、响应头Content-Encoding为gzip,使用GzipSource包装解析。
总结
对用户构建的 Request 进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的 Request 将符合网络请求规范的Request交给下一个拦截器处理,并获取 Response 如果响应体经过了GZIP压缩,那就需要解压,再构建成用户可用的 Response 并返回
缓存拦截器:缓存策略的重点主要在CacheStrategy中
@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());
}
}
// 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、创建 CacheStrategy ,创建时会判断是否能够使用缓存
3、交给下一个责任链继续处理
4、后续工作,返回304则用缓存的响应;否则使用网络响应并缓存本次响应(只缓存Get请求的响应)
以上步骤第二步中的CacheStrategy中的逻辑处理要理清楚:networkRequest和cacheResponse
networkRequest | cacheResponce | 处理结果 |
---|---|---|
null | null | 报504 |
null | not null | 用缓存 |
not null | null | 发请求 |
not null | not null | 发请求,若得到304(无修改)则更新缓存 |
综上结果,可概述为networkRequest存在则优先发起网络请求,否则用cacheResponse缓存
未完待续~~~
连接拦截器:打开与目标服务器的连接,并执行下一个拦截器
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
未完待续~~~
请求服务器拦截器:利用 HttpCodec 发出请求到服务器并且解析生成 Response
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.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.
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.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
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(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(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
未完待续~~