概括起来说OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。
特点如下:
先要说一下,是怎么使用的
package com.meituan.okhttp.client;
import com.meituan.okhttp.client.interceptor.LoggingInterceptor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.BufferedSink;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@Slf4j
public class OkHttpRequest {
private static final String URL = "http://www.baidu.com";
private static final MediaType MEDIA_TYPE_JSON = MediaType
.parse("application/json; charset = utf-8");
//OkHttpClient client = new OkHttpClient();
OkHttpClient client = new OkHttpClient.Builder().build();
public void syncGet() {//同步get方法
Request request = new Request.Builder().url(URL).build();
final Call call = client.newCall(request);
//因为同步阻塞会阻塞主方法的运行,所以另起线程,当然也可以用线程池,还不如用异步get方法
new Thread(new Runnable() {
public void run() {
try {
Response response = call.execute();//调用call拿到返回结果
log.info("syncGet: " + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void aSyncGet() {//异步get方法
Request request = new Request.Builder()
.url(URL)
.get()//默认是get 可以不写
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {//异步发起的请求会被加入到队列中通过线程池来执行。通过回调方式拿到结果
public void onFailure(Call call, IOException e) {
log.info("onFailure");
}
public void onResponse(Call call, Response response)
throws IOException {
Request request1 = call.request();//这个只是说明可以拿到request。
log.info("request: " + request1);
log.info("aSyncGet onSuccess: " + response.body().string());
}
});
}
public void aSyncPost() {//异步post方法
RequestBody requestBody = new RequestBody() {
@Nullable
@Override
public MediaType contentType() {
return MediaType
.parse("application/json; charset = utf-8");
}
@Override
public void writeTo(@NotNull BufferedSink bufferedSink)
throws IOException {
bufferedSink.write("zcz".getBytes());
}
};
Request request = new Request.Builder()
.url(URL)
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call,
@NotNull IOException e) {
log.info("onFailure: post");
}
@Override
public void onResponse(@NotNull Call call,
@NotNull Response response) throws IOException {
log.info("onSuccess: post" + response.body().string());
}
});
}
public void testInterceptor() {//添加自定义拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
log.info("onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (body != null) {
log.info("onResponse: " + response.body().string());
body.close();
}
}
});
}
}
这里get方法的同步和异步只体现在Call这个对象上,只是调用的方法不同,具体有什么不同呢?这个以后再说。还有一个需要注意的地方就是异步方法通过回调方式拿到Response。
OkHttpClient client = new OkHttpClient();
OkHttpClient client = new OkHttpClient.Builder().build();
其实推荐让 OkHttpClient 保持单例,用同一个 OkHttpClient 实例来执行你的所有请求,因为每一个 OkHttpClient 实例都拥有自己的连接池和线程池,重用这些资源可以减少延时和节省资源,如果为每个请求创建一个 OkHttpClient 实例,显然就是一种资源的浪费。
第一种没什么说的(属性保持默认)。
说一下第二种,看到这个你会想到什么呢?没错!就是builder(建造者)模式,那么问题来了,
首先要了解建造者模式。设想一个场景:当一个类有很多属性要初始化,并且大多都保持默认,只需要变动其中的某个或者某几个,如果普通方法需要创建很多的重载的构造方法。建造者模式就是专门来解决这个痛点的。那么怎么解决的呢?
一般情况下,有一个内部类通常叫做Builder,其属性与外部类相同,内部类的的“set方法”,除了set属性之外,还会返回本身。这样做的目的就是可以“链式”设置属性。外部类的构造方法就可以只传入这个内部类(外部类.属性=内部类.属性),而内部类的builder()方法对外部类进行初始化(调用外部类的构造方法),并返回外部类。没错!OkHttpClient就是使用了这种方式,理解了这个,就好理解OkHttpClient这个类了
放源代码太麻烦,放一张结构图,就是上面所述的那样。
okhttp3.OkHttpClient:
final Dispatcher dispatcher; // 调度器
final @Nullable
Proxy proxy; // 代理
final List<Protocol> protocols; // 协议
final List<ConnectionSpec> connectionSpecs; // 传输层版本和连接协议
final List<Interceptor> interceptors; // 拦截器
final List<Interceptor> networkInterceptors; // 网络拦截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector; // 代理选择器
final CookieJar cookieJar; // cookie
final @Nullable
Cache cache; // 缓存
final @Nullable
InternalCache internalCache; // 内部缓存
final SocketFactory socketFactory; // socket 工厂
final SSLSocketFactory sslSocketFactory; // 安全套接层 socket 工厂,用于 https
final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名
final HostnameVerifier hostnameVerifier; // 主机名字验证
final CertificatePinner certificatePinner; // 证书链
final Authenticator proxyAuthenticator; // 代理身份验证
final Authenticator authenticator; // 本地身份验证
final ConnectionPool connectionPool; // 连接池
final Dns dns; // 域名
final boolean followSslRedirects; // 安全套接层重定向
final boolean followRedirects; // 本地重定向
final boolean retryOnConnectionFailure; // 重试连接失败
final int callTimeout;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
没错真的有很多属性!属性都有注释,不一一解释,感兴趣的可以细看,注意的是缓存,拦截器,链接池等字眼,以后再解释!
okhttp3.OkHttpClient.Builder
public static final class Builder {
Dispatcher dispatcher;//任务调度器
@Nullable
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable
Cache cache;
@Nullable
InternalCache internalCache;
SocketFactory socketFactory;
@Nullable
SSLSocketFactory sslSocketFactory;
@Nullable
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int callTimeout;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
public Builder() {
dispatcher = new Dispatcher();//任务调度器
protocols = DEFAULT_PROTOCOLS;//协议
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();//链接池
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
callTimeout = 0;
connectTimeout = 10_000;//超时时间
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
public Builder addInterceptor(Interceptor interceptor) {
if (interceptor == null)
throw new IllegalArgumentException("interceptor == null");
interceptors.add(interceptor);
return this;
}
属性一一对应。设值返回this,就像之前所讲的。
okhttp3.OkHttpClient#OkHttpClient
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util
.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = Util.platformTrustManager();
this.sslSocketFactory = newSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner
.get(trustManager);
}
if (sslSocketFactory != null) {
Platform.get().configureSslSocketFactory(sslSocketFactory);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner
.withCertificateChainCleaner(certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.callTimeout = builder.callTimeout;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
if (interceptors.contains(null)) {
throw new IllegalStateException(
"Null interceptor: " + interceptors);
}
if (networkInterceptors.contains(null)) {
throw new IllegalStateException(
"Null network interceptor: " + networkInterceptors);
}
}
这个类到此为止,感兴趣可以自己看。
由于也使用了建造者模式,细节就不分析了,只分析属性,大部分可以见名知意,难理解的已经标注
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable
RequestBody body;
//通过tags来同时取消多个请求。当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。
// 之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。
final Map<Class<?>, Object> tags;
//相当于请求头中的Cache-Control:
private volatile @Nullable CacheControl cacheControl; // Lazily initialized.
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;//方法,默认get
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
分析完OkHttpClient和Request,继续看demo
Call call = client.newCall(request);
每一个请求任务都封装为一个Call,其实现为RealCall。
okhttp3.Call.Factory#newCall
public interface Call extends Cloneable {
...
interface Factory {
Call newCall(Request request);
}
}
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
....
@Override
public Call newCall(Request request) {//实现接口,重写方法
return RealCall.newRealCall(this, request, false /* for web socket */);//默认实现
}
}
其实是调用okhttp3.RealCall#newRealCall这个方法实现的。
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;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
//创建了一个RetryAndFollowUpInterceptor,用于处理请求错误和重定向等,
// 这是 Okhttp 框架的精髓 interceptor chain 中的一环,
// 默认情况下也是第一个拦截器,除非调用 OkHttpClient.Builder#addInterceptor(Interceptor) 来添加全局的拦截器。
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
this.timeout = new AsyncTimeout() {
@Override
protected void timedOut() {
cancel();
}
};
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
创建call的时候添加了一个事件监听器,看看源码是做什么的,这里包含了很多方法。其实可以理解为"call"的生命周期。不仔细看了。
在OkHttpClient中,我们可以传入EventListener的工厂方法,为每一个请求创建一个EventListener,来接收非常细的事件回调
看demo
Response response = call.execute();//调用call拿到返回结果
之前分析过,这里的call其实是RealCall,直接看其execute()方法,okhttp3.RealCall#execute
/**
* 检测这个 call 是否已经执行了,保证每个 call 只能执行一次。
* 通知 dispatcher 已经进入执行状态,将 call 加入到 runningSyncCalls 队列中。
* 调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果。
* 最后还要通知 dispatcher 自己已经执行完毕,将 call 从 runningSyncCalls 队列中移除。
* @return
* @throws IOException
*/
@Override
public Response execute() throws IOException {//模板方法实现
synchronized (this) {
// 每个 call 只能执行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 请求开始, 将自己加入到runningSyncCalls队列中
client.dispatcher().executed(this);
// 通过一系列拦截器请求处理和响应处理得到最终的返回结果
Response result = getResponseWithInterceptorChain();//调用 getResponseWithInterceptorChain()获得响应内容
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 请求完成, 将其从runningSyncCalls队列中移除
client.dispatcher().finished(this);
}
}
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
所以其实这里只是加入到一个队列里面,并没有发起请求,发起请求还是通过getResponseWithInterceptorChain()实现的。这里先卖一下关子,一会再讲这个方法,先讲一下异步的方式,因为都会调用这个方法。
看demo
call.enqueue(new Callback() {//异步发起的请求会被加入到队列中通过线程池来执行。通过回调方式拿到结果
public void onFailure(Call call, IOException e) {
log.info("onFailure");
}
public void onResponse(Call call, Response response)
throws IOException {
Request request1 = call.request();//这个只是说明可以拿到request。
log.info("request: " + request1);
log.info("aSyncGet onSuccess: " + response.body().string());
}
});
在异步请求中,我们通过Callback来获得简单清晰的请求回调(onFailure、onResponse)
其实是调用okhttp3.RealCall#enqueue这个方法实现的。
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");//每个请求只能执行一次
executed = true;
}
captureCallStackTrace();//为retryAndFollowUpInterceptor加入了一个用于追踪堆栈信息的callStackTrace
eventListener.callStart(this);//之前加的listener,在这里调用
//这里创建了一个 AsyncCall 并将Callback传入,接着再交给任务分发器 Dispatcher 来进一步处理。
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
void enqueue(AsyncCall call) {
// 将AsyncCall加入到准备异步调用的队列中
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
/**
* Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
* them on the executor service. Must not be called with synchronization because executing calls
* can call into user code.
*
* 将 readyAsyncCalls 中的任务移动到 runningAsyncCalls中,并交给线程池来执行。
*
* 从准备异步请求的队列中取出可以执行的请求(正在运行的异步请求不得超过64,同一个host下的异步请求不得超过5个),
* 加入到 executableCalls 列表中。
* 循环 executableCalls 取出请求 AsyncCall 对象,调用其 executeOn 方法。
*
* @return true if the dispatcher is currently running calls.
*/
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
//若条件允许,将readyAsyncCalls中的任务移动到runningAsyncCalls中,并交给线程池执行
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.大于最大请求数64,跳出
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost)
continue; // Host max capacity.
i.remove();
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 中的 runningAsyncCalls双端队列中通过线程池来执行。
okhttp3.Dispatcher#executorService
/**
* 这个线程池没有核心线程,线程数量没有限制,空闲60s就会回收,适用于大量耗时较短的任务;
* 与 Executors.newCachedThreadPool() 比较类似;
*
* 虽然线程池无任务上限,但是Dispatcher对入口enqueue()进行了把关,
* 最大的异步任务数默认是64,同一个主机默认是5,当然这两个默认值是可以修改的,Dispatcher提供的修改接口;
*/
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;
}
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);//这里的this指AsyncCall
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
AsyncCall必然实现Runable接口,因此反过来看其run()方法,看引用(okhttp3.RealCall.AsyncCall#executeOn)知道AsyncCall其实是RealCall的一个内部类
现实是AsyncCall并没有直接实现Runable接口
final class AsyncCall extends NamedRunnable {
...
}
但是其父类NamedRunnable实现了Runnable接口
/**
* Runnable implementation which always sets its thread name.
*/
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override
public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);//设置名字,方便监控
try {
execute();//模板方法,具体实现留给子类实现
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
这是一个模板方法的典型实现,因此需要看子类的execute()方法
@Override
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
//异步和同步走的是同样的方式,只不过在子线程中执行
// 请求网络获取结果
Response response = getResponseWithInterceptorChain();//调用 getResponseWithInterceptorChain()获得响应内容
signalledCallback = true;//这个标记主要是避免异常时2次回调
responseCallback.onResponse(RealCall.this, response);//回调Callback,将响应内容传回去
} catch (IOException e) {
e = timeoutExit(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); //回调Callback告知失败
}
} catch (Throwable t) {
cancel();
if (!signalledCallback) {
IOException canceledException = new IOException("canceled due to " + t);
responseCallback.onFailure(RealCall.this, canceledException);
}
throw t;
} finally {
//不管请求成功与否,都进行finished()操作
// 调度完成,移出队列
client.dispatcher().finished(this);
}
}
好了,同步和异步都要调用okhttp3.RealCall#getResponseWithInterceptorChain这个方法获取Response。
之前说了很多队列,这里总结一下,其实是三个队列,都是在okhttp3.Dispatcher类里面
public final class Dispatcher {
private int maxRequests = 64;//最大请求数量
private int maxRequestsPerHost = 5;//每台主机最大的请求数量
private @Nullable Runnable idleCallback;
/**
* Executes calls. Created lazily.
*/
private @Nullable
ExecutorService executorService;//线程池,跟CachedThreadPool非常类似,这种类型的线程池,适用于大量的耗时较短的异步任务。
//异步请求,Dispatcher 是通过启动 ExcuteService 执行,线程池的最大并发量 64,
// 异步请求先放置在 readyAsyncCalls,可以执行时放到 runningAsyncCalls 中,执行结束从runningAsyncCalls 中移除。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//准备执行的请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在运行的请求队列
//同步请求,由于它是即时运行的, Dispatcher 只需要运行前请求前存储到 runningSyncCalls,
// 请求结束后从 runningSyncCalls 中移除即可。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//一个正在运行的同步请求队列
...
}
/**
* 这里先是创建了一个 Interceptor 的ArrayLIst,然后将各类 interceptor 全部加入到ArrayLIst中,之后按照顺序执行。包含以下 interceptor:
*
* interceptors:配置 OkHttpClient 时设置的 inteceptors
* RetryAndFollowUpInterceptor:负责失败重试以及重定向
* BridgeInterceptor:负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应
* CacheInterceptor:负责读取缓存直接返回、更新缓存
* ConnectInterceptor:负责和服务器建立连接
* networkInterceptors:配置 OkHttpClient 时设置的 networkInterceptors
* CallServerInterceptor:负责向服务器发送请求数据、从服务器读取响应数据
* @return
* @throws IOException
*/
//拦截器链
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();//这是一个List,是有序的
interceptors.addAll(client.interceptors());//首先添加的是用户添加的全局拦截器
interceptors.add(retryAndFollowUpInterceptor);//错误、重定向拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));//封装request和response拦截器,桥接拦截器,桥接应用层与网络层,添加必要的头、
//构造这个拦截器的时候传入的是我们构造的OkHttpClient中设置的interanlCache,
// 而当我们用默认方式构造OkHttpClient的时候是不会创建缓存的,也就是internalCache=null的
interceptors.add(new CacheInterceptor(client.internalCache()));//缓存处理,Last-Modified、ETag、DiskLruCache等
interceptors.add(new ConnectInterceptor(client));//负责和服务器建立连接拦截器
//从这就知道,通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
//配置 OkHttpClient 时设置的 networkInterceptors
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//真正访问服务器的拦截器
//负责向服务器发送请求数据、从服务器读取响应数据(实际网络请求)
interceptors.add(new CallServerInterceptor(forWebSocket));
//注意这里的0,即从头开始执行,这里传入的参数很多都是null,之后再创建
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//最后通过RealInterceptorChain#proceed(Request)来执行整个 interceptor chain
Response response = chain.proceed(originalRequest);
if (retryAndFollowUpInterceptor.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
}
OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类:
①一类是全局的 interceptor,该类 interceptor 在整个拦截器链中最早被调用,通过 OkHttpClient.Builder#addInterceptor(Interceptor) 传入;
②另外一类是非网页请求的 interceptor ,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的 interceptor 被保存在 List interceptors 集合中,按照添加顺序来逐个调用,具体可参考 RealCall#getResponseWithInterceptorChain() 方法。通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 传入;
这里举一个简单的例子,例如有这样一个需求,我要监控App通过 OkHttp 发出的所有原始请求,以及整个请求所耗费的时间,针对这样的需求就可以使用第一类全局的 interceptor 在拦截器链头去做。
在OkHttp3中,其灵活性很大程度上体现在可以 intercept 其任意一个环节,而这个优势便是okhttp3整个请求响应架构体系的精髓所在,先放出一张主框架请求流程图,接着再分析源码。
OkHttpClient 实现了 Call.Fctory,负责为 Request 创建 Call。
RealCall 是 Call 的具体实现,它的异步请求是通过 Dispatcher 调度器利用ExcutorService实现,而最终进行网络请求时和同步请求一样,都是通过 getResponseWithInterceptorChain 方法实现。
getResponseWithInterceptorChain 方法中采用了责任链模式,每一个拦截器各司其职,主要做两件事。
拦截上一层拦截器封装好的 Request,然后自身对这个 Request 进行处理,处理后向下传递。
接收下一层拦截器传递回来的 Response,然后自身对 Response 进行处理,返回给上一层。
retryAndFollowUpInterceptor——失败和重定向过滤器BridgeInterceptor——封装request和response过滤器CacheInterceptor——缓存相关的过滤器,负责读取缓存直接返回、更新缓存ConnectInterceptor——负责和服务器建立连接,连接池等networkInterceptors——配置 OkHttpClient 时设置的 networkInterceptorsCallServerInterceptor——负责向服务器发送请求数据、从服务器读取响应数据(实际网络请求)
高内聚,低耦合,可扩展,单一职责。
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;//主要方法
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable
Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
各个拦截器都实现了这个接口,如果想自己扩展也可以实现该接口比如
com.meituan.okhttp.client.interceptor.LoggingInterceptor
package com.meituan.okhttp.client.interceptor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@Slf4j
public class LoggingInterceptor implements Interceptor {
private static final String TAG = "LoggingInterceptor";
// private static final Logger logger = LogManager.getLogger();
@NotNull
@Override
public Response intercept(@NotNull Chain chain)
throws IOException {
Request request = chain.request();
long startTime = System.nanoTime();
Response response = chain.proceed(request);
log.info("time" + (System.nanoTime()-startTime));
return response;
}
}
com.meituan.okhttp.client.OkHttpRequest#testInterceptor
public void testInterceptor() {//添加自定义拦截器
OkHttpClient okHttpClient = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();
Request request = new Request.Builder()
.url("http://www.publicobject.com/helloworld.txt")
.header("User-Agent", "OkHttp Example")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
log.info("onFailure: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (body != null) {
log.info("onResponse: " + response.body().string());
body.close();
}
}
});
}
其interceptor方法很长,但是简化以后
@Override public Response intercept(Chain chain) throws IOException {
。。。
while (true) {
。。。
try {
response = realChain.proceed(request, streamAllocation, null, null);
}
。。。
if(满足条件){
return response;
}
。。。
//不满足条件,一顿操作,赋值再来!
request = followUp;
priorResponse = response;
}
}
其实就流程来上,我认为宏观上代码缩减到这样就够了,甚至可以再删点,这里先从流程上理解.其实代码成这样,基本上大家都能理解了,一个while(true)表明这是个循环体,循环体主要做的事可以看到其实是递归的主要方法。
response = realChain.proceed(request, streamAllocation, null, null);
执行了这个方法后,就会交给下一个过滤器继续执行,所以单从这里来看,我们可以简单的理解为这个过滤器其实没做什么。但是当出现了一些问题,导致不满足条件的时候,就需要进行一系列的操作,重新复制Request,重新请求,这也就是while的功能,对应的也就是这个过滤器的主要功能:重试和重定向。这里我们宏观上已经对RetryAndFollowUpInterceptor有了一个基本的理解了。
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//创建一个StreamAllocation,刚传入的时候是null,这个类大概可以理解为是处理Connections,Streams,Calls三者之间的关系
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
//统计重定向次数,不能大于20
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//取消
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;//初始化时赋值为true
try {
//调用下一个interceptor的来获得响应内容
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
//尝试连接一个路由失败,这个请求还没有被发出
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;//重试
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
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.
//没有捕获到的异常,最终要释放
//由于releaseConnection初始化为true,
// 而当正常执行realChain.proceed或在执行过程中捕捉到异常时设置为false,
// 所以当执行过程中捕捉到没有检测到的异常时,需要释放一些内容。
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
//这里基本上都没有讲,priorResponse是用来保存前一个Resposne的,
// 这里可以看到将前一个Response和当前的Resposne结合在一起了,
// 对应的场景是,当获得Resposne后,发现需要重定向,则将当前Resposne设置给priorResponse,再执行一遍流程,
//直到不需要重定向了,则将priorResponse和Resposne结合起来。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
//重定向处理
//判断是否需要重定向,如果需要重定向则返回一个重定向的Request,没有则为null
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {//不需要重定向
//是WebSocket,释放
streamAllocation.release();
return response;//返回response
}
//需要重定向,关闭响应流
closeQuietly(response.body());
//重定向次数++,并且小于最大重定向次数MAX_FOLLOW_UPS(20)
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//是UnrepeatableRequestBody, 刚才看过也就是是流类型,没有被缓存,不能重定向
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//判断是否相同,不然重新创建一个streamConnection
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;
priorResponse = response;
}
}
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
*/
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
//如果OkHttpClient直接配置拒绝失败重连,return false,默认时true
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
//如果请求已经发送,并且这个请求体是一个UnrepeatableRequestBody类型,则不能重试。
//StreamedRequestBody实现了UnrepeatableRequestBody接口,是个流类型,不会被缓存,所以只能执行一次,具体可看。
if (requestSendStarted && requestIsUnrepeatable(e, userRequest)) return false;
// This exception is fatal.
//一些严重的问题,就不要重试了
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
//没有更多的路由就不要重试了
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
/**
* 1.通过Request尝试到Cache中拿缓存(里面非常多流程),当然前提是OkHttpClient中配置了缓存,默认是不支持的。
* 2.根据response,time,request创建一个缓存策略,用于判断怎样使用缓存。
* 3.如果缓存策略中设置禁止使用网络,并且缓存又为空,则构建一个Resposne直接返回,注意返回码=504
* 4.缓存策略中设置不使用网络,但是又缓存,直接返回缓存
* 5.接着走后续过滤器的流程,chain.proceed(networkRequest)
* 6.当缓存存在的时候,如果网络返回的Resposne为304,则使用缓存的Resposne。
* 7.构建网络请求的Resposne
* 8.当在OKHttpClient中配置了缓存,则将这个Resposne缓存起来。
* 9.缓存起来的步骤也是先缓存header,再缓存body。
* 10.返回Resposne。
*
*
* @param chain
* @return
* @throws IOException
*/
@Override
public Response intercept(Chain chain) throws IOException {
//1.尝试通过这个Request拿缓存
//默认cache为null,可以配置cache,不为空尝试获取缓存中的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根据response,time,request创建一个缓存策略,用于判断怎样使用缓存
//对应的是CacheStrategy这个类,里面主要涉及Http协议中缓存的相关设置
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.
//2.如果不允许使用网络并且缓存为空,新建一个504的Resposne返回。
//如果缓存策略中禁止使用网络,并且缓存又为空,则构建一个Resposne直接返回,注意返回码=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 we don't need the network, we're done.
//不使用网络,但是又缓存,直接返回缓存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//调用下一个拦截器进行网络请求
//3.如果不允许使用网络,但是有缓存,返回缓存。
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.
//4.链式调用下一个过滤器。
networkResponse = chain.proceed(networkRequest);
//当缓存响应和网络响应同时存在的时候,选择用哪个
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果返回码是304,客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户
// 只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
//则使用缓存的响应
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());
}
}
//使用网络响应
//6、7.使用网络请求得到的Resposne,并且将这个Resposne缓存起来(前提当然是能缓存)。
// 接下来就是脑袋都大的细节了,我也不敢说分析的十分详细,只能就我的理解总体分析,学习。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//所以默认创建的OkHttpClient是没有缓存的
//存缓存这个步骤的分析了,其实前面的取缓存的分析结束后,
// 这里对存缓存不难猜测其实是想对应的,也就比较好理解了,对应的大体应该是header存入clean[0],body存入clean[1]。
if (cache != null) {
//9。 将响应缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//缓存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//缓存body
return cacheWritingResponse(cacheRequest, response);
}
//只能缓存GET....不然移除request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
@Override
public Response intercept(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
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取连接
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
@Override
public Response intercept(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();
//开始写入header
realChain.eventListener().requestHeadersStart(realChain.call());
//HttpCodec其实是一个接口,对应的使用策略模式分别根据是Http还是Http/2请求,这里就看一下Http1Codec的实现吧。
httpCodec.writeRequestHeaders(request);//1.开始写入header
//写入结束
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;//首先构建了一个null的responseBuilder。
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.
//2.当Header为Expect: 100-continue时,只发送请求头
// 当Header为Expect: 100-continue时,只发送请求头
// 发送一个请求, 包含一个Expect:100-continue, 询问Server使用愿意接受数据
// 接收到Server返回的100-continue应答以后, 才把数据POST给Server
// 1.如果可以继续请求,则responseBuilder=null
// 2.如果不行,则responseBuilder不为空,并且为返回的Header
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();//刷新请求
realChain.eventListener().responseHeadersStart(realChain.call());
//读取response的header信息,并返回一个responseBuilder赋值给responseBuilder。
responseBuilder = httpCodec.readResponseHeaders(true);
}
//如果可以继续请求,则Responsebuilder=null,执行if判断里的内容,可以看到就是对于请求体的写入操作,当然任然是使用Okio进行写入操作。
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
//得到响应后,根据Resposne判断是否写入请求体
// 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);
//3.写入请求体
request.body().writeTo(bufferedRequestBody);
//写入完成
bufferedRequestBody.close();
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();
}
}
//4.结束请求
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
//5.得到响应头
responseBuilder = httpCodec.readResponseHeaders(false);
}
//6.构建初步响应
// 通过返回得到的responseBuilder构建携带有响应头的Reponse。
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);
// 剩下的其实就是对弈返回码的判断,对应正常的返回码的话,构建响应体到Response中,最后将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;
}
1.首先发现了计算机网络的重要性,Http协议,准备后面入一本书,再回顾学习一下。
2.线程安全各种方式,各种数据结构。
3.设计模式,粗略回顾一下:建造者模式,责任链模式,策略模式…其实不用强行使用设计模式,其实主要是设计思想
4.面向接口编程,这个不用说,越看越重要。
5.方法的命名,其实我感觉挺有趣,也挺讲究的。
6.注释,其实和5差不多,基本上就是编程风格了。
https://github.com/ouyangxizhu/okhttp-demo.git
https://github.com/ouyangxizhu/okhttp-origin-3.12.x.git