流程及源码分析
- OkHttpClient使用过程主要涉及到OkHttpClient、Request、Response、Call、Interceptor,具体参考OkHttp使用。
- OkHttp在设计时采用门面模式,将整个系统复杂性隐藏,子系统通过OkHttpClient客户端对外提供。
流程
创建 OkHttpClient
OkHttpClient主要是一些配置设置,如代理配置、ssl证书配置等,
- 默认构造函数
OkHttpClient okHttpClient = new OkHttpClient();
- 通过Builder
OkHttpClient client = okHttpClient.newBuilder().build();
创建Call
Call 接口
- Call 是OkHttp中表示HTTP请求的核心接口,定义了请求执行的基本操作。
- 它封装了请求的发送和响应的接收,并提供了取消请求、同步/异步执行等功能
核心方法:
方法 | 说明 |
---|---|
Request request() | 返回当前请求的 Request 对象,包含URL、方法、头部等信息 |
Response execute() throws IOException | 同步执行请求,阻塞直到收到响应或发生异常 |
void enqueue(Callback responseCallback) | 异步执行请求,通过回调 Callback 返回响应或错误 |
void cancel() | 取消请求。如果请求已经完成,则无效果 |
boolean isExecuted() | 判断请求是否已经执行 |
boolean isCanceled() | 判断请求是否被取消 |
Timeout timeout() | 返回请求的超时配置。 |
创建
通过OkHttpClient的newCall方法进行创建。
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
特点:
- 不可重用:每个 Call 实例只能执行一次,重复调用 execute() 或 enqueue() 会抛出异常。
- 线程安全:Call 可以在多线程环境中安全使用。
RealCall
RealCall 是 Call 接口的唯一实现类,负责实际执行网络请求。
核心属性
属性 | 说明 |
---|---|
OkHttpClient client | 关联的 OkHttpClient 实例,用于配置请求的执行环境(如连接池、拦截器等) |
Request originalRequest | 原始的请求对象 |
boolean executed | 标记请求是否已经执行 |
boolean canceled | 标记请求是否被取消 |
EventListener eventListener | 用于监听请求生命周期事件(如连接建立、请求发送、响应接收等) |
核心方法
方法 | 说明 |
---|---|
execute() | 同步执行请求 |
enqueue(Callback responseCallback) | 异步请求 |
cancel() | 取消 |
getResponseWithInterceptorChain() | 构建拦截器链 |
分发器
- Dispatcher 是OkHttp的调度器,负责管理同步和异步请求的执行,内部还有一个线程池。
- 在构建Client的时候会创建,也可以自己创建分发器。
核心属性
属性 | 说明 |
---|---|
int maxRequests = 64 | 异步请求时同时存在的最大请求 |
int maxRequestsPerHost = 5 | 异步请求同一域名同时存在的最大请求 |
Runnable idleCallback | 闲置任务,由使用者使用 |
ExecutorService executorService | 线程池,用于执行异步请求 |
Deque readyAsyncCalls | 等待执行的异步请求队列 |
Deque runningAsyncCalls | 正在执行的异步请求队列 |
Deque runningSyncCalls | 正在执行的同步请求队列 |
核心方法
方法 | 说明 |
---|---|
enqueue(AsyncCall call) | 将异步请求加入队列,并根据最大并发数决定是否立即执行 |
executed(RealCall call) | 标记同步请求为正在执行 |
finished(AsyncCall call) | 异步请求完成后调用,从运行队列中移除,并尝试执行下一个等待的请求 |
finished(RealCall call) | 同步请求完成后调用,从运行队列中移除 |
Call执行
executed
- 同步检查请求是否已执行。
- 捕获当前请求的调用栈信息,便于调试和问题排查。
- 触发请求开始事件。
- 将请求加入同步执行队列。
- 发起请求并获取响应。
- 捕获异常并触发失败事件。
- 请求完成后的清理工作。
//RealCall.java
/*
* 它会阻塞当前线程,直到请求完成或发生异常
*/
@Override
public Response execute() throws IOException {
synchronized (this) {
//不能重复执行
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//通知事件监听器,请求已经开始
eventListener.callStart(this);
try {
//分发器执行
client.dispatcher().executed(this);
// 执行请求
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//请求完成
client.dispatcher().finished(this);
}
enqueue
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
//不能重复执行
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//分发器执行
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
分发器执行
executed
//Dispatcher.java
/**
* 添加到正在同步执行队列中
*/
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
enqueue
synchronized void enqueue(AsyncCall call) {
// 当正在执行的任务未超过最大限制64且同一Host的请求不超过5个
// 添加到正在执行队列
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call); //添加到正在执行队列
executorService().execute(call); //提交给线程池
} else {
//加入等待队列,需要等待有空闲名额才开始执行
readyAsyncCalls.add(call);
}
}
异步执行AsyncCall
- AsyncCall 是 RealCall 的内部类,用于封装异步请求的执行逻辑,提交给分发器。
- Asynccall 实际上是一个 Runnable 的子类,使用线程启动一个 Runnabll 时会执行 run 方法,在 Asynccall 中被重定向到 execute 方法
核心属性
属性 | 说明 |
---|---|
Callback responseCallback | 用户提供的回调接口,用于返回响应或错误 |
核心方法
方法 | 说明 |
---|---|
execute() | 调用 getResponseWithInterceptorChain() 获取响应。通过 Callback 返回响应或异常无论成功或失败,最终都会通知 Dispatcher 请求完成。 |
run() | 实现 Runnable 接口,作为任务提交到线程池执行 |
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;
}
@Override
protected void execute() {
boolean signalledCallback = false;
try {
//执行请求
Response response = getResponseWithInterceptorChain();
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 {
client.dispatcher().finished(this);
}
}
}
分发器的线程池
- 分发器中的线程池,其实就和 Executors.newcachedThreadPool()创建的线程一样。
- 首先核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。
- 最大线程 Integer.MAX_VALUE 与等待队列 synchronousQueue 的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。
线程池具体可以参考线程池详解
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;
}
执行请求
- OkHttp实际请求主要是在getResponseWithInterceptorChain(),采用的是责任链模式,关于这个模式详情可参考责任链。
- 负责构建拦截器链并处理HTTP请求。拦截器链是OkHttp的核心机制,通过一系列拦截器对请求和响应进行处理
- 自定义拦截器:处理全局逻辑(如日志、认证)。
- 重试与重定向拦截器:处理请求重试和重定向。
- 桥接拦截器:补充请求头、处理 Cookie。
- 缓存拦截器:检查缓存,决定是否发起网络请求。
- 连接拦截器:建立网络连接。
- 网络拦截器:处理网络层的请求和响应。
- 请求拦截器:发送请求并接收响应。
Response getResponseWithInterceptorChain() throws IOException {
//初始化一个空的拦截器列表,用于存储所有拦截器
List<Interceptor> interceptors = new ArrayList<>();
//添加自定义拦截器,将用户通过 OkHttpClient 添加的自定义拦截器加入列表
//这些拦截器会最先执行
interceptors.addAll(client.interceptors());
//添加重试与重定向拦截器
interceptors.add(retryAndFollowUpInterceptor);
//添加桥接拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//添加缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//添加连接拦截器
interceptors.add(new ConnectInterceptor(client));
//如果不是WebSocket请求,添加用户通过 OkHttpClient 添加的网络拦截器
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//添加请求拦截器
interceptors.add(new CallServerInterceptor(forWebSocket));
//创建拦截器链
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, //拦截器列表
null, null, null,
0,//当前拦截器的索引(初始为0)
originalRequest,//原始请求
this, //当前的 Call 实例
eventListener, //事件监听器
client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());//超时配置
//启动拦截器链,依次执行每个拦截器
//RealInterceptorChain.proceed() 方法会调用当前拦截器的 intercept() 方法
//每个拦截器在执行完自己的逻辑后,会调用 chain.proceed() 将请求传递给下一个拦截器
//最后一个拦截器(CallServerInterceptor)会直接与服务器通信,获取响应并返回
return chain.proceed(originalRequest);
}
ResponseWithInterceptorChain执行流程
在ResponseWithInterceptorChain()会将请求交给责任链中的一个一个拦截器。默认有五类拦截器
,也用户自定义拦截器(通过 OkHttpClient 添加)。
- 重试与重定向拦截器(RetryAndFollowUpInterceptor)
- 桥接拦截器(BridgeInterceptor),处理请求头和响应头
- 缓存拦截器(CacheInterceptor),管理缓存
- 连接拦截器(ConnectInterceptor),建立网络连接
- 网络拦截器(NetworkInterceptor),处理网络层逻辑
- 请求拦截器(CallServerInterceptor),发送请求并接收响应
拦截器
RetryAndFollowUpInterceptor
- 主要完成重试与重定向。
- 请求阶段发生RouteException 和IOException 异常会判断是否重试
//RetryAndFollowUpInterceptor.java
public Response intercept(Chain chain) throws IOException {
...
try {
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流正在读写数据的时候断开连接)
// HTTP2才会抛出connectionshutdownException。所以对于HTTP1 requestsendstarted-定是true
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
- 通过recover判断是否需要重试
/*
* @param e 请求失败时抛出的异常
* @param streamAllocation 负责管理网络连接和流的对象
* @param requestSendStarted 标记请求是否已经开始发送
* @param userRequest 用户原始的请求对象
*/
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
//通知 StreamAllocation 当前流失败,释放相关资源
streamAllocation.streamFailed(e);
//在配置okhttpclient是设置了不允许重试(默认允许),则一旦发生请求失败就不再重试
if (!client.retryOnConnectionFailure()) return false;
//检查请求体是否可重复发送
// requestSendStarted 为 true 表示请求已经开始发送
// UnrepeatableRequestBody 表示请求体只能发送一次(例如流式请求体)
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody)
return false;
//检查异常是否可恢复,会根据异常类型和请求状态判断是否可以重试
if (!isRecoverable(e, requestSendStarted)) return false;
//检查是否还有更多的路由(Route)可以尝试
//StreamAllocation 负责管理连接的路由(Route)
if (!streamAllocation.hasMoreRoutes()) return false;
// 允许重试
return true;
}
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;
}
- 重定向
如果请求结束后没有发生异常并不代表当前获得的响应就是最终需要交给用户的,还需要进一步来判断是否需要重定向的判断。重定向的判断位于 followupRequest 方法。
/*
* 根据服务器的响应状态码(如重定向、认证失败等),决定是否需要发起后续请求。
* 构建并返回一个新的请求对象,用于后续请求。
* @param userResponse 服务器的响应对象
* @param route 当前请求的路由信息(如代理、目标地址等)
*/
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 处理代理认证失败的情况
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
//检查当前是否使用了HTTP代理
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not " +
"using proxy");
}
//调用 ProxyAuthenticator 进行代理认证,并返回新的请求对象
return client.proxyAuthenticator().authenticate(route, userResponse);
//HTTP 401(认证失败)
case HTTP_UNAUTHORIZED:
//调用 Authenticator 进行认证,并返回新的请求对象
return client.authenticator().authenticate(route, userResponse);
// HTTP 301/302/303/307/308(重定向)
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
//检查客户端是否允许重定向(followRedirects)
if (!client.followRedirects()) return null;
//获取重定向地址(Location 头)
String location = userResponse.header("Location");
if (location == null) return null;
//解析重定向地址,并检查协议是否允许(如HTTP到HTTPS)
HttpUrl url = userResponse.request().url().resolve(location);
if (url == null) return null;
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
//根据重定向类型(如GET、POST)调整请求方法和请求体
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() :
null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// 如果重定向到不同主机,移除认证头(Authorization)
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
//返回新的请求对象
return requestBuilder.url(url).build();
//HTTP 408(请求超时)
case HTTP_CLIENT_TIMEOUT:
// 检查是否允许重试(retryOnConnectionFailure)
if (!client.retryOnConnectionFailure()) {
return null;
}
//检查请求体是否可重复发送
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
//检查是否已经重试过
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
return null;
}
//检查 Retry-After 头,决定是否延迟重试
if (retryAfter(userResponse, 0) > 0) {
return null;
}
return userResponse.request();
//HTTP 503(服务不可用)
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request();
}
return null;
default:
return null;
}
}
BridgeInterceptor
连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能发给服务器,在补全请求头之后再交给下个拦截器处理。
- 添加或修改请求头(如Content-Type、Content-Length、Host、Connection、Accept-Encoding、Cookie、User-Agent等)。
- 处理gzip压缩的响应体。
- 管理Cookie的存储和加载。
public Response intercept(Chain chain) throws IOException {
//从Chain中获取原始的Request对象,并创建一个新的Request.Builder对象,用于构建修改后的请求
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// 处理请求体(RequestBody)
RequestBody body = userRequest.body();
//检查其Content-Type和Content-Length
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");
}
}
//处理Host头
if (userRequest.header("Host") == null) {
//如果请求中没有Host头,则根据请求的URL添加Host头
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//处理Connection头
if (userRequest.header("Connection") == null) {
//添加Connection: Keep-Alive头,以保持长连接
requestBuilder.header("Connection", "Keep-Alive");
}
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());
}
//使用修改后的请求对象调用chain.proceed(),执行实际的网络请求,并获取响应
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();
}
CacheInterceptor
- 核心功能是根据缓存策略决定是否使用缓存、是否发起网络请求,并在必要时更新缓存。
步骤
- 缓存查找:从缓存中查找与请求匹配的响应。
- 缓存策略:根据请求和缓存候选决定是否使用缓存或发起网络请求。
- 条件请求:处理304 Not Modified响应,优化缓存使用。
- 缓存更新:将网络响应写入缓存,或根据请求方法移除缓存。
- 错误处理:在无法满足请求时返回504错误。
public Response intercept(Chain chain) throws IOException {
//获取缓存候选
//如果缓存(cache)存在,尝试从缓存中获取与当前请求匹配的缓存响应(cacheCandidate)
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 缓存策略
//根据当前时间、请求和缓存候选,生成缓存策略(CacheStrategy)
//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.
}
//无网络请求且无缓存
//返回一个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 {
networkResponse = chain.proceed(networkRequest);
} finally {
//请求失败有缓存候选,则关闭缓存候选的响应体
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 处理条件请求(Conditional GET)
if (cacheResponse != null) {
//网络请求返回304 Not Modified,则表示服务器资源未修改,可以直接使用缓存响应
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();
//合并缓存响应和网络响应的头信息,更新缓存,并返回新的响应
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
//关闭缓存响应的响应体
closeQuietly(cacheResponse.body());
}
}
//t构建最终的响应对象,包含缓存响应和网络响应的信息
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//更新缓存
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response,
networkRequest)) {
//将响应写入缓存
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//如果请求方法(如POST、PUT等)会使缓存失效,则移除相关缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
}
}
}
return response;
}
缓存策略
判断是否可以缓存主要是通过cachestrategy,在 cachestrategy 中存在两个成员: networkRequest 与cacheResponse。他们的组合如下:
networkRequest | cacheResponse | 说明 |
---|---|---|
Null | Not Null | 使用缓存 |
Not Null | Null | 发起请求 |
Null | Null | 直接gg,返回504 |
Not Null | Not Null | 发起请求,若得到响应为304(无修改),则更新缓存响应并返回 |
-
判断是否使用缓存都是通过CacheStrategy。
- 常用属性,也就是请求头
属性 | 说明 |
---|---|
Date | 消息发送的时间 |
Expires | 资源过期的时间 |
Last-Modified | 资源最后修改时间 |
ETag | 资源在服务器的唯一标识 |
Age | 服务器用缓存响应请求,该缓存从产生到现在经过多长时间(秒) |
If-Modified-Since | 服务器没有在指定的时间后修改请求对应资源,返回304(无修改) |
If-None-Match | 服务器将其与请求对应资源的 Etag 值进行比较,匹配返回304 |
Cache-Control |
Cache-Control有如下几种组合
值 | 说明 |
---|---|
max-age | 单位秒,资源最大有效时间 |
public | 表明该资源可以被任何用户缓存,比如客户端,代理服务器等都可以缓存资源 |
private | 表明该资源只能被单个用户缓存,默认是private |
no-store | 资源不允许被缓存 |
no-cache | (请求)不使用缓存 |
immutable | (响应)资源不会改变 |
min-fresh | [秒]:(请求)缓存最小新鲜度(用户认为这个缓存有效的时长) |
must-revalidate | (响应)不允许使用过期缓存 |
max-stale | [秒]:(请求)缓存过期后多久内仍然有效 |
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
//本次请求缓存响应不为空,先保存数据
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
//todo 濡傛灉鍙互浣跨敤缂撳瓨锛岄偅networkRequest蹇呭畾涓簄ull锛涙寚瀹氫簡鍙娇鐢ㄧ紦瀛樹絾鏄痭etworkRequest鍙堜笉涓簄ull锛屽啿绐併€傞偅灏眊g(鎷︽埅鍣ㄨ繑鍥?04)
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
通过getCandidate判断缓存策略
- 缓存是否存在:如果缓存不存在,直接发起网络请求。
- HTTPS请求的缓存:确保HTTPS请求的缓存包含握手信息。
- 缓存响应的有效性:检查缓存响应是否可缓存。
- 用户请求配置:根据请求的缓存控制头决定是否使用缓存。
- 资源是否不变:如果缓存响应不可变,直接使用缓存。
- 缓存有效期:计算缓存的有效期,判断是否允许使用缓存。
- 缓存过期处理:如果缓存过期,尝试使用条件请求验证缓存是否仍然有效。
private CacheStrategy getCandidate() {
//1. 缓存是否存在
//表示没有找到对应的缓存,直接返回一个需要发起网络请求的策略。
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//2. https请求的缓存
//如果请求是HTTPS,但缓存响应中没有握手信息(handshake),则缓存无效,需要发起网络请求
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//3.缓存响应的有效性
//如果不可缓存,则需要发起网络请求
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//4.用户请求配置
//获取请求的缓存控制头(Cache-Control)
//如果请求头包含no-cache(不使用缓存)或包含条件请求头(如If-Modified-Since或If-None-Match),则需要发起网络请求
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//5.资源是否不变
//获取缓存响应的缓存控制头
//如果缓存响应包含immutable(资源不可变),则直接使用缓存响应,无需发起网络请求
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//6.响应的缓存有效期
long ageMillis = cacheResponseAge();//缓存响应的年龄
long freshMillis = computeFreshnessLifetime();//缓存有效期
if (requestCaching.maxAgeSeconds() != -1) {
//如果请求中指定了max-age,则综合响应有效期和请求的max-age,取最小值作为缓存有效期
freshMillis = Math.min(freshMillis,
SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
// 7.请求的缓存控制
//如果请求包含min-fresh,表示缓存必须至少还有指定时间才有效。
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//如果请求包含max-stale,表示缓存过期后还可以使用指定时间
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//8.判断是否允许使用缓存
//如果缓存未过期(ageMillis + minFreshMillis < freshMillis + maxStaleMillis),则允许使用缓存。
//如果缓存接近过期,添加Warning头,提示缓存可能已过期
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// 9.缓存过期处理
//如果缓存已过期,尝试使用条件请求(If-None-Match或If-Modified-Since)验证缓存是否仍然有效。
//如果没有条件头可用,则直接发起网络请求。
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
//10.构建条件请求
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
ConnectInterceptor
- 负责在拦截器链中建立与目标服务器的网络连接。
- 初始化网络连接(RealConnection)和 HTTP 编解码器(HttpCodec),并将它们传递给后续的拦截器。
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 {
//获取当前的请求(request)和流分配器(streamAllocation)
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//用于管理网络连接和流的类
//它负责从连接池中获取或创建新的连接(RealConnection),并为请求分配一个 HTTP 流(HttpCodec)
StreamAllocation streamAllocation = realChain.streamAllocation();
//创建 HTTP 流
//根据请求方法决定是否进行详细的健康检查(doExtensiveHealthChecks)。对于非 GET 请求(如 POST、PUT 等),需要进行更严格的检查。
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//调用 streamAllocation.newStream() 方法创建一个 HTTP 流(HttpCodec),用于编码请求和解码响应。
//HttpCodec 是一个接口,具体实现可能是 Http1Codec(HTTP/1.1)或 Http2Codec(HTTP/2)。
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取网络连接
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
StreamAllocation
StreamAllocation 是 OkHttp 中用于管理网络连接和 HTTP 流的类。它的主要职责是从连接池中获取或创建连接,并为请求分配一个 HTTP 流。
核心职责
-
连接管理:
- 从连接池中查找可复用的连接。
- 如果没有可复用的连接,则创建新的连接。
- 管理连接的生命周期,包括连接的创建、复用和释放。
-
流分配:
- 为每个请求分配一个 HTTP 流(HttpCodec)。
- 支持 HTTP/1.1 和 HTTP/2 协议。
-
健康检查:
- 对连接进行健康检查,确保连接可用。
- 对于非 GET 请求(如 POST、PUT 等),进行更严格的健康检查。
关键方法
-
newStream():
- 创建一个新的 HTTP 流(HttpCodec)。
- 如果连接池中有可复用的连接,则复用;否则创建新的连接。
- 根据请求方法决定是否进行详细的健康检查。
-
connection():
- 返回当前的网络连接(RealConnection)。
-
release():
- 释放连接和流,将其返回到连接池中。
工作流程
- 当 ConnectInterceptor 调用 newStream() 时,StreamAllocation 会尝试从连接池中获取一个可复用的连接。
- 如果没有可复用的连接,则创建一个新的连接。
- 为请求分配一个 HTTP 流(HttpCodec),并将其传递给后续的拦截器。
HttpCodec
HttpCodec 是一个接口,定义了 HTTP 请求和响应的编码与解码方法。它的具体实现类包括 Http1Codec(用于 HTTP/1.1)和 Http2Codec(用于 HTTP/2)。
核心职责
- 编码请求:
- 将请求头和请求体编码为字节流,发送给服务器。
- 解码响应:
- 从服务器接收字节流,解码为响应头和响应体。
- 协议支持:
- 支持 HTTP/1.1 和 HTTP/2 协议。
关键方法
- writeRequestHeaders():
- 将请求头写入网络流。
- createRequestBody():
- 创建一个用于写入请求体的 Sink。
- readResponseHeaders():
- 读取响应头。
- openResponseBody():
- 打开响应体,返回一个 Source。
实现类
-
Http1Codec:
- 用于 HTTP/1.1 协议。
- 基于 Socket 实现请求和响应的读写。
-
Http2Codec:
- 用于 HTTP/2 协议。
- 基于 Http2Connection 实现请求和响应的读写。
工作流程
- 当 CallServerInterceptor 调用 writeRequestHeaders() 时,HttpCodec 会将请求头编码并发送给服务器。
- 当 CallServerInterceptor 调用 createRequestBody() 时,HttpCodec 会创建一个 Sink,用于写入请求体。
- 当 CallServerInterceptor 调用 readResponseHeaders() 时,HttpCodec 会从服务器读取响应头。
- 当 CallServerInterceptor 调用 openResponseBody() 时,HttpCodec 会打开响应体,返回一个 Source。
RealConnection
RealConnection 是 OkHttp 中表示实际网络连接的类。它封装了底层的 Socket 和协议相关的信息。
核心职责
- 连接管理:
- 封装底层的 Socket,管理连接的建立、复用和关闭。
- 协议协商:
- 在建立连接时,与服务器协商使用的协议(HTTP/1.1 或 HTTP/2)。
- 连接复用:
- 支持 HTTP/1.1 的连接复用(Keep-Alive)。
- 支持 HTTP/2 的多路复用(Multiplexing)。
关键属性
- socket:
- 底层的 Socket,用于与服务器通信。
- protocol:
- 当前连接使用的协议(如 HTTP_1_1 或 HTTP_2)。
- http2Connection:
- 如果使用 HTTP/2 协议,则包含一个 Http2Connection 实例。
关键方法
- connect():
- 建立与服务器的连接。
- 进行协议协商,确定使用 HTTP/1.1 还是 HTTP/2。
- isHealthy():
- 检查连接是否健康,是否可以复用。
- newCodec():
- 根据协议创建一个新的 HttpCodec 实例。
工作流程
- 当 StreamAllocation 调用 newStream() 时,RealConnection 会尝试复用现有的连接。
- 如果没有可复用的连接,则调用 connect() 方法建立新的连接。
- 在建立连接时,进行协议协商,确定使用 HTTP/1.1 还是 HTTP/2。
- 根据协议创建一个新的 HttpCodec 实例,并将其返回给 StreamAllocation。
三者协作流程
- StreamAllocation 从连接池中获取或创建一个 RealConnection。
- RealConnection 建立与服务器的连接,并进行协议协商。
- StreamAllocation 调用 RealConnection 的 newCodec() 方法,创建一个 HttpCodec 实例。
- HttpCodec 负责编码请求和解码响应。
- CallServerInterceptor 使用 HttpCodec 发送请求并接收响应。
- 请求完成后,StreamAllocation 释放连接和流,将其返回到连接池中。
CallServerInterceptor
- OkHttp 拦截器链中的最后一个拦截器,负责与服务器进行实际的网络通信,包括发送请求和接收响应。
- 它是整个拦截器链的核心,直接与服务器交互,完成 HTTP 请求的最终处理。
主要职责
- 发送请求:将请求头和请求体发送给服务器。
- 接收响应:从服务器接收响应头和响应体。
- 处理特殊响应:如 100 Continue 和 WebSocket 升级。
- 管理连接:根据响应头决定是否复用连接。
public Response intercept(Chain chain) throws IOException {
//1.获取上下文信息
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();
//2.发送请求头
//通知事件监听器请求头开始发送。
//使用 HttpCodec 将请求头写入网络流。
//通知事件监听器请求头发送完成。
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
//3.处理请求体
Response.Builder responseBuilder = null;
//如果请求方法允许包含请求体(如 POST、PUT 等),并且请求体不为空,则处理请求体
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//如果请求头包含 Expect: 100-continue,则先发送请求头并等待服务器的 100 Continue 响应
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
//如果收到 100 Continue,则继续发送请求体
//使用 HttpCodec 创建请求体的输出流,并将请求体写入网络流
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()) {
// 如果没有收到 100 Continue,则禁止复用当前连接(对于 HTTP/1.1)
streamAllocation.noNewStreams();
}
}
//4.完成请求的发送,确保所有数据都已写入网络流
httpCodec.finishRequest();
//5.读取响应头
if (responseBuilder == null) {
//通知事件监听器响应头开始接收
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//6.构建响应
//包含请求、握手信息、请求发送时间和响应接收时间
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
//7.处理 100 Continue 响应
if (code == 100) {
// 重新读取实际的响应头
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
//8.通知响应头接收完成
realChain.eventListener().responseHeadersEnd(realChain.call(), response);
//9.处理 WebSocket 升级
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;
}