OKHttp 文档篇

初略的看了Retrofit之后,OkHttp就是必不可少的了,Retrofit依赖了OkHttp,两个库都有一个Call这么一个类,其意图也一样,只是所处的层次不同。 Retrofit依赖OkHttp来实现真正的Http网络请求。而OkHttp相对来是更加复杂的,本文先把其文档过一遍。

Overview

OkHttp

OkHttp是一个实现HTTP协议的客户端,其高效性源于:

  • Http/2 若多个请求目标是一个主机,这几个请求可以共享一个Socket
  • 若是Http/2不支持,即服务端不支持,用连接池的降低请求延迟
  • 自带GZIP压缩数据
  • 对于重复请求,自带一个Response缓存避免无意义请求

当网络不好时,OkHttp支持自动重试。能帮助处理一些常见的问题, 比如服务有多个IP地址,若第一个IP失败,会自动切换到后续的IP尝试,这对于多个数据源的服务端来说很有用。
支持现代 TLS 特性,(TLS 1.3, ALPN, certificate pinning)// todo 了解下

OkHttp是易用的,其request/response可以用构造者模式创建,且其对象是不可变的(immutability)。
支持同步异步调用。

补充,不可变性的优势有
不用担心并发,既然该对象不可变,那么多个线程访问时,就不存在不同步问题。 免除副作用的影响,一个对象不可变,那么其使用过程中始终是一样的,假设使用两次,第二次结果保证和第一次一样。

Get a URL

1
2
3
4
5
6
7
8
9
10
11
12
13
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
// 构造者模式,按照需修改部分配置
Request request = new Request.Builder()
.url(url)
.build();

// 同步调用
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}

Post to a Server

发送http post请求时,携带实体数据body

http 格式包括 requestLine、header、body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}

Requirements

要求Android 5.0+,Java 8+

依赖了 OKio 和 Kotlin标准库,两者向后兼容都是令人放心的。

推荐保持该库最新版本,和浏览器一样,保持最新的http客户端可提升安全性。本库会跟上TLS的更新。

OkHttp 会使用平台自带的TLS的实现等等。

Call

Http client要做的事很清晰,接受一个request,产生一个response,使用Call来表示。

Rewriting request 重写一个请求

用户在使用OkHttp时,是在站在比较高得层次思考需求得,我们只是简单得描述一个http请求,而okHttp会补充一些额外得细节,使得请求 更加有效率。

对于 origin request,okHttp可能会添加 Content-Length, TransferEncoding, User-Agent, Host, Connection, Content-Type。 若是没有注明 Accept-Encoding,会依据Response添加合适的。
若存在Cookie,也会添加。

对于一些请求会使用缓存好的Response. 当服务端支持了,If-Modified-Since If-None-Match头部信息,当客户端存在缓存时,会发送一个 get 请求来获知本地的缓存是否可用,客户端携带着我何时 获取过这个信息,服务端判断是否需要返回更新的数据,假设没有更新,将返回一个没有body的 response,从而提高效率。

Rewriting Response 重写一个响应

若是透明压缩开启,OkHttp将会丢弃Content-Encoding 和 Content-Length,因为对用户来说,其得到的时解压后的body,而header返回的是压缩的body, 故其长度是不对的。 (个人疑问?为什么不把解压后的长度值更新到header中,然后返回给用户?)

Follow-up Request 自动跟随request

重定向,转发的区别? 重定向会返回一个新的地址给客户端,让其重新请求,客户端有感知 转发,则在服务器内部转发该请求,客户端无感知

对于重定向请求,服务器返回响应码为 302 ,okHttp会帮助重新请求新的地址,返回给最终结果给用户。这样,用户对重定向也无感知了~~

对于需要验证身份的请求,(响应码为401,身份未验证),OkHttp会自动从 Authenticator(需要自己配置),获取对应的凭证来验证身份。

Retrying Request 请求重试

有时连接失败了,可能时因为连接断开了,或服务端不可达,OkHttp会自动尝试不同的路由。

由于,rewrite, redirect, follow-ups 和 retries的存在,一个请求可能会在中间过程会有很多个 request和 response,而 OkHttp 使用Call来封装 这些过程,用户感觉到还是只有一个输入一个输出。

Call执行有两种方式:

  • 同步 sync: 调用线程阻塞等待知道结果可读。
  • 异步 async: 调用线程只是将请求入队列,传入的callback会在结果可用时执行。

Call 可以被任意线程取消,只要该请求完成都可以取消。
注意,当某一个线程被取消时,此时在写入reqeustBody或读取responseBody的代码都会抛出IO异常,设计如此。

Dispatch 分发调度

对于异步请求,如何管理同时并发的请求是个问题。创建过多的连接会方法资源,过少会导致请求延迟较大。

如何平衡空间与时间的问题,使用Dispatcher来设置,默认的连接池大小的,默认为5,最大值为64。

其原理与线程池基本是一致的。

Caching

OkHttp 提供了一个可选的缓存模块,默认关闭。 参照一些实用的设计,比如市面的浏览器。

Basic Usage

1
2
3
4
5
6
7
8
9
private val client: OkHttpClient = OkHttpClient.Builder()
.cache(Cache(
// 缓存文件路径
directory = File(application.cacheDir, "http_cache"),
// $0.05 worth of phone storage in 2020
// 最大的缓存大小
maxSize = 50L * 1024L * 1024L // 50 MiB
))
.build()

理想情况下,若是缓存命中,会跳过 DNS, 连接网络,下载数据包,

未命中,比如,未请求过该数据,或该数据不可缓存,依据返回来的时间判断缓存已经过期了。

Conditional Cache Hit, (条件命中?),需要请求网络来确定本地的缓存能够可用,通常是服务端返回一个304的状态告知缓存未修改,即依据客户端请求发来的 if-modified-since。

Connection

虽然用户只提供了一个URL来发送一个请求,但是OkHttp建立一个连接却需要三个要素: URL, Address, Route

URL

何为URL, Uniform Resource Locator,统一资源定位,比如一个 https://github.com/square/okhttp。

是抽象的

  • 只是简单区分非加密http和加密https,却没有指定使用哪种加密算法。既没有要求如何验证对方的证书(HostNameVerify),也没说哪些证书可以信任(SSLSocketFactory)。
  • 没有指明哪些代理服务应该使用,如何验证代理服务器。

也是具体的,一个URL指定了资源路径和查询参数,每个Web服务支持很多URL.

Address

地址指定了连接的是哪一个web服务,以及一些静态的配置,如端口,https的设置,偏好的协议(Http/2, SPDY)

SPDY是一个优化的Http协议,多路复用、请求优先级、报头压缩(http报头用的明文,这里用的是二进制)。不过,http/2推出后已经不建议使用了~

同一个地址的URL可以共享一个tcp socket连接,低延迟,高吞吐。
若是服务端支持http/1.x,那么其连接池会复用。

还是好好了解http 1.x > 2.0的变化吧

Routes