Okhttp中类作用简介
Okhttp中可直接调用的类
包含Okhttp的请求流程中使用到的类,以及可以在OkhttpClient中自定义的实现类。
HttpUrl
Okhttp中解析Url得到的数据,包括以下几种数据:
- scheme : 协议名称,如http、https。
- username、password : 登录信息,用于身份认证。
- host : 服务器地址
- port : 服务器端口号,http端口默认80,https端口默认443.
- pathSegments : 带层次的文件路径,指服务器上的文件路径
- queryNamesAndValues : 查询字符串,根据key=value拼接的参数。
- fragment : 片段标识符,用来标记以获取资源中的子资源。
Address
表示连接到服务器的规范。对于一个简单的连接来说它包括服务端的IP和端口。对于一个显示代理的请求,它还包括一些代理的信息。对于一个安全的连接来说,它还包括SSL套接字工厂、主机名验证和证书处理器。 Address中包含以下信息:
final HttpUrl url;
final Dns dns;
final SocketFactory socketFactory;
final Authenticator proxyAuthenticator;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议(SSL/TLS)
final ProxySelector proxySelector;
final Proxy proxy;
final SSLSocketFactory sslSocketFactory; //安全套层socket工厂 用于https
final HostnameVerifier hostnameVerifier; //主机名字确认
final CertificatePinner certificatePinner; //证书链
Request
封装HTTP请求的各种参数,包括以下五大类:
final HttpUrl url; //请求的连接,包含协议、ip、端口、账户密码等
final String method; //请求的方式 ,支持Get、Head、Post、Put、Delete、Patch、Options
final Headers headers; //请求的头部
final RequestBody body; //请求体
final Object tag; //对象标记,用于删除请求
Response
Dns
域名解析系统,将主机名解析成对应的ip地址,Okhttp默认提供了一个DNS实现,调用系统的方法获取对应的ip列表,我们也可以继承Dns接口主动实现。
private class DnsSystem : Dns {
override fun lookup(hostname: String): List<InetAddress> {
try {
return InetAddress.getAllByName(hostname).toList()
} catch (e: NullPointerException) {
throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply {
initCause(e)
}
}
}
}
Dispatcher
分发器,用于执行同步或者异步的请求。
对于同步的请求,会在Dispatcher的同步队列中插入该条请求,然后再调用执行。执行完毕后,会调用Dispatcher的finished方法删除该条请求。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
对于异步请求,Okhttp中定义了默认的最大请求数为64,对于同一ip的地址来说,最大并发数为5,当然这些都是可以修改的。所以在Dispatcher中会使用两条队列来记录请求:
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
runningAsyncCalls:运行队列用来存储一条请求,并通过线程池开启一个空闲线程来执行。
readyAsyncCalls:准备队列用来存储超出规定容量的请求。
可以替换Okhttp中默认的Dispatcher对象,创建一个类继承自DisPatcher:
OkHttpClient.Builder().dispatcher(Dispatcher())
CookieJar
Okhttp默认不提供存储Cookie的操作,需要我们实现CookieJar接口来保存Cookies。
OkHttpClient.Builder().cookieJar(CookieJar.NO_COOKIES)
Authenticator
响应来自远程服务器或代理服务器的身份认证质疑。当服务器需要用户代理的认证信息时,会通过返回401或407来通知用户代理。当用户代理在接受到401或407响应码时,需要将认证信息添加到Headers中。例如当我们初次登入一个网站时,有时会弹出认证弹窗需要我们输入账号密码,这就是服务端返回401,需要我们添加认证信息也就是账号密码。
Okhttp中默认给的一个返回null的认证信息,具体的实现需要我们手动加入认证信息,例子如下:
//服务端认证
class HttpAuthenticator : okhttp3.Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val credential = Credentials.basic("test_name","test-pwd")
return response.request.newBuilder().addHeader("Authorization",credential).build()
}
}
//代理服务器认证
class ProxyAuthenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val credential = Credentials.basic("test_name", "test-pwd")
return response.request.newBuilder().addHeader("Proxy-Authorization", credential).build()
}
}
401: 服务端返回需要用户身份认证信息。
在Okhttp中会调用OkhttpClient中的authenticator对象来生成一个带有header("Authorization",credential)的头部。
RetryAndFollowUpInterceptor拦截器中在收到401时,会判断是否进行重试操作
407: 代理服务器需要认证信息。
在Okhttp中会调用OkhttpClient中的proxyAuthenticator对象来生成一个带有header("Proxy-Authorization",credential)的头部。
ConnectInterceptor拦截器在创建隧道时,如果收到407时,会调用该对象进行重新连接
通过上述的再次构建一个Request请求并发送。
Cache
OKhttp中有一层拦截器CacheInterceptor
用来处理缓存的请求和响应。在OkhttpClient中可以设置缓存的路径大小,默认是不缓存任何响应的。
如果要设置缓存的话,我们首先在OkhttpClient中定义一个Cache对象,并替换Okhttp中的null对象,需要指定缓存的路径,和缓存的大小,内部采用DiskLruCache来实现文件的缓存。
val okHttpClient = OkHttpClient.Builder()
.cache(Cache(application.cacheDir,10*1024*1024))
.build()
在Request中我们还可以配置缓存策略,来决定Request是否采用网络还是缓存。
val cacheCOntrol = CacheControl.Builder()
.noCache()
.noStore()
.onlyIfCached()
.maxAge(2,TimeUnit.SECONDS)
.maxStale(2,TimeUnit.SECONDS)
.minFresh(2,TimeUnit.SECONDS)
.build()
val request: Request = Request.Builder()
.cacheControl(CacheControl.FORCE_CACHE)
.cacheControl(cacheCOntrol)
.build()
CertificatePinner
通过在客户端内部指定host的证书,可以达到锁定证书,防止中间人攻击的风险。
# 小写主机名或通配符模式(如*.example.com)。
# SHA-256或SHA-1哈希。每个pin都是证书主题公钥信息的散列,以base64编码,前缀为sha256/或sha1/。
val certificatePinner = CertificatePinner.Builder()
.add("hostname","SHA1..........")
.build()
val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
注意: 使用certificatePinner时,可能会存在证书过期和证书切换的问题,因为证书是写在客户端的。
SocketFactory
Okhttp中实现了SocketFactory
的DefaultSocketFactory
类去创建一个Socket套接字,实现HTTP的连接。
SSLSocketFactory
安全套接层Socket,用于HTTPS。
HostnameVerifier
主机名验证,在握手期间,可以通过改接口来返回是否允许该连接。
public interface HostnameVerifier {
public boolean verify(String hostname, SSLSession session);
}
Okhttp中默认实现了一个OkHostnameVerifier
类。
ConnectionPool
管理HTTP和HTTP/2连接的重用,以减少网络延迟。请求相同的地址的Request可以共用一个连接。 Okhttp中默认的最大空闲连接池为5,存活时间为5分钟。
Okhttp中内部类
包括ExchangeFinder、Exchange、ExchangeCodec、RealConnection、路由和代理等。
RealConnectionPool
默认的连接池实现。在ConnectInterceptor拦截器中创建连接时,会优先从连接池中查找是否有可用的连接,当没有可用的时,会企图创建一个连接并加入到连接池中。
RealConnection
实现了Connection接口,代表一条具体的连接。
EventListener
监听事件。
ExchangeFinder
获取一条安全可靠的连接,来携带请求。 在RetryAndFollowUpInterceptor中会负责创建ExchangeFinder对象。
//在RetryAndFollowUpInterceptor中调用以下方法
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
/**
* 调用RealCall的enterNetworkInterceptorExchange方法
* newExchangeFinder只有当不属于重试的情况下才为true
*/
fun enterNetworkInterceptorExchange(request: Request, newExchangeFinder: Boolean) {
check(interceptorScopedExchange == null)
......
if (newExchangeFinder) {
this.exchangeFinder = ExchangeFinder(
connectionPool,
createAddress(request.url),
this,
eventListener
)
}
}
Exchange||ExchangeCodec
编解码接口类,编码HTTP请求和解码HTTP响应。 在ConnectInterceptor拦截器中会负责创建一个Exchange和ExchangeCodec对象。
/**
* 调用RealCall的initExchange方法来创建一个个Exchange和ExchangeCodec对象,
* 用来携带即将到来的请求和响应。
*/
val exchange = realChain.call.initExchange(chain)
internal fun initExchange(chain: RealInterceptorChain): Exchange {
......
val exchangeFinder = this.exchangeFinder!!
val codec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
synchronized(this) {
this.requestBodyOpen = true
this.responseBodyOpen = true
}
if (canceled) throw IOException("Canceled")
return result
}
调用RealCall的exchangeFinder对象的find方法返回一个ExchangeCodec对象的同时,会调用findHealthyConnection方法获取一个安全的连接,其实就是获取一个RealConnection对象,同时将我在OkhttpClient对象中设置的各种超时参数传递进去。
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure(e.lastConnectException)
throw e
} catch (e: IOException) {
trackFailure(e)
throw RouteException(e)
}
}
最后调用RealConnection的newCodec方法获取一个可用的ExchangeCodec对象。由于ExchangeCodec是一个接口,因此具体的实现是Http1ExchangeCodec和Http2ExchangeCodec。
internal fun newCodec(client: OkHttpClient, chain: RealInterceptorChain): ExchangeCodec {
val socket = this.socket!!
val source = this.source!!
val sink = this.sink!!
val http2Connection = this.http2Connection
return if (http2Connection != null) {
Http2ExchangeCodec(client, this, chain, http2Connection)
} else {
socket.soTimeout = chain.readTimeoutMillis()
source.timeout().timeout(chain.readTimeoutMillis.toLong(), MILLISECONDS)
sink.timeout().timeout(chain.writeTimeoutMillis.toLong(), MILLISECONDS)
Http1ExchangeCodec(client, this, source, sink)
}
}