相关文章:
Android进阶宝典 -- OkHttp3源码分析(Kotlin版本分发器和拦截器)
在之前的文章中,详细介绍了OkHttp网络请求的主流程分析,包括基本的使用以及同步/异步网络请求逻辑,那么本节将会继续深入OkHttp拦截器,看请求是如何与服务器完成通信的。
1 五大拦截器
首先我们先看下,异步请求时拦截器处理逻辑:
kotlin
fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (transmitter.isCanceled) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw transmitter.noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null)
}
}
}
除去自定义的拦截器,剩余的拦截器有:RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor
,他们被统一放在了一个Interceptor集合中,并被封装成了RealInterceptorChain对象,并执行了proceed方法。
java
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
// Call the next interceptor in the chain.
// 主要就是将index++;
val next = RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
// 取出当前位置的拦截器
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
// 执行拦截器的intercept方法,同时传入下一个拦截器
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
// Confirm that the next interceptor made its required call to chain.proceed().
check(exchange == null || 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
}
在proceed方法中,首先构建了一个新的RealInterceptChain对象next
,唯一的变化就是index做了自增处理,同时取出当前index的拦截器,执行了其intercept
方法,同时把next
作为参数传进去。
假如当前拦截器为RetryAndFollowUpInterceptor
,那么在RetryAndFollowUpInterceptor
中,会执行intercept
方法,在执行proceed方法后,会拿到下一个拦截器BridgeInterceptor
并执行其intercept
方法。
如此一来,每个拦截器都会按顺序执行,同时把response从底层回传到上层。
2 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor
是重试/重定向拦截器,在intercept方法中,我们看到当前拦截器并没有对请求做任何处理,直接传递到下一级拦截器中,所以RetryAndFollowUpInterceptor
做的主要工作就是处理异常重试。
kotlin
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val realChain = chain as RealInterceptorChain
val transmitter = realChain.transmitter()
var followUpCount = 0
var priorResponse: Response? = null
while (true) {
transmitter.prepareToConnect(request)
if (transmitter.isCanceled) {
throw IOException("Canceled")
}
var response: Response
var success = false
try {
response = realChain.proceed(request, transmitter, null)
success = true
} catch (e: RouteException) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, transmitter, false, request)) {
throw e.firstConnectException
}
continue
} catch (e: IOException) {
// An attempt to communicate with a server failed. The request may have been sent.
val requestSendStarted = e !is ConnectionShutdownException
if (!recover(e, transmitter, requestSendStarted, request)) throw e
continue
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException()
}
}
// 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 = response.exchange
val route = exchange?.connection()?.route()
//重定向的判断
val followUp = followUpRequest(response, route)
// 如果为空,说明不需要重定向
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
transmitter.timeoutEarlyExit()
}
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
return response
}
response.body?.closeQuietly()
if (transmitter.hasExchange()) {
exchange?.detachWithViolence()
}
// 超出重定向的次数也不可以,直接抛异常。
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
}
}
其重试逻辑就是通过while死循环,来捕获后续的请求过程中的异常,因此我们需要关注catch住的两个异常,分别是为RouteException
和IOException
。
2.1 可以重试的条件
当捕获到异常的时候,并不代表一定会发起重试,这里面会细分很多的场景。
例如捕获到了RouteException
,在recover
方法中会判断是否支持重试,如果不支持重试,那么就直接抛出异常交给上层处理。
kotlin
private fun recover(
e: IOException,
transmitter: Transmitter,
requestSendStarted: Boolean,
userRequest: Request
): 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 (!transmitter.canRetry()) return false
// For failure recovery, use the same route selector with a new connection.
return true
}
那么什么场景下不支持重试呢?
- OkHttpClinet初始化时,
retryOnConnectionFailure
属性设置为false,此时不允许错误重试,默认情况下为true,支持重试。 - 当使用post请求时,请求体类型不支持重试;
- 当发生协议错误、Socket超时、SSL证书错误时,不支持重试;
- 当没有多余的路由时,不支持重试。
2.2 重定向处理
当请求成功,拿到response之后,会调用followUpRequest
方法判断是否需要重定向,如果返回的request为空,则不需要重定向,直接返回响应即可。
如果返回的request不为空,则代表需要进行重定向,需要拿到重定向的request再次向服务端发起请求,但重定向的次数有限,默认是20次。
kotlin
private fun followUpRequest(userResponse: Response, route: Route?): Request? {
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
// 307 308
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT -> {
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (method != "GET" && method != "HEAD") {
return null
}
return buildRedirectRequest(userResponse, method)
}
// 300 - 30x
HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// ......
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 maintainBody = HttpMethod.redirectsWithBody(method)
if (HttpMethod.redirectsToGet(method)) {
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()
}
判断重定向是通过响应code,如果是30x开头的状态码,那么就需要从响应头的Location
字段中取出重定向的地址,并重新构造新的请求。
3 BridgeInterceptor
BridgeInterceptor
相对来说比较简单,主要是补全请求头的参数,这里会在请求头中声明响应体要用gzip压缩,同时在拿到响应之后,通过gzip解压。
在之前的文章中介绍过,gzip能够最大化的压缩数据,同时提高传输效率。
kotlin
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
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) {
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
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
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()
}
4 CacheInterceptor
CacheInterceptor
是OkHttp中与缓存相关的拦截器,用于存储一次请求的响应数据,并持久化存储在本地。这里要注意,缓存只有GET请求才有缓存,因为是拿请求的url作为key。
kotlin
OkHttpClient.Builder()
.cache(Cache(File("xxx"), 24 * 1024 * 1024))
.retryOnConnectionFailure(false)
.build()
如果想要使用缓存,那么必须在OkHttpClient初始化时声明,否则默认cache字段为空。
kotlin
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
//从缓存中拿数据
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
// 策略对象1 - 网络请求
val networkRequest = strategy.networkRequest
// 策略对象2 - 响应缓存
val cacheResponse = strategy.cacheResponse
cache?.trackResponse(strategy)
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.
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT) //直接504
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
}
// If we don't need the network, we're done.
//直接用缓存中的数据
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build()
}
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.
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
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
} else {
cacheResponse.body?.closeQuietly()
}
}
// cacheResponse为空,但是networkRequest不为空
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response)
}
// 如果是post请求,那么就直接将缓存移除
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
}
4.1 用缓存还是发起网络请求?
当我们从缓存中拿到数据之后,我们可以直接用吗?当然不可以,如果接口的数据没有发生变化,那么可以直接用,如果接口的数据发生变化,那么就不能直接用缓存的数据。所以如何判断需要从缓存中拿数据,还是从网络拿数据,就需要CacheStrategy
中的两个成员变量来看。
kotlin
class CacheStrategy internal constructor(
/** The request to send on the network, or null if this call doesn't use the network. */
val networkRequest: Request?,
/** The cached response to return or validate; or null if this call doesn't use a cache. */
val cacheResponse: Response?
)
如果networkRequest
为null,那么就不需要发起网络请求;如果cacheResponse
为null,那么就不能用缓存中的数据;反之可以使用。
- 如果
networkRequest
为空,且cacheResponse
也为空,那么此次请求失败,直接返回504的响应code,在RetryAndFollowUpInterceptor
拦截器中处理异常。 - 如果
networkRequest
为空,且cacheResponse
不为空,那么就直接将缓存中的数据返回即可。 - 如果
networkRequest
不为空,且cacheResponse
为空,那么就需要使用网络请求拿到的数据,并将响应数据加到缓存中。 - 如果
networkRequest
不为空,且cacheResponse
不为空,那么就发起请求,此时返回的响应code可能为304(与上次请求的数据无变化),那么就更新缓存直接返回即可。
4.2 如何判断用缓存还是网络数据
这个问题,就需要看CacheStrategy
的内部逻辑。
kotlin
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
// No cached response.
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
// Drop the cached response if it's missing a required handshake.
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// If this response shouldn't have been stored, it should never be used as a response source.
// This check should be redundant as long as the persistence store is well-behaved and the
// rules are constant.
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
val responseCaching = cacheResponse.cacheControl
val ageMillis = cacheResponseAge()
var freshMillis = computeFreshnessLifetime()
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
var maxStaleMillis: Long = 0
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection "Response is stale"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection "Heuristic expiration"")
}
return CacheStrategy(null, builder.build())
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
val conditionName: String
val conditionValue: String?
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
else -> return CacheStrategy(request, null) // No condition! Make a regular request.
}
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
return CacheStrategy(conditionalRequest, cacheResponse)
}
这部分需要对https的缓存非常熟悉,包括请求头中的If-None-Match
、If-Modified-Since
,来判断缓存是否有效,以及判断服务器的数据是否发生了改变,来决定缓存是否可以使用,如果有精力的伙伴可以对缓存这一部分进行详细的了解。
5 ConnectInterceptor
ConnectInterceptor
的主要作用就是与目标服务器建立连接,这里我们就需要了解OkHttp中关于连接池的相关原理性的问题。
kotlin
/** Opens a connection to the target server and proceeds to the next interceptor. */
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val request = realChain.request()
val transmitter = realChain.transmitter()
// We need the network to satisfy this request. Possibly for validating a conditional GET.
val doExtensiveHealthChecks = request.method != "GET"
val exchange = transmitter.newExchange(chain, doExtensiveHealthChecks)
return realChain.proceed(request, transmitter, exchange)
}
}
5.1 连接池以及复用
为什么需要连接池,上层在使用https请求时,底层的基础协议依然是tcp协议,需要三次握手和四次挥手,如果每次请求完毕都关闭连接,那么下次请求需要重新握手,效率太低,因此OkHttp中采用了连接池复用的方案解决此问题。
kotlin
class ConnectionPool(
maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit
) {
internal val delegate = RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit)
constructor() : this(5, 5, TimeUnit.MINUTES)
/** Returns the number of idle connections in the pool. */
fun idleConnectionCount(): Int = delegate.idleConnectionCount()
/** Returns total number of connections in the pool. */
fun connectionCount(): Int = delegate.connectionCount()
/** Close and remove all idle connections in the pool. */
fun evictAll() {
delegate.evictAll()
}
}
OkHttp中的连接池为ConnectionPool
,外部通过delegate来使用,默认最大的空闲连接数为5,连接时长为5分钟。
kotlin
fun put(connection: RealConnection) {
assert(Thread.holdsLock(this))
if (!cleanupRunning) {
cleanupRunning = true
executor.execute(cleanupRunnable)
}
connections.add(connection)
}
在每次往连接池中加入连接的时候,都会执行清理任务,首先会循环遍历连接池中的集合,看空闲的连接和正在使用的连接的个数,并且会找到一个空闲时间最久的连接。
java
fun cleanup(now: Long): Long {
var inUseConnectionCount = 0
var idleConnectionCount = 0
var longestIdleConnection: RealConnection? = null
var longestIdleDurationNs = Long.MIN_VALUE
// Find either a connection to evict, or the time that the next eviction is due.
synchronized(this) {
for (connection in connections) {
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++
continue
}
idleConnectionCount++
// If the connection is ready to be evicted, we're done.
val idleDurationNs = now - connection.idleAtNanos
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs
longestIdleConnection = connection
}
}
when {
longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections -> {
// We've found a connection to evict. Remove it from the list, then close it below
// (outside of the synchronized block).
connections.remove(longestIdleConnection)
}
idleConnectionCount > 0 -> {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs
}
inUseConnectionCount > 0 -> {
// All connections are in use. It'll be at least the keep alive duration 'til we run
// again.
return keepAliveDurationNs
}
else -> {
// No connections, idle or in use.
cleanupRunning = false
return -1
}
}
}
longestIdleConnection!!.socket().closeQuietly()
// Cleanup again immediately.
return 0
}
遍历完成之后,会判断空闲时间最久的这个链接是否超过了5min(最长空闲时间),或者判断空闲连接个数是否超过了5个,如果是就直接移除,并关闭socket。
当需要从连接池中取出连接的时候,需要判断此次请求的域名、路由之类的属性是否一致,如果保持一致,那么就可以复用连接池中的连接。
kotlin
fun transmitterAcquirePooledConnection(
address: Address,
transmitter: Transmitter,
routes: List<Route>?,
requireMultiplexed: Boolean
): Boolean {
assert(Thread.holdsLock(this))
for (connection in connections) {
if (requireMultiplexed && !connection.isMultiplexed) continue
if (!connection.isEligible(address, routes)) continue
transmitter.acquireConnectionNoEvents(connection)
return true
}
return false
}
如果没有匹配到,那么就可以新建一个RealConnection连接,放到连接池中。
6 CallServerInterceptor
这是责任链的最后一环,在ConnectInterceptor
中与目标服务器建立socket连接后(无论是从连接池中拿到的,还是新建的),在CallServerInterceptor
中就是将请求发送到服务器,拿到服务端返回的响应,往回抛。
kotlin
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 responseHeadersStarted = false
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()
responseHeadersStarted = true
exchange.responseHeadersStart()
responseBuilder = exchange.readResponseHeaders(true)
}
// 收到了服务端100的响应
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 (!responseHeadersStarted) {
exchange.responseHeadersStart()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(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
response = exchange.readResponseHeaders(false)!!
.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()
}
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
}
}
6.1 大容量请求体处理
当我们使用大容量请求体的时候,会在请求头中添加字段:Expect :100-continue
,此时会先把请求头发送出去,那么此时如果服务端返回了100的响应,
kotlin
override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
val headers = stream!!.takeHeaders()
val responseBuilder = readHttp2HeadersList(headers, protocol)
// 如果响应头code 为100,那么返回null
return if (expectContinue && responseBuilder.code == HTTP_CONTINUE) {
null
} else {
responseBuilder
}
}
那么就继续将请求体发送过去即可;如果服务端不允许,那么就直接返回了响应体,那么就把响应体当做response返回给上层即可。
总结
以上就是OkHttp的5大拦截器,RetryAndFollwupInterceptor
作为护城河,帮助我们try-catch请求过程中的异常,并发起重试,保证请求的可靠性和稳定性,同时对于重定向的请求,会在内部处理并重新请求;BridgeInterceptor
会帮助我们填补遗漏的请求头参数;CacheInterceptor
会帮助缓存Http GET请求响应数据,并提供了详细的缓存策略告知我们是否可以使用缓存,还是需要重新从服务端发起请求,一定程度上提高了响应速度;ConnectInterceptor
则是利用了OkHttp中的连接池,与目标服务器建立了socket连接,在可复用的基础上提高了请求的效率;CallServerInterceptor
是责任链的最后一环,将请求发送到服务端,拿到服务器的响应,同时处理了大请求体的请求策略。