如官网(square.github.io/okhttp/) 介绍,OkHttpClient有很多优点。
OkHttp是一个HTTP客户端,以下特性默认有效:
- HTTP/2支持允许所有请求到同一主机共享一个套接字。
- 连接池减少了请求延迟(如果HTTP/2不可用)。
- 透明GZIP缩小下载大小。
- 响应缓存完全避免了网络的重复请求。
当网络出现问题时,OkHttp会从常见的连接问题中静默地恢复。如果服务有多个IP地址,如果第一次连接失败,OkHttp将尝试替代地址。
OkHttp非常易用,请求/响应API设计具有链式调用和不变性。它既支持同步阻塞调用,也支持带回调的异步调用。
现在,我们一起来看看OkHttpClient源码实现,本文采用3.14.0版本。
js
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.0</version>
</dependency>
一、执行同步请求
先看一个同步请求示例。
java
public static OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.build();
private static String doPost(String url, String param) throws IOException {
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), param);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Accept-Type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
ResponseBody body = response.body();
return Objects.nonNull(body) ? body.string() : "";
}
return "";
}
同步请求整个流程如下图。
创建OkHttpClient
首先,需要创建一个OkHttpClient
,可使用OkHttpClient.Builder
内部类来构建。当采用new OkHttpClient()时,所有属性都使用默认值。
java
OkHttpClient的几个重要属性
// 调度器
final Dispatcher dispatcher;
// 网络代理
final @Nullable Proxy proxy;
// 连接器,默认为空
final List<Interceptor> interceptors;
// 事件监听器
final EventListener.Factory eventListenerFactory;
// 响应缓存
final Cache cache;
// 连接池
final ConnectionPool connectionPool;
// 连接失败是否重试,默认为TRUE
final boolean retryOnConnectionFailure;
// 总超时时间,包括connect, write, read在内,默认为0即无限制
final int callTimeout;
// 默认为10秒
final int connectTimeout;
// 默认为10秒
final int readTimeout;
// 默认为10秒
final int writeTimeout;
java
public OkHttpClient() {
this(new Builder());
}
RealCall同步执行和Interceptor实现
执行client.newCall(Request request)
,会调用RealCall.newRealCall()
创建RealCall
对象。
java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
java
final class RealCall implements Call {
final OkHttpClient client;
private Transmitter transmitter;
final Request originalRequest;
final boolean forWebSocket;
private boolean executed;
// 省略......
@Override
public Response execute() throws IOException {
// 防止并发
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
// 放入runningSyncCalls队列
client.dispatcher().executed(this);
// 通过拦截器链执行
return getResponseWithInterceptorChain();
} finally {
client.dispatcher().finished(this);
}
}
}
RealCall
中构建了InterceptorChain
,开始执行,在最后一个拦截器CallServerInterceptor
中发出网络请求。
java
Response getResponseWithInterceptorChain() throws IOException {
// 添加拦截器
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) {
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:补充请求头,处理gzip编码的响应
- CacheInterceptor:尝试从缓存中获取响应,没有是继续执行连接器链,并将响应缓存。使用文件系统存放数据。
- ConnectInterceptor:与目标主机建立Socket连接
- CallServerInterceptor:最后一个拦截器,发起网络请求,等待并构建响应。
Transmitter
OkHttp的应用程序层和网络层之间的桥梁。该类公开了高级应用层原语:连接、请求、响应和流。
Exchange
交换机,处理单个HTTP请求和响应,负责连接管理和事件,通过ExchangeCodec
处理网络I/O。
ExchangeCodec
编码请求、解码响应,支持HTTP/1.1、HTTP/2.0,处理网络I/O。
二、执行异步请求
下面是一个异步请求的示例,回调逻辑是Callback
接口的一个匿名内部类。
java
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class OkHttp3ClientTest {
public static OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.build();
public static void main(String[] args) throws IOException {
doAsyncGet("http://localhost:8010/user/queryAll");
}
private static void doAsyncGet(String url) {
Request request = new Request.Builder()
.url(url)
.get()
.addHeader("Accept-Type", "application/json")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
assert response.body() != null;
System.out.println(response.body().string());
}
});
}
}
异步请求整个流程如下图。
创建AsyncCall
通过RealCall.enqueue(Callback responseCallback)
,指定回调逻辑,构建AsyncCall
对象,启动异步请求,该方法无返回值。
java
@Override public void enqueue(Callback responseCallback) {
// 防止重复执行
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
// 封装AsyncCall,放入readyAsyncCalls队列
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
AsyncCall
间接实现了Runnable
接口,run()方法中调用了AsyncCall.execute()
,发起http请求也是调用上面看过的getResponseWithInterceptorChain()
,只是增加了成功或失败的回调。
scala
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
// 对某个host的调用计数
private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
// 省略......
@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);
}
} finally {
// 其中会将host并发计数减1
client.dispatcher().finished(this);
}
}
}
Dispatcher调度
该类用于调度异步请求,实现并发度控制。 Dispatcher.enqueue(AsyncCall call)
中,通过Dispatcher.promoteAndExecute()
触发消费readyAsyncCalls队列中的AsyncCall
。
每个Dispatcher
都持有一个线程池executorService
(使用SynchronousQueue),限制了并发执行的最大请求数为64(超过时将排队等候),限制对每个host的最大并发请求数为5。 因此,请求并发度控制,并不是通过线程池中线程数来限制,而是通过执行中的AsyncCall
数量来限制。
java
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
// 队列容量为0,不暂存任务。如果有空闲线程则复用,没有则创建新线程来处理任务
private ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
// 返回Dispatcher是否在运行中
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
// 筛选一些可执行的异步请求
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // 超过请求并发限制时,不会立即处理新的AsyncCall。
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // 超过host请求并发限制
i.remove();
// 对某个host的正在进行中的请求数+1
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
// 移动到runningAsyncCalls队列
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
// 遍历执行每个异步请求
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
// 向executorService提交AsyncCall
asyncCall.executeOn(executorService());
}
return isRunning;
}
// 省略......
}
当一个AsyncCall
执行结束时,会调用Dispatcher.finished()
方法从队列中移除该call,同时触发Dispatcher.promoteAndExecute()
继续消费readyAsyncCalls
队列。
下一篇中,我们一起来看看连接池、响应缓存等特性的实现。