2019-11-04Okhttp源码解析-拦截器<一>
2019-11-04 本文已影响0人
猫KK
这里,来分析Okhttp中自己添加的拦截器
//方法可能会抛出异常
@Throws(IOException::class)
fun getResponseWithInterceptorChain(): Response {
//添加自定义拦截器
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)
.......
}
1. RetryAndFollowUpInterceptor
先来看第一个,请求失败重试,或者重定向拦截器
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
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) 循环来实现重试
while (true) {
//初始化,主要是初始化ExchangeFinder类
transmitter.prepareToConnect(request)
//如果取消了,直接抛出异常,终止循环
if (transmitter.isCanceled) {
throw IOException("Canceled")
}
var response: Response
var success = false
//通过try catch 来捕获请求出现的异常
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.
//请求失败,判断是否可以重连,如果可以,则continue然后再次请求
//否则抛出异常,终止请求
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.
//捕获IO异常,一样处理
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.
//这个值第一次为null,重定向请求时不为null
//不为null,说明为重定向,重新生成response
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)
//如果followUp为null,说明不需要重定向
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重新复制,通过死循环在次请求
request = followUp
priorResponse = response
}
}
通过上面,可以知道有两个方法比较关键
- recover():请求失败,是否可以再次请求
- followUpRequest():判断是否需要重定向
先来看第一个
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
}
//通过异常来判断是否需要重试
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) {
//socket 超时,不再重试
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
}
}
//SSLPeerUnverifiedException 异常
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
}
通过请求的异常等,判断是否需要再次重试。现在来看第二个方法,判断是否需要重定向
@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, route: Route?): Request? {
//获取请求码
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)
//308、307 需要重定向
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"
//如果请求方法不为GET、HEAD不进行重定向
if (method != "GET" && method != "HEAD") {
return null
}
//构建重定向的Request
return buildRedirectRequest(userResponse, method)
}
//300、301、302、303 情况,都进行重定向
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
}
//其他情况,进行重定向
else -> return null
}
}
通过上面,http请求码为300、301、302、303情况下需要重定向,308、307 情况下为GET、HEAD 方法需要重定向,来看如何构建重定向的Request
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// 是否允许重定向
if (!client.followRedirects) return null
//判断头信息中是否包含Location,需要重定向时Location的值为重定向的连接
val location = userResponse.header("Location") ?: return null
// Don't follow redirects to unsupported protocols.
//通过location 获取重定向的url
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()
}
判断是否需要重定向,是感觉http返回的状态码,和返回的头信息中是否包含Location,重定向的连接就是Location的值。如果需要重定向,则通过while(true)再次请求实现重定向
2. BridgeInterceptor
配置请求头信息
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
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) {
//添加Content-Type
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
//添加Content-Length
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) {
//添加Connection
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) {
//添加User-Agent
requestBuilder.header("User-Agent", userAgent)
}
//发起请求
val networkResponse = chain.proceed(requestBuilder.build())
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
//如果进行了gzip压缩 则解压
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()
}
这个拦截器主要是添加一些请求头信息,并判断是否进行了gzip压缩,如果压缩了,在返回数据中对返回数据解压