OkHttp
Square公司的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。从Android 4.4开始httpURLConnection的底层实现的Okhttp。
- 支持Http/2并允许对统一主机的所有请求共享一个套接字
- 通过连接池,减少了请求延迟
- 默认通过GZip压缩数据
- 响应缓存,避免了重复请求的网络
- 请求失败自动充实主机的其他ip,自动重定向
OkHttp从4.0开始采用Kotlin语言编写
OkHttp使用流程
首先先列举出Okhttp中重要的类
| 类 | 说明 |
|---|---|
| OkHttpClient | 客户端对象 |
| Request | 访问请求类,用于创建get、post等请求 |
| RequestBody | 请求数据类,在post、put、delete请求中用到 |
| Response | 网络请求的响应类 |
| MediaType | 数据类型,用来表明数据是json,image,pdf等一系列格式 |
| Call | 请求接口 |
| Call.execute() | 同步的请求方法 |
| Call.enqueue(Callback) | 异步的请求方法,不能在此进行UI更新操作 |
1.创建一个OkHttpClient客户端
方式一: 直接调用OkHttpClient构造函数的方式
OkHttpClient client = new OkHttpClient();
方式二: 调用OkHttpClient的Builder构建自定义参数的方式, 可以配置读写超时, 添加自定义拦截器, 配置缓冲等等
//采用建造者模式
client = new OkHttpClient.Builder()
.connectTimeout(10 * 1000, TimeUnit.MICROSECONDS) //连接超时阈值
.callTimeout(10 * 1000, TimeUnit.MILLISECONDS) //请求超时阈值
.writeTimeout(10 * 1000, TimeUnit.MILLISECONDS) //写超时阈值
.readTimeout(10 * 1000, TimeUnit.MICROSECONDS) //读超时阈值
.retryOnConnectionFailure(true) //失败重试
.cache(new Cache(new File(context.getCacheDir(), "OkHttpCache"), 10 * 1024 * 1024))//添加缓存
.addInterceptor(new LoggingInterceptor()) //Log日志拦截器
.addInterceptor(new CacheInterceptor()) //Cache缓存拦截器
.build();
2.创建一个请求
- 创建get请求
Request request = new Request.Builder()
.url(url)
.get()
.addHeader("token", "") //添加header
.build();
- 创建post请求
//创建MediaType
MediaType JSON = MediaType.get("application/json; charset=utf-8");
//创建一个RequestBody
RequestBody body = RequestBody.create("json字符串", JSON);
//创建请求
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("token", "") //添加header
.build();
- OkHttp还支持put、delete、patch请求类型,与post类型无多大差异,此处不讨论了
3.创建一个Call
Call call = client.newCall(request);
4.发起请求
发起一个同步请求
Response response = call.execute();
发起一个异步请求
//发起异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
}
});
5.获取请求响应
response.body();
OkHttp源码剖析
OkHttp内部执行流程大致如下图所示:
OkHttp经过传OkHttpClient.Builder() 参数的构造函数创建出OkHttpClient,同时Request经过传Request.Builder() 参数的构造函数创建出Request,然后OkHttpClient客户端通过newCall方法,传入Request请求实例,返回一个Call实例,Call在调用execute和enqueue方法后,底层经过了分发器、拦截器后完成整个请求,并返回了Response
OkHttpClient
OkhttpClient采用Builder设计模式,其内部管理了一个Builder类, 其内部维护了HTTP请求过程中大量的配置参数,如下:
class Builder constructor() {
//分发器
internal var dispatcher: Dispatcher = Dispatcher()
//
internal var connectionPool: ConnectionPool = ConnectionPool()
internal val interceptors: MutableList<Interceptor> = mutableListOf()
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
internal var retryOnConnectionFailure = true
internal var authenticator: Authenticator = Authenticator.NONE
internal var followRedirects = true
internal var followSslRedirects = true
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
internal var cache: Cache? = null
internal var dns: Dns = Dns.SYSTEM
internal var proxy: Proxy? = null
internal var proxySelector: ProxySelector? = null
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
internal var x509TrustManagerOrNull: X509TrustManager? = null
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
internal var certificateChainCleaner: CertificateChainCleaner? = null
internal var callTimeout = 0
internal var connectTimeout = 10_000
internal var readTimeout = 10_000
internal var writeTimeout = 10_000
internal var pingInterval = 0
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
internal var routeDatabase: RouteDatabase? = null
internal constructor(okHttpClient: OkHttpClient) : this() {
this.dispatcher = okHttpClient.dispatcher
this.connectionPool = okHttpClient.connectionPool
this.interceptors += okHttpClient.interceptors
this.networkInterceptors += okHttpClient.networkInterceptors
this.eventListenerFactory = okHttpClient.eventListenerFactory
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
this.authenticator = okHttpClient.authenticator
this.followRedirects = okHttpClient.followRedirects
this.followSslRedirects = okHttpClient.followSslRedirects
this.cookieJar = okHttpClient.cookieJar
this.cache = okHttpClient.cache
this.dns = okHttpClient.dns
this.proxy = okHttpClient.proxy
this.proxySelector = okHttpClient.proxySelector
this.proxyAuthenticator = okHttpClient.proxyAuthenticator
this.socketFactory = okHttpClient.socketFactory
this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
this.x509TrustManagerOrNull = okHttpClient.x509TrustManager
this.connectionSpecs = okHttpClient.connectionSpecs
this.protocols = okHttpClient.protocols
this.hostnameVerifier = okHttpClient.hostnameVerifier
this.certificatePinner = okHttpClient.certificatePinner
this.certificateChainCleaner = okHttpClient.certificateChainCleaner
this.callTimeout = okHttpClient.callTimeoutMillis
this.connectTimeout = okHttpClient.connectTimeoutMillis
this.readTimeout = okHttpClient.readTimeoutMillis
this.writeTimeout = okHttpClient.writeTimeoutMillis
this.pingInterval = okHttpClient.pingIntervalMillis
this.minWebSocketMessageToCompress = okHttpClient.minWebSocketMessageToCompress
this.routeDatabase = okHttpClient.routeDatabase
}
/**
* Sets the dispatcher used to set policy and execute asynchronous requests. Must not be null.
*/
fun dispatcher(dispatcher: Dispatcher) = apply {
this.dispatcher = dispatcher
}
.....
fun build(): OkHttpClient = OkHttpClient(this)
}
Request
Request request = new Request.Builder()
.url(url)
.get()
.addHeader("token", "") //添加header
.build();
Request采用Builder设计模式,其内部管理了一个Builder类, 其内部维护了四个变量,
- 请求url
- 请求方法method
- 请求头Headers.Builder
- 请求体RequestBody
同时,还提供了各种快捷方法
| 方法 | 说明 |
|---|---|
| Builder() | 默认构造函数,默认method为Get |
| Builder(request: Request) | 带Request的构造方法 |
| url(url: HttpUrl) | 配置url |
| url(url: String) | 配置url |
| url(url: URL) | 配置url |
| header(name: String, value: String) | 配置header |
| addHeader(name: String, value: String) | 配置header |
| removeHeader(name: String) | 配置header |
| headers(headers: Headers) | 配置header |
| cacheControl(cacheControl: CacheControl) | 配置缓存控制 |
| get() | get请求 |
| head() | |
| post(body: RequestBody) | post请求 |
| delete(body: RequestBody? = EMPTY_REQUEST) | delete请求 |
| put(body: RequestBody) | put请求 |
| patch(body: RequestBody) | patch请求 |
| method(method: String, body: RequestBody?) | method方法 |
| tag(tag: Any?) | 配置Tag |
| tag(type: Class, tag: T?) | 配置Tag |
| build() | 构建实例 |
如下:
open class Builder {
internal var url: HttpUrl? = null
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
/** A mutable map of tags, or an immutable empty map if we don't have any. */
internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()
constructor() {
this.method = "GET"
this.headers = Headers.Builder()
}
internal constructor(request: Request) {
this.url = request.url
this.method = request.method
this.body = request.body
this.tags = if (request.tags.isEmpty()) {
mutableMapOf()
} else {
request.tags.toMutableMap()
}
this.headers = request.headers.newBuilder()
}
open fun url(url: HttpUrl): Builder = apply {
this.url = url
}
/**
* Sets the URL target of this request.
*
* @throws IllegalArgumentException if [url] is not a valid HTTP or HTTPS URL. Avoid this
* exception by calling [HttpUrl.parse]; it returns null for invalid URLs.
*/
open fun url(url: String): Builder {
// Silently replace web socket URLs with HTTP URLs.
val finalUrl: String = when {
url.startsWith("ws:", ignoreCase = true) -> {
"http:${url.substring(3)}"
}
url.startsWith("wss:", ignoreCase = true) -> {
"https:${url.substring(4)}"
}
else -> url
}
return url(finalUrl.toHttpUrl())
}
/**
* Sets the URL target of this request.
*
* @throws IllegalArgumentException if the scheme of [url] is not `http` or `https`.
*/
open fun url(url: URL) = url(url.toString().toHttpUrl())
/**
* Sets the header named [name] to [value]. If this request already has any headers
* with that name, they are all replaced.
*/
open fun header(name: String, value: String) = apply {
headers[name] = value
}
/**
* Adds a header with [name] and [value]. Prefer this method for multiply-valued
* headers like "Cookie".
*
* Note that for some headers including `Content-Length` and `Content-Encoding`,
* OkHttp may replace [value] with a header derived from the request body.
*/
open fun addHeader(name: String, value: String) = apply {
headers.add(name, value)
}
/** Removes all headers named [name] on this builder. */
open fun removeHeader(name: String) = apply {
headers.removeAll(name)
}
/** Removes all headers on this builder and adds [headers]. */
open fun headers(headers: Headers) = apply {
this.headers = headers.newBuilder()
}
/**
* Sets this request's `Cache-Control` header, replacing any cache control headers already
* present. If [cacheControl] doesn't define any directives, this clears this request's
* cache-control headers.
*/
open fun cacheControl(cacheControl: CacheControl): Builder {
val value = cacheControl.toString()
return when {
value.isEmpty() -> removeHeader("Cache-Control")
else -> header("Cache-Control", value)
}
}
open fun get() = method("GET", null)
open fun head() = method("HEAD", null)
open fun post(body: RequestBody) = method("POST", body)
@JvmOverloads
open fun delete(body: RequestBody? = EMPTY_REQUEST) = method("DELETE", body)
open fun put(body: RequestBody) = method("PUT", body)
open fun patch(body: RequestBody) = method("PATCH", body)
open fun method(method: String, body: RequestBody?): Builder = apply {
require(method.isNotEmpty()) {
"method.isEmpty() == true"
}
if (body == null) {
require(!HttpMethod.requiresRequestBody(method)) {
"method $method must have a request body."
}
} else {
require(HttpMethod.permitsRequestBody(method)) {
"method $method must not have a request body."
}
}
this.method = method
this.body = body
}
/** Attaches [tag] to the request using `Object.class` as a key. */
open fun tag(tag: Any?): Builder = tag(Any::class.java, tag)
/**
* Attaches [tag] to the request using [type] as a key. Tags can be read from a
* request using [Request.tag]. Use null to remove any existing tag assigned for [type].
*
* Use this API to attach timing, debugging, or other application data to a request so that
* you may read it in interceptors, event listeners, or callbacks.
*/
open fun <T> tag(type: Class<in T>, tag: T?) = apply {
if (tag == null) {
tags.remove(type)
} else {
if (tags.isEmpty()) {
tags = mutableMapOf()
}
tags[type] = type.cast(tag)!! // Force-unwrap due to lack of contracts on Class#cast()
}
}
open fun build(): Request {
return Request(
checkNotNull(url) { "url == null" },
method,
headers.build(),
body,
tags.toImmutableMap()
)
}
}
Call
Call里定义了整个请求所需要的接口
interface Call : Cloneable {
//返回Request
fun request(): Request
//同步请求
@Throws(IOException::class)
fun execute(): Response
//异步请求
fun enqueue(responseCallback: Callback)
//取消请求
fun cancel()
//是否已经发送了请求
fun isExecuted(): Boolean
//是否取消了请求
fun isCanceled(): Boolean
//超时
fun timeout(): Timeout
public override fun clone(): Call
fun interface Factory {
fun newCall(request: Request): Call
}
}
Call的具体实现类是RealCall, RealCall主要属性及方法如下
-
构造函数
class RealCall( val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean ) : Call -
主要属性
//连接池 private val connectionPool: RealConnectionPool = client.connectionPool.delegate //Event监听器 internal val eventListener: EventListener = client.eventListenerFactory.create(this) private val timeout = object : AsyncTimeout() { override fun timedOut() { cancel() } }.apply { timeout(client.callTimeoutMillis.toLong(), MILLISECONDS) } private var callStackTrace: Any? = null private var exchangeFinder: ExchangeFinder? = null var connection: RealConnection? = null private set private var timeoutEarlyExit = false internal var interceptorScopedExchange: Exchange? = null private set private var requestBodyOpen = false private var responseBodyOpen = false private var expectMoreExchanges = true @Volatile private var canceled = false @Volatile private var exchange: Exchange? = null @Volatile var connectionToCancel: RealConnection? = null -
重写函数Request()
override fun request(): Request = originalRequest //构造函数里传入 -
重写函数execute()
override fun execute(): Response { check(executed.compareAndSet(false, true)) { "Already Executed" } timeout.enter() callStart() try { //调用Diapatcher,runningSyncCalls队列里添加任务,可见分发器里的同步请求 client.dispatcher.executed(this) //发起请求,在Okhttp中,不管是同步请求,还是异步请求,最终都会调用此方法发起请求 return getResponseWithInterceptorChain() } finally { client.dispatcher.finished(this) } } -
重写函数enqueue(responseCallback: Callback)
override fun enqueue(responseCallback: Callback) { check(executed.compareAndSet(false, true)) { "Already Executed" } callStart() //调用Diapatcher client.dispatcher.enqueue(AsyncCall(responseCallback)) } -
重写函数cancel()
override fun cancel() { if (canceled) return // Already canceled. canceled = true exchange?.cancel() connectionToCancel?.cancel() eventListener.canceled(this) } -
重写函数isExecuted()
override fun isExecuted(): Boolean = executed.get() -
重写函数isCanceled()
override fun isCanceled() = canceled -
重写函数timeout()
private val timeout = object : AsyncTimeout() { override fun timedOut() { cancel() } }.apply { timeout(client.callTimeoutMillis.toLong(), MILLISECONDS) } override fun timeout() = timeout -
内部类AsyncCall
internal inner class AsyncCall( private val responseCallback: Callback ) : Runnable { //同一Host的连接计数器 @Volatile var callsPerHost = AtomicInteger(0) private set //设置计数器 fun reuseCallsPerHostFrom(other: AsyncCall) { this.callsPerHost = other.callsPerHost } //请求的域名 val host: String get() = originalRequest.url.host //请求Request val request: Request get() = originalRequest //Call val call: RealCall get() = this@RealCall /** * Attempt to enqueue this async call on [executorService]. This will attempt to clean up * if the executor has been shut down by reporting the call as failed. * * */ fun executeOn(executorService: ExecutorService) { client.dispatcher.assertThreadDoesntHoldLock() var success = false try { //线程池执行任务 executorService.execute(this) success = true } catch (e: RejectedExecutionException) { val ioException = InterruptedIOException("executor rejected") ioException.initCause(e) noMoreExchanges(ioException) responseCallback.onFailure(this@RealCall, ioException) } finally { if (!success) { client.dispatcher.finished(this) // This call is no longer running! } } }
Okhttp分发器
Dispatcher 分发器就是来调配请求任务的,内部会包含一个线程池,管理线程资源和分配。可以在创建 OkHttpClient 时,传递我们 自己定义的线程池来创建分发器。其主要属性有
//异步请求同时存在的最大请求数,注意是异步不包括同步的。
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
//异步请求同一域名同时存在的最大请求数,注意是异步不包括同步的。
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
private var executorServiceOrNull: ExecutorService? = null
//异步请求使用的线程池
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
//corePoolSize = 0
//maximumPoolSize = Int.MAX_VALUE
//keepAliveTime = 60s
//workQueue = SynchronousQueue()
//非守护线程
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
//闲置任务(没有请求时候可执行一些任务,由使用者设置)
@set:Synchronized
@get:Synchronized
var idleCallback: Runnable? = null
//异步请求等待执行的队列
/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
//异步请求正在执行队列
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
//同步请求正在执行的队列
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()
-
同步请求
/** Used by [Call.execute] to signal it is in-flight. */ @Synchronized internal fun executed(call: RealCall) { //直接把Call请求加入同步队列 runningSyncCalls.add(call) } -
异步请求
internal fun enqueue(call: AsyncCall) { synchronized(this) { //1.将AsyncCall加入等待执行队列 readyAsyncCalls.add(call) // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (!call.call.forWebSocket) { //查找是否已经存在至少一个相同Host的AsyncCall对象,并且返回最先查到的AsyncCall val existingCall = findExistingCallWithHost(call.host) //如果已经存在,就把之前AsyncCall对象的计数器也设置给当前的AsyncCall对象 if (existingCall != null) call.reuseCallsPerHostFrom(existingCall) } } //3. promoteAndExecute() } // private fun promoteAndExecute(): Boolean { this.assertThreadDoesntHoldLock() //创建空的可执行AsyncCall集合 val executableCalls = mutableListOf<AsyncCall>() //是否运行状态 val isRunning: Boolean //同步锁 synchronized(this) { //对异步请求正在执行队列进行迭代循环 val i = readyAsyncCalls.iterator() while (i.hasNext()) { //拿到一个asyncCall val asyncCall = i.next() //如果正在请求的队列数量大于最大请求数,如果是就跳出迭代 if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity. //判断同一Host的连接计数器的值是否大于maxRequestsPerHost,如果是就跳出迭代循环 if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity. //从readyAsyncCalls移除 i.remove() //同一Host的连接计数器自增1 asyncCall.callsPerHost.incrementAndGet() //添加到可执行集合中 executableCalls.add(asyncCall) //添加到正在执行的任务队列中====该asyncCall对象已经是被当作执行中状态的了 runningAsyncCalls.add(asyncCall) } //fun runningCallsCount(): Int = runningAsyncCalls.size + runningSyncCalls.size //如果正在执行Call数量>0,isRunning为真 isRunning = runningCallsCount() > 0 } //遍历可执行集合 for (i in 0 until executableCalls.size) { val asyncCall = executableCalls[i] //调用asyncCall.executeOn方法 asyncCall.executeOn(executorService) } return isRunning } //查找是否已经存在至少一个相同Host的AsyncCall对象,并且返回最先查到的AsyncCall private fun findExistingCallWithHost(host: String): AsyncCall? { for (existingCall in runningAsyncCalls) { if (existingCall.host == host) return existingCall } for (existingCall in readyAsyncCalls) { if (existingCall.host == host) return existingCall } return null } -
finished
当同步或者异步网络请求结束之后,会将该call从对应的队列中删除
/** Used by [AsyncCall.run] to signal completion. */ internal fun finished(call: AsyncCall) { //同一Host连接计数器递减 1 call.callsPerHost.decrementAndGet() finished(runningAsyncCalls, call) } /** Used by [Call.execute] to signal completion. */ internal fun finished(call: RealCall) { finished(runningSyncCalls, call) } private fun <T> finished(calls: Deque<T>, call: T) { val idleCallback: Runnable? synchronized(this) { //从指定队列中移除call if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!") idleCallback = this.idleCallback } val isRunning = promoteAndExecute() if (!isRunning && idleCallback != null) { idleCallback.run() } }
Okhttp拦截器
拦截器采用了责任链设计模式,这样的好处是将请求的发送和处理分开,而且能够动态添加中间的处理方实现对请求的处理等操作
Okhttp 五大拦截器
-
RetryAndFollowUpInterceptor
重试及重定向拦截器在交给下一个拦截器之前,负责判断用户是否取消了请求;
在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
-
BridgeInterceptor
桥接拦截器在交给下一个拦截器之前,负责将HTTP协议必备的请求头加入其中,比如Host,并添加一些默认的行为,比如GZIP压缩;
在获得了结果后,调用保存cookie接口并解析GZIP数据。
-
CacheInterceptor
缓存拦截器在交给下一个拦截器之前,读取并判断是否使用缓存;
获得结果后判断是否使用缓存。
-
ConnectInterceptor
连接拦截器在交给下一个拦截器之前,负责找到或者新建一个连接,并获得相应的socket流;
在获得结果后不进行额外处理
-
CallServerInterceptor
请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据
OkHttp 五大拦截器完成整个http请求的流程如下:
拦截器
- 在OKHttp中,我们发出的请求并不是直接连接到服务器获取结果,而是由Okhttp中拦截器截获我们发出的请求,对请求进行观察、修改等操作,然后返回结果
- 在OkHttp中,Interceptor是一个接口,如下,它定义了intercept(Chain)函数来实现拦截功能,Chain接口定义了一系列方法,其中最重要的是proceed函数
fun interface Interceptor{
@Throws(IOException::class)
fun intercept(chain: Chain): Response
companion object {
inline operator fun invoke(crossinline block: (chain: Chain) -> Response):Interceptor = Interceptor { block(it) }
}
interface Chain {
fun request(): Request
@Throws(IOException::class)
fun proceed(request: Request): Response
fun connection(): Connection?
fun call(): Call
fun connectTimeoutMillis(): Int
fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain
fun readTimeoutMillis(): Int
fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain
fun writeTimeoutMillis(): Int
fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
}
}
当拦截器链开始工作时,取出第一个拦截器,并实例化一个新的拦截器,执行intercept() 方法,如果拦截器需要下一个拦截器获取响应,那就通过参数拿到拦截器链再次执行proceed() 方法,这里取出第二个拦截器,并继续调用该拦截器的 intercept方法,并以此类推,直到获取响应。
拦截器接口的基本使用套路如下:
//自定义一个Logging拦截器
public class LoggingInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
//这个chain里面包含了request和response,所以你要什么都可以从这里拿
//1. 获取Request
Request request = chain.request();
//1.x 处理Request
long t1 = System.nanoTime();//请求发起的时间
Log.i("Leo", String.format("发送请求 %s on %s%n%s", request.url(), chain.connection(), request.headers()));
//2. 获取Response
Response response = chain.proceed(request);
//2.x 处理Response
long t2 = System.nanoTime();//收到响应的时间
//这里不能直接使用response.body().string()的方式输出日志
//因为response.body().string()之后,response中的流会被关闭,程序会报错,我们需要创建出一
//个新的response给应用层处理
ResponseBody responseBody = response.peekBody(1024 * 1024);
Log.i("Leo", String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s",
response.request().url(),
responseBody.string(),
(t2 - t1) / 1e6d,
response.headers()));
//3.返回Response
return response;
}
}
在讲五大拦截器之前,先介绍下接口Chain的具体实现类:RealInterceptorChain,如下
//实例化位置 RealCall->getResponseWithInterceptorChain->
//val chain = RealInterceptorChain(
// call = this,
// interceptors = interceptors,
// index = 0,
// exchange = null,
// request = originalRequest,
// connectTimeoutMillis = client.connectTimeoutMillis,
// readTimeoutMillis = client.readTimeoutMillis,
// writeTimeoutMillis = client.writeTimeoutMillis
)
class RealInterceptorChain(
internal val call: RealCall,
private val interceptors: List<Interceptor>, //拦截器列表,装载了所有拦截器,其中五大拦截器在后面
private val index: Int, //拦截器列表索引
internal val exchange: Exchange?,
internal val request: Request, //封装的请求对象
internal val connectTimeoutMillis: Int, //连接超时时间
internal val readTimeoutMillis: Int, //读超时时间
internal val writeTimeoutMillis: Int //写超时时间
) : Interceptor.Chain {
//proceed方法执行次数
private var calls: Int = 0
//copy一份实例
internal fun copy(
index: Int = this.index,
exchange: Exchange? = this.exchange,
request: Request = this.request,
connectTimeoutMillis: Int = this.connectTimeoutMillis,
readTimeoutMillis: Int = this.readTimeoutMillis,
writeTimeoutMillis: Int = this.writeTimeoutMillis
) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
readTimeoutMillis, writeTimeoutMillis)
//
override fun connection(): Connection? = exchange?.connection
//
override fun connectTimeoutMillis(): Int = connectTimeoutMillis
//
override fun withConnectTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }
return copy(connectTimeoutMillis = checkDuration("connectTimeout", timeout.toLong(), unit))
}
override fun readTimeoutMillis(): Int = readTimeoutMillis
override fun withReadTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }
return copy(readTimeoutMillis = checkDuration("readTimeout", timeout.toLong(), unit))
}
override fun writeTimeoutMillis(): Int = writeTimeoutMillis
override fun withWriteTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain {
check(exchange == null) { "Timeouts can't be adjusted in a network interceptor" }
return copy(writeTimeoutMillis = checkDuration("writeTimeout", timeout.toLong(), unit))
}
override fun call(): Call = call
override fun request(): Request = request
@Throws(IOException::class)
override fun proceed(request: Request): Response {
//1.检查当前索引是否合法
check(index < interceptors.size)
//2.记录本方法使用次数
calls++
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// Call the next interceptor in the chain.
// 创建新的拦截器链对象RealInterceptorChain,并将index+1
val next = copy(index = index + 1, request = request)
// 获取下一个拦截器
val interceptor = interceptors[index]
// 执行拦截器的intercept方法获取结果,并将next拦截器链对象传入
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException("interceptor $interceptor returned null")
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
}
RetryAndFollowUpInterceptor
重试及重定向拦截器 创建请求时,如果没有自定义拦截器,那第一个拦截器就是该拦截器
该拦截器作用是 连接失败后进行重试,对请求结果跟进后进行重定向
异常有
- RouteException
- IOException
- ProtocolException 协议异常
- FileNotFoundException
- InterruptedIOException 中断异常
- SSLHandshakeException SSL 握手异常
- CertificateException
- SSLPeerUnverifiedException SSL握手未授权异常
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 获取拦截器链
val realChain = chain as RealInterceptorChain
// 获取请求request
var request = chain.request
// 获取realCall
val call = realChain.call
// 重复次数
var followUpCount = 0
// 前一个响应
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
//=============================================
try {
// 处理响应
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
//RouteException 路由异常, 抛出异常,则检测连接是否还可以继续
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
//继续循环
continue
} catch (e: IOException) {
//IOException IO异常, 和服务端建立连接失败
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
//继续循环
continue
}
//=============================================
// Attach the prior response if it exists. Such responses never have a body.
//如果存在,请附上先前的响应。 这样的反应永远不会有实体。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
//重定向次数不能超过MAX_FOLLOW_UPS,MAX_FOLLOW_UPS默认值是20
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
// closeExchange
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* `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 fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure) return false
// We can't send the request body again.
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false
// No more routes to attempt.
if (!call.retryAfterFailure()) return false
// For failure recovery, use the same route selector with a new connection.
return true
}
private fun requestIsOneShot(e: IOException, userRequest: Request): Boolean {
val requestBody = userRequest.body
return (requestBody != null && requestBody.isOneShot()) || e is FileNotFoundException
}
private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
// If there was a protocol problem, don't recover.
if (e is ProtocolException) {
return false
}
// If there was an interruption don't recover, but if there was a timeout connecting to a route
// we should try the next route (if there is one).
if (e is InterruptedIOException) {
return e is SocketTimeoutException && !requestSendStarted
}
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
// again with a different route.
if (e is SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
if (e.cause is CertificateException) {
return false
}
}
if (e is SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
return false
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true
}
/**
* Figures out the HTTP request to make in response to receiving [userResponse]. This will
* either add authentication headers, follow redirects or handle a client request timeout. If a
* follow-up is either unnecessary or not applicable, this returns null.
*/
@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
//407
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
//401
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
//重定向
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
//408
HTTP_CLIENT_TIMEOUT -> {
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure) {
// The application layer has directed us not to retry the request.
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
//503
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request
}
return null
}
//421
HTTP_MISDIRECTED_REQUEST -> {
// OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
// RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
// we can retry on a different connection.
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
//重定向操作
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// Does the client allow redirects?
if (!client.followRedirects) return null
val location = userResponse.header("Location") ?: return null
// Don't follow redirects to unsupported protocols.
val url = userResponse.request.url.resolve(location) ?: return null
// If configured, don't follow redirects between SSL and non-SSL.
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
// Most redirects don't include a request body.
val requestBuilder = userResponse.request.newBuilder()
if (HttpMethod.permitsRequestBody(method)) {
val responseCode = userResponse.code
val maintainBody = HttpMethod.redirectsWithBody(method) ||
responseCode == HTTP_PERM_REDIRECT ||
responseCode == HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
requestBuilder.method("GET", null)
} else {
val requestBody = if (maintainBody) userResponse.request.body else null
requestBuilder.method(method, requestBody)
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding")
requestBuilder.removeHeader("Content-Length")
requestBuilder.removeHeader("Content-Type")
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
private fun retryAfter(userResponse: Response, defaultDelay: Int): Int {
val header = userResponse.header("Retry-After") ?: return defaultDelay
// https://tools.ietf.org/html/rfc7231#section-7.1.3
// currently ignores a HTTP-date, and assumes any non int 0 is a delay
if (header.matches("\d+".toRegex())) {
return Integer.valueOf(header)
}
return Integer.MAX_VALUE
}
companion object {
/**
* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox,
* curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
*/
private const val MAX_FOLLOW_UPS = 20
}
}
//禁止重定向,交给用户自己操作
new OkHttpClient().newBuilder()
.followRedirects(false) //禁止OkHttp的重定向操作,我们自己处理重定向
.followSslRedirects(false)//https的重定向也自己处理
总结
- 本拦截器是五大拦截器中的第一个,首次接触到Request、最后一次接触到Response,该拦截器主要作用是判断是否需要重试和重定向
- 重试的前提是出现了RouteException与IOException,一旦出现该两个异常,就会通过recover方法进行判断是否需要重试
- 重定向f安生在重试之后,如果不满足重试的条件,还需要进一步调用 followUpRequest 根据Response的响应码,如果直接请求失败,Response都不存在就会抛出异常,如果遇到3xx,就需要调用重定向方法buildRedirectRequest
- 重定向最大发生20次,MAX_FOLLOW_UPS=20
BridgeInterceptor
BridgeInterceptor连接应用程序和服务器的桥梁,我们发出的请求会经过该拦截器的处理才能发给服务器。其主要作用如下
- 在request阶段配置请求头,cookie信息等
- 在response阶段,处理并保存返回的cookie信息,判断是否需要gzip解压
桥接拦截器所处理的操作一般有设置请求内容的长度,编码,gzip压缩,cookie等,获取响应后保存Cookie等操作
| 请求头 | 说明 |
|---|---|
| Content-type | 请求体类型,application/json |
| Content-Length/Transfer-Encoding | 请求头解析方式,内容编码/传输编码 Content-Encoding 通常用于对实体内容进行压缩编码,目的是优化传输,例如用 gzip 压缩文本文件,能大幅减小体积。 Transfer-Encoding ???? |
| Host | 请求的主机站点 |
| Connection:Keep-Alive | 保持长连接 |
| Accept-Encoding:gzip | 接收响应支持gzip压缩 |
| Cookie | cookie身份辨别 |
| User-Agent | 请求的用户信息,如操作系统、浏览器等等 |
其主要操作流程如下
- 先从chain中获取request,然后从request中获取body
- 从body中获取contentType,如果不为空就设置Content-Type请求头类型
- 从body中获取body内容长度contentLength,如果contentLength=-1,就添加Content-Length,移除Transfer-Encoding,反之,就添加Transfer-Encoding,移除Content-Length
- 配置Host请求头
- 配置Connection请求头
- 配置Accept-Encoding头为gzip压缩
- 配置Cookie头
- 配置User-Agent头
- 处理并保存返回的cookie信息
- 判断是否需要gzip解压
//桥接拦截器
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// 获取Request
val userRequest = chain.request()
// 获取Request的Builder
val requestBuilder = userRequest.newBuilder()
// 获取请求体
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
//设置请求体类型Content-Type
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
//Content-Length和Transfer-Encoding互斥
if (contentLength != -1L) {
//内容编码
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
//传输编码
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
// 设置请求主机站点 Host
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
// 设置保持长连接
requestBuilder.header("Connection", "Keep-Alive")
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
// 接收响应支持gzip压缩
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
//设置cookie
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
//设置用户请求信息,okhttp的userAgent是"okhttp/${OkHttp.VERSION}"
requestBuilder.header("User-Agent", userAgent)
}
///////////////////////////////////////
// 获取响应
val networkResponse = chain.proceed(requestBuilder.build())
//响应header, 如果没有自定义配置cookieJar==null,则什么都不做
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
//判断服务器是否支持gzip压缩格式,如果支持则交给okio压缩
if (transparentGzip && "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
/** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */
private fun cookieHeader(cookies: List<Cookie>): String = buildString {
cookies.forEachIndexed { index, cookie ->
if (index > 0) append("; ")
append(cookie.name).append('=').append(cookie.value)
}
}
}
总结
- 桥接拦截器对用户构建的Request 进行添加或者删除相关头部信息,以转换为能够真正进行网络请求的Request,将符合网络请求规范的Request交给下一个拦截器处理
- 获取Response,如果响应体经过了GZIP压缩,那就需要解压,再构建成用户可用的Response并返回
CacheInterceptor
缓存拦截器 主要是通过一个缓存策略来判断当前是使用网络请求的response还是缓存的response,判断流程如下
-
判断是否配置了缓存(Cache对象需要在OkhtpClient的Builder中手动配置)如果配置缓存,则通过request获取到对应的response缓存
new OkHttpClient.Builder() .cache(new Cache(new File(context.getCacheDir(), "OkHttpCache"), 10 * 1024 * 1024))//添加缓存 -
创建缓存策略类,用于处理怎么使用缓存
-
如果当前不允许使用网络,而且缓存为空,则返回状态码为504的response
-
如果不能使用网络,但是有缓存,则直接返回缓存
-
如果使用网络,则调用下一个拦截器处理并返回对应的response对象
-
如果当前缓存和网络都可用,则判断当前网络response的状态码,如果状态码为304(表示内容没有变化),则使用缓存,否则使用网络
-
如果当前有配置缓存(Cache对象),则缓存response(只能缓存GET请求)
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 如果配置了缓存,则通过request获取Response缓存,默认cache为空
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// 创建缓存策略类,用于处理怎么使用缓存
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
// 如果不使用网络,则 networkRequest为 null
val networkRequest = strategy.networkRequest
// 如果不使用缓存,则 cacheResponse为 null
val cacheResponse = strategy.cacheResponse
// 更新统计指标, 增加命中率
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body?.closeQuietly()
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 如果当前不允许使用网络,而且缓存为空,则返回状态码为504的response
// HTTP_GATEWAY_TIMEOUT = 504
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// If we don't need the network, we're done.
// 如果不能使用网络,但是有缓存,则直接返回缓存
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
// 使用网络,则调用下一个拦截器处理并返回对应的response对象
var networkResponse: Response? = null
try {
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) {
cacheCandidate.body?.closeQuietly()
}
}
// If we have a cache response too, then we're doing a conditional get.
// 在发送了request的情况下,如果当前缓存可用,则判断当前网络response的状态码,如果状态码为304(表示内容没有变化),则使用缓存,否则使用网络
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
//304 请求内容并没有改变,使用缓存
val 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.also {
listener.cacheHit(call, it)
}
} else {
// 关闭缓存流
cacheResponse.body?.closeQuietly()
}
}
// 使用网络请求得到的response
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
// 缓存网络response
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
// 只能缓存GET请求,不是则移除request
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
// 返回response
return response
}
/**
* Returns a new source that writes bytes to [cacheRequest] as they are read by the source
* consumer. This is careful to discard bytes left over when the stream is closed; otherwise we
* may never exhaust the source stream and therefore not complete the cached response.
*/
@Throws(IOException::class)
private fun cacheWritingResponse(cacheRequest: CacheRequest?, response: Response): Response {
// Some apps return a null body; for compatibility we treat that like a null cache request.
if (cacheRequest == null) return response
val cacheBodyUnbuffered = cacheRequest.body()
val source = response.body!!.source()
val cacheBody = cacheBodyUnbuffered.buffer()
val cacheWritingSource = object : Source {
private var cacheRequestClosed = false
@Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead: Long
try {
bytesRead = source.read(sink, byteCount)
} catch (e: IOException) {
if (!cacheRequestClosed) {
cacheRequestClosed = true
cacheRequest.abort() // Failed to write a complete cache response.
}
throw e
}
if (bytesRead == -1L) {
if (!cacheRequestClosed) {
cacheRequestClosed = true
cacheBody.close() // The cache response is complete!
}
return -1
}
sink.copyTo(cacheBody.buffer, sink.size - bytesRead, bytesRead)
cacheBody.emitCompleteSegments()
return bytesRead
}
override fun timeout() = source.timeout()
@Throws(IOException::class)
override fun close() {
if (!cacheRequestClosed &&
!discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
cacheRequestClosed = true
cacheRequest.abort()
}
source.close()
}
}
val contentType = response.header("Content-Type")
val contentLength = response.body.contentLength()
return response.newBuilder()
.body(RealResponseBody(contentType, contentLength, cacheWritingSource.buffer()))
.build()
}
companion object {
private fun stripBody(response: Response?): Response? {
return if (response?.body != null) {
response.newBuilder().body(null).build()
} else {
response
}
}
/** Combines cached headers with a network headers as defined by RFC 7234, 4.3.4. */
private fun combine(cachedHeaders: Headers, networkHeaders: Headers): Headers {
val result = Headers.Builder()
for (index in 0 until cachedHeaders.size) {
val fieldName = cachedHeaders.name(index)
val value = cachedHeaders.value(index)
if ("Warning".equals(fieldName, ignoreCase = true) && value.startsWith("1")) {
// Drop 100-level freshness warnings.
continue
}
if (isContentSpecificHeader(fieldName) ||
!isEndToEnd(fieldName) ||
networkHeaders[fieldName] == null) {
result.addLenient(fieldName, value)
}
}
for (index in 0 until networkHeaders.size) {
val fieldName = networkHeaders.name(index)
if (!isContentSpecificHeader(fieldName) && isEndToEnd(fieldName)) {
result.addLenient(fieldName, networkHeaders.value(index))
}
}
return result.build()
}
/**
* Returns true if [fieldName] is an end-to-end HTTP header, as defined by RFC 2616,
* 13.5.1.
*/
private fun isEndToEnd(fieldName: String): Boolean {
return !"Connection".equals(fieldName, ignoreCase = true) &&
!"Keep-Alive".equals(fieldName, ignoreCase = true) &&
!"Proxy-Authenticate".equals(fieldName, ignoreCase = true) &&
!"Proxy-Authorization".equals(fieldName, ignoreCase = true) &&
!"TE".equals(fieldName, ignoreCase = true) &&
!"Trailers".equals(fieldName, ignoreCase = true) &&
!"Transfer-Encoding".equals(fieldName, ignoreCase = true) &&
!"Upgrade".equals(fieldName, ignoreCase = true)
}
/**
* Returns true if [fieldName] is content specific and therefore should always be used
* from cached headers.
*/
private fun isContentSpecificHeader(fieldName: String): Boolean {
return "Content-Length".equals(fieldName, ignoreCase = true) ||
"Content-Encoding".equals(fieldName, ignoreCase = true) ||
"Content-Type".equals(fieldName, ignoreCase = true)
}
}
}
ConnectInterceptor
连接拦截器,打开与目标服务器的连接,正式开启网络请求,并执行下一个拦截器
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
class Exchange(
internal val call: RealCall,
internal val eventListener: EventListener,
internal val finder: ExchangeFinder,
private val codec: ExchangeCodec
) {
/** Returns true if the request body need not complete before the response body starts. */
internal var isDuplex: Boolean = false
private set
internal val connection: RealConnection = codec.connection
internal val isCoalescedConnection: Boolean
get() = finder.address.url.host != connection.route().address.url.host
@Throws(IOException::class)
fun writeRequestHeaders(request: Request) {
try {
eventListener.requestHeadersStart(call)
codec.writeRequestHeaders(request)
eventListener.requestHeadersEnd(call, request)
} catch (e: IOException) {
eventListener.requestFailed(call, e)
trackFailure(e)
throw e
}
}
@Throws(IOException::class)
fun createRequestBody(request: Request, duplex: Boolean): Sink {
this.isDuplex = duplex
val contentLength = request.body!!.contentLength()
eventListener.requestBodyStart(call)
val rawRequestBody = codec.createRequestBody(request, contentLength)
return RequestBodySink(rawRequestBody, contentLength)
}
@Throws(IOException::class)
fun flushRequest() {
try {
codec.flushRequest()
} catch (e: IOException) {
eventListener.requestFailed(call, e)
trackFailure(e)
throw e
}
}
@Throws(IOException::class)
fun finishRequest() {
try {
codec.finishRequest()
} catch (e: IOException) {
eventListener.requestFailed(call, e)
trackFailure(e)
throw e
}
}
fun responseHeadersStart() {
eventListener.responseHeadersStart(call)
}
@Throws(IOException::class)
fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
try {
val result = codec.readResponseHeaders(expectContinue)
result?.initExchange(this)
return result
} catch (e: IOException) {
eventListener.responseFailed(call, e)
trackFailure(e)
throw e
}
}
fun responseHeadersEnd(response: Response) {
eventListener.responseHeadersEnd(call, response)
}
@Throws(IOException::class)
fun openResponseBody(response: Response): ResponseBody {
try {
val contentType = response.header("Content-Type")
val contentLength = codec.reportedContentLength(response)
val rawSource = codec.openResponseBodySource(response)
val source = ResponseBodySource(rawSource, contentLength)
return RealResponseBody(contentType, contentLength, source.buffer())
} catch (e: IOException) {
eventListener.responseFailed(call, e)
trackFailure(e)
throw e
}
}
@Throws(IOException::class)
fun trailers(): Headers = codec.trailers()
@Throws(SocketException::class)
fun newWebSocketStreams(): RealWebSocket.Streams {
call.timeoutEarlyExit()
return codec.connection.newWebSocketStreams(this)
}
fun webSocketUpgradeFailed() {
bodyComplete(-1L, responseDone = true, requestDone = true, e = null)
}
fun noNewExchangesOnConnection() {
codec.connection.noNewExchanges()
}
fun cancel() {
codec.cancel()
}
/**
* Revoke this exchange's access to streams. This is necessary when a follow-up request is
* required but the preceding exchange hasn't completed yet.
*/
fun detachWithViolence() {
codec.cancel()
call.messageDone(this, requestDone = true, responseDone = true, e = null)
}
private fun trackFailure(e: IOException) {
finder.trackFailure(e)
codec.connection.trackFailure(call, e)
}
fun <E : IOException?> bodyComplete(
bytesRead: Long,
responseDone: Boolean,
requestDone: Boolean,
e: E
): E {
if (e != null) {
trackFailure(e)
}
if (requestDone) {
if (e != null) {
eventListener.requestFailed(call, e)
} else {
eventListener.requestBodyEnd(call, bytesRead)
}
}
if (responseDone) {
if (e != null) {
eventListener.responseFailed(call, e)
} else {
eventListener.responseBodyEnd(call, bytesRead)
}
}
return call.messageDone(this, requestDone, responseDone, e)
}
fun noRequestBody() {
call.messageDone(this, requestDone = true, responseDone = false, e = null)
}
/** A request body that fires events when it completes. */
private inner class RequestBodySink(
delegate: Sink,
/** The exact number of bytes to be written, or -1L if that is unknown. */
private val contentLength: Long
) : ForwardingSink(delegate) {
private var completed = false
private var bytesReceived = 0L
private var closed = false
@Throws(IOException::class)
override fun write(source: Buffer, byteCount: Long) {
check(!closed) { "closed" }
if (contentLength != -1L && bytesReceived + byteCount > contentLength) {
throw ProtocolException(
"expected $contentLength bytes but received ${bytesReceived + byteCount}")
}
try {
super.write(source, byteCount)
this.bytesReceived += byteCount
} catch (e: IOException) {
throw complete(e)
}
}
@Throws(IOException::class)
override fun flush() {
try {
super.flush()
} catch (e: IOException) {
throw complete(e)
}
}
@Throws(IOException::class)
override fun close() {
if (closed) return
closed = true
if (contentLength != -1L && bytesReceived != contentLength) {
throw ProtocolException("unexpected end of stream")
}
try {
super.close()
complete(null)
} catch (e: IOException) {
throw complete(e)
}
}
private fun <E : IOException?> complete(e: E): E {
if (completed) return e
completed = true
return bodyComplete(bytesReceived, responseDone = false, requestDone = true, e = e)
}
}
/** A response body that fires events when it completes. */
internal inner class ResponseBodySource(
delegate: Source,
private val contentLength: Long
) : ForwardingSource(delegate) {
private var bytesReceived = 0L
private var invokeStartEvent = true
private var completed = false
private var closed = false
init {
if (contentLength == 0L) {
complete(null)
}
}
@Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long {
check(!closed) { "closed" }
try {
val read = delegate.read(sink, byteCount)
if (invokeStartEvent) {
invokeStartEvent = false
eventListener.responseBodyStart(call)
}
if (read == -1L) {
complete(null)
return -1L
}
val newBytesReceived = bytesReceived + read
if (contentLength != -1L && newBytesReceived > contentLength) {
throw ProtocolException("expected $contentLength bytes but received $newBytesReceived")
}
bytesReceived = newBytesReceived
if (newBytesReceived == contentLength) {
complete(null)
}
return read
} catch (e: IOException) {
throw complete(e)
}
}
@Throws(IOException::class)
override fun close() {
if (closed) return
closed = true
try {
super.close()
complete(null)
} catch (e: IOException) {
throw complete(e)
}
}
fun <E : IOException?> complete(e: E): E {
if (completed) return e
completed = true
// If the body is closed without reading any bytes send a responseBodyStart() now.
if (e == null && invokeStartEvent) {
invokeStartEvent = false
eventListener.responseBodyStart(call)
}
return bodyComplete(bytesReceived, responseDone = true, requestDone = false, e = e)
}
}
}
总结
该拦截器作用是为了获取一份与目标服务器的连接,在这个连接上进行HTTP数据的收发
CallServerInterceptor
该拦截器主要作用是向服务器发送请求,最终返回Response
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
val sentRequestMillis = System.currentTimeMillis()
// 写入请求头
exchange.writeRequestHeaders(request)
var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != 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.
// 判断是否有请求体
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
//询问是否有请求体
exchange.flushRequest() //刷新请求
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
if (responseBuilder == null) {
//
if (requestBody.isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
//写入请求
exchange.flushRequest()
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.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.
exchange.noNewExchangesOnConnection()
}
}
} else {
exchange.noRequestBody()
}
// 结束请求
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var 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 status.
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
}
response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code
}
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
// 如果请求头中 Connection对应的值为 close,则关闭连接
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
return response
}
}
OkHttp面试题汇总
- OKHttp有哪些拦截器,分别起什么作⽤
- OkHttp怎么实现连接池
- OkHttp⾥⾯⽤到了什么设计模式
- Okhttp是怎么复用的,线程池