HTTP客户端
java.net
包中提供了一些基本的方法,使用过 http 协议来访问网络资源,但是操作起来比较繁琐不够灵活,因此诞生了一些优秀的HTTP客户端工具,包括 Apache HttpClient、OkHttp,在笔者接触过的项目里又以 OkHttp 使用更加广泛,所以本次重点介绍 OkHttp
OkHttp
关于 OkHttp 这里直接贴官网介绍(https://square.github.io/okhttp/)
- HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.OkHttp is an HTTP client that’s efficient by default:
- HTTP/2 support allows all requests to the same host to share a socket.
- Connection pooling reduces request latency (if HTTP/2 isn’t available).
- Transparent GZIP shrinks download sizes.
- Response caching avoids the network completely for repeat requests
如何使用OkHttp
这里依然贴官网 demo(https://square.github.io/okhttp/recipes/) 同步调用
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0; i < responseHeaders.size(); i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
}
异步调用
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
}
OkHttp实例创建
从 demo 里可以得知使用 OkHttp 之前需要 new 一个 OkHttpClient,至于如何创建,OkHttp 实际上提供了 Builder 方式进行个性化的配置,一份比较完整的配置通常是这样的(配置里的参数使用的是自带的默认值,OkHttp 版本3.11.0):
public OkHttpClient buildOkHttpClient() {
ExecutorService executor = new ThreadPoolExecutor(50, 200, 30, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
Dispatcher dispatcher = new Dispatcher(executor);
dispatcher.setMaxRequests(64);
dispatcher.setMaxRequestsPerHost(5);
return new OkHttpClient.Builder()
.connectTimeout(10_000, TimeUnit.MILLISECONDS)
.readTimeout(10_000, TimeUnit.MILLISECONDS)
.writeTimeout(10_000, TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
.dispatcher(dispatcher)
.build();
}
Dispatcher,直接查看源码注释
Policy on when async requests are executed. Each dispatcher uses an {@link ExecutorService} to run calls internally. If you supply your own executor, it should be able to run {@linkplain #getMaxRequests the configured maximum} number of calls concurrently.
概括来说就是异步调用时 OkHttp 底层实际会使用一个默认的线程池,这个线程池源码为:
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;
}
最大线程数为 Integer.MAX_VALUE,这个是不是太危险了?不过异步调用的时候实际是会经过以下方法的:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
也就是在往这个线程池提交任务的时候是会先做判断的,这里涉及到 maxRequests 和 maxRequestsPerHost 两个配置参数,见名知意,maxRequests 是允许最大的并发数,maxRequestsPerHos 是每个调用域名允许最大的并发数,超过配置的并发数则进入一个就绪队列,后面细节就不具体展开讲,感兴趣可以查看网友的源码分析:https://www.jianshu.com/p/6166d28983a2
ConnectionPool 是指创建的连接池,用源码的注释来说
Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that share the same {@link Address} may share a {@link Connection}. This class implements the policy of which connections to keep open for future use
如果要复用 HTTP 连接,那么可以通过 maxIdleConnections 进行控制,即允许最大的空闲连接数,这个连接数只针对同一个host有效,并非针对总的连接数,需要注意的是当 response header 头里如果返回Connection: close
,那么是无法复用连接的,不仅笔者踩过这个坑,也有网友踩过这个坑,具体可见具:https://stackoverflow.com/questions/41011287/why-okhttp-doesnt-reuse-its-connections
而大部分人关注的更多的是 OkHttp 的几个超时时间:
connectTimeout
底层 socket 连接建立的超时时间,当连接 IP 不可达的情况下,需要等待很长一段时间(默认时长)
readTimeout
读超时时间,Source(类似 InputStream)的超时时间,这个时间也是大家普遍认为的第三方接口调用超时时间
writeTimeout
写超时时间, Sink(类似 OutputStream)超时时间,从远程获得数据后再往 socket 写的时间,这个耗时通常非常少,笔者人为打了断点才模拟触发
总结
- dispachter 异步调用时才有用,通过 maxRequests (允许最大的并发数),maxRequestsPerHos (每个调用域名允许最大的并发数)来进行并发控制
- connectTimeout 与远程 host 建立连接的超时时间
- readTimeout 通常意义上的从远程接口获取到数据的超时时间
- writeTimeout 获得数据后往 socket 写的超时时间
- connectionPool 用于管理HTTP连接,同一个 host 达到复用的效果降低开销,通过maxIdleConnections 控制同一个 host 允许的最大空闲连接数
- response header头里返回
Connection: close
,那么是无法复用连接的