OkHttp实现原理
以下是 OkHttp 同步与异步请求的完整流程示意图,涵盖了从 RealCall 创建到 Dispatcher 分发,再经过拦截器链处理直至返回响应的核心步骤:
#mermaid-svg-SbUAGjCDbNByTEnE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SbUAGjCDbNByTEnE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SbUAGjCDbNByTEnE .error-icon{fill:#552222;}#mermaid-svg-SbUAGjCDbNByTEnE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SbUAGjCDbNByTEnE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SbUAGjCDbNByTEnE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SbUAGjCDbNByTEnE .marker.cross{stroke:#333333;}#mermaid-svg-SbUAGjCDbNByTEnE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SbUAGjCDbNByTEnE p{margin:0;}#mermaid-svg-SbUAGjCDbNByTEnE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SbUAGjCDbNByTEnE .cluster-label text{fill:#333;}#mermaid-svg-SbUAGjCDbNByTEnE .cluster-label span{color:#333;}#mermaid-svg-SbUAGjCDbNByTEnE .cluster-label span p{background-color:transparent;}#mermaid-svg-SbUAGjCDbNByTEnE .label text,#mermaid-svg-SbUAGjCDbNByTEnE span{fill:#333;color:#333;}#mermaid-svg-SbUAGjCDbNByTEnE .node rect,#mermaid-svg-SbUAGjCDbNByTEnE .node circle,#mermaid-svg-SbUAGjCDbNByTEnE .node ellipse,#mermaid-svg-SbUAGjCDbNByTEnE .node polygon,#mermaid-svg-SbUAGjCDbNByTEnE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SbUAGjCDbNByTEnE .rough-node .label text,#mermaid-svg-SbUAGjCDbNByTEnE .node .label text,#mermaid-svg-SbUAGjCDbNByTEnE .image-shape .label,#mermaid-svg-SbUAGjCDbNByTEnE .icon-shape .label{text-anchor:middle;}#mermaid-svg-SbUAGjCDbNByTEnE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SbUAGjCDbNByTEnE .rough-node .label,#mermaid-svg-SbUAGjCDbNByTEnE .node .label,#mermaid-svg-SbUAGjCDbNByTEnE .image-shape .label,#mermaid-svg-SbUAGjCDbNByTEnE .icon-shape .label{text-align:center;}#mermaid-svg-SbUAGjCDbNByTEnE .node.clickable{cursor:pointer;}#mermaid-svg-SbUAGjCDbNByTEnE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SbUAGjCDbNByTEnE .arrowheadPath{fill:#333333;}#mermaid-svg-SbUAGjCDbNByTEnE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SbUAGjCDbNByTEnE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SbUAGjCDbNByTEnE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SbUAGjCDbNByTEnE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SbUAGjCDbNByTEnE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SbUAGjCDbNByTEnE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SbUAGjCDbNByTEnE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SbUAGjCDbNByTEnE .cluster text{fill:#333;}#mermaid-svg-SbUAGjCDbNByTEnE .cluster span{color:#333;}#mermaid-svg-SbUAGjCDbNByTEnE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SbUAGjCDbNByTEnE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SbUAGjCDbNByTEnE rect.text{fill:none;stroke-width:0;}#mermaid-svg-SbUAGjCDbNByTEnE .icon-shape,#mermaid-svg-SbUAGjCDbNByTEnE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SbUAGjCDbNByTEnE .icon-shape p,#mermaid-svg-SbUAGjCDbNByTEnE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SbUAGjCDbNByTEnE .icon-shape .label rect,#mermaid-svg-SbUAGjCDbNByTEnE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SbUAGjCDbNByTEnE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SbUAGjCDbNByTEnE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SbUAGjCDbNByTEnE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同步
异步
条件满足
条件不满足
拦截器链 (Interceptor Chain)
RetryAndFollowUpInterceptor
重试与重定向
BridgeInterceptor
请求/响应桥接
CacheInterceptor
缓存处理
ConnectInterceptor
建立连接
CallServerInterceptor
与服务器通信
用户调用 client.newCall(request)
创建 RealCall 对象
请求类型?
调用 call.execute()
调用 call.enqueue(callback)
Dispatcher.executed(this)
进入拦截器链
getResponseWithInterceptorChain()
返回 Response
Dispatcher.finished(this)
同步请求结束
创建 AsyncCall(callback)
Dispatcher.enqueue(AsyncCall)
加入 readyAsyncCalls 队列
promoteAndExecute() 检查
移入 runningAsyncCalls 队列
AsyncCall.executeOn(executorService)
线程池执行 AsyncCall.run()
进入拦截器链
getResponseWithInterceptorChain()
返回 Response
回调 callback.onResponse()
Dispatcher.finished(this)
异步请求结束
留在 readyAsyncCalls 队列等待
OkHttp使用
java
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.get()
.url("https:www.baidu.com")
.build();
// 通过client的newCall实例化一个RealCall
Call call = client.newCall(request);
// 通过RealCall发起同步请求
Response response = call.execute();
// 通过RealCall发起异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
}
});
原理
1.通过newCall获得RealCall对象
// OkHttpClient.kt
// 实例化一个RealCall,注意参数将OkHttpClient自身传给了RealCall
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
newCall函数实例化了一个RealCall,并将OkHttpClient自身与Request作为参数传递给了RealCall。RealCall的构造函数如下
kotlin
class RealCall(
val client: OkHttpClient,
/** The application's original request unadulterated by redirects or auth headers. */
val originalRequest: Request,
val forWebSocket: Boolean
) : Call {
// ...
}
所以execute函数和enqueue函数的调用的都是RealCall中的代码,如下:
kotlin
// RealCall.kt
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
// 注意这里调用Dispatcher的enqueue方法时实例化了一个AsyncCall
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
RealCall中不管是execute函数还是enqueue函数,最终都是通过OkHttpClient的Dispatcher发起的。
2.分发器--Dispatcher
Dispatcher可以翻译为分发器,是OKHttp中非常重要的一个角色。它是OkHttpClient中的一个成员变量,如下:
kotlin
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
}
它的主要作用是用来调配请求任务的,Dispatcher会根据情况决定任务是被放到ready队列还是放到running队列。同时,还会根据条件将任务从ready队列调入running队列。Dispatcher类的代码结构如下:
kotlin
class Dispatcher constructor() {
// Okhttp能够同时发起的最大请求数
var maxRequests = 64
// 同一个Host能同时发起的最大请求数
var maxRequestsPerHost = 5
// 线程池
val executorService: ExecutorService
/** 异步调用的ready任务队列 */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** 异步调用的running任务队列 */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** 同步调用的任务队列 */
private val runningSyncCalls = ArrayDeque<RealCall>()
constructor(executorService: ExecutorService) : this() {
this.executorServiceOrNull = executorService
}
}
上述代码中的注释给出了Dispatcher中几个比较重要的参数:
- maxRequests 表示Okhttp能够同时发起的最大请求数,在同一时刻OKHttp能够最大支持64的请求同时执行。
- maxRequestsPerHost 表示同一个Host能发起的最大请求数,即同一个Host,在同一时刻最大支持5个请求同时执行。
- executorService OkHttp的线程池。
- readyAsyncCalls 异步调用时ready状态的任务队列,所有异步请求都会事先加入到ready队列,然后根据running队列中的个数来决定是否将其移入到running队列。
- runningAsyncCalls 异步调用时running状态的任务队列
以异步调用enqueue方法为例,来分析源码
kotlin
// Dispatcher.kt
// 这里的AsyncCall是在RealCall的enqueue方法中实例化出来的
internal fun enqueue(call: AsyncCall) {
synchronized(this) { // 保证线程安全
// 先将任务加入准备队列
readyAsyncCalls.add(call)
// ...
// 执行任务的主流程
promoteAndExecute()
}
}
enqueue函数先将任务添加到ready异步任务队列中,然后调用promoteAndExecute开始执行任务,在promoteAndExecute方法中会根据条件将要执行的任务从ready队列移动,代码如下:
kotlin
// Dispatcher
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
// 正在执行的任务大于等于64,直接结束循环遍历,此时任务被添加到了等待队列中等待执行
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 如果当前正在这个host的请求数量大于maxRequestsPerHost,则跳过该任务,遍历后边任务
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
// 将任务从队列中移除
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
// 通过限制条件后则将任务加入到running队列
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
// 遍历executableCalls集合并调用RealCall的executeOn开始执行任务
asyncCall.executeOn(executorService)
}
return isRunning
}
可以看到,如果当前正在执行的任务大于maxRequests时,直接结束循环,意味着新发起的请求被添加到了ready队列中等待执行。
如果当前正在running的请求的host的数量大于maxRequestsPerHost,那么这个任务也不会被添加到running队列。continue后继续遍历后边的任务。
通过上述两个限制条件后,任务最终会被添加到executableCalls和runningAsyncCalls中等待执行。
最后,遍历executableCalls集合并调用RealCall的executeOn开始执行任务。
RealCall的executeOn方法如下:
kotlin
// RealCall
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
// 将RealCall交给线程池执行
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!
}
}
}
上述方法中通过executorService将任务交给线程池来执行。而executorService线程池是在Dispatcher中被初始化的:
kotlin
// Dispatcher
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
可以看到在OkHttp中创建的这个线程池核心线程数是0,最大线程数是Int.MAX_VALUE,且传入了一个SynchronousQueue的阻塞队列。这样创建出来的线程池有一个特点,即:高并发、最大吞吐量。
这里还应该注意一下SynchronousQueue,SynchronousQueue是一个没有容量的容器,通过SynchronousQueue保证了所有任务不会被添加到等待队列中,而是创建新的线程立即执行任务。不会造成任务被阻塞到队列。
RealCall被加入线程池之后则会去执行RealCall的run方法,run方法的代码如下:
kotlin
// RealCall
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
// 通过通过拦截器使用责任链模式获取服务器响应数据。
val response = getResponseWithInterceptorChain()
signalledCallback = true
// 请求成功后回调结果
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
// ...
responseCallback.onFailure(this@RealCall, e)
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
}
接下来,请求被交给了getResponseWithInterceptorChain函数来执行,getResponseWithInterceptorChain函数中则是通过责任链模式来获取服务器响应数据的。
3.OkHttp中的责任链模式
Dispatcher将请求最终交给了Interceptor,最终的请求也是在Intercept中执行的。Interceptor在OkHttp中是另一个重要角色。本节就来详细的分析OkHttp的拦截器。
看下getResponseWithInterceptorChain
kotlin
// RealCall
internal 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)
// 构建interceptors的责任链
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// 通过责任链依次执行拦截器的intercept方法,并返回请求结果
val response = chain.proceed(originalRequest)
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
首先创建了一个interceptors的集合,并将一系列的interceptor添加到了集合中,然后通过责任链模式依次执行所有interceptor的intercept方法。
Interceptor是一个接口,内部有一个intercept方法,以及一个Chain的内部接口:
kotlin
fun interface Interceptor {
@Throws(IOException::class)
fun intercept(chain: Chain): Response
// ...
interface Chain {
fun request(): Request
@Throws(IOException::class)
fun proceed(request: Request): Response
// ...
}
}
Interceptor中intercept函数负责请求的处理。还有一个内部接口Chain,它是事件处理链的接口,实现代码在RealInterceptorChain中。
在getResponseWithInterceptorChain函数中实例化了一个RealInterceptorChain,构造方法中的前三个参数很重要,第一个参数是一个RealCall,不必多说。第二个是所有的拦截器的集合,第三个参数是一个index,它是用来标记按顺序执行interceptors集合中的数组的。也就是说interceptor的执行的逻辑是通过RealInterceptorChain进行驱动的。当实例化了RealInterceptorChain之后便调用了它的proceed函数。看下RealInterceptorChain代码如下:
kotlin
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 {
// ...
@Throws(IOException::class)
override fun proceed(request: Request): Response {
// ...
// 通过copy函数,实例化了一个新的RealInterceptorChain实例,注意index加了1
val next = copy(index + 1, request)
// 获取interceptors集合中的第index个拦截器,此处index是0,即获取第一个拦截器
val interceptor = interceptors[index]
// 执行拦截器的intercept方法,并传入下一个要执行的RealInterceptorChain实例
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
// ...
return response
}
// 实例化了一个新的RealInterceptorChain对象
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)
}
在RealInterceptorChain中首先通过copy函数实例化了一个新的RealInterceptorChain对象,并且index参数相比以前加了1,接着取到第index个拦截器执行了它的intercept函数,这个函数节后了下一个要执行的RealInterceptorChain对象。
看下拦截器中intercept的实现,以BridgeInterceptor为例:
kotlin
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// ...
// 调用了chain的process方法
val networkResponse = chain.proceed(requestBuilder.build())
return responseBuilder.build()
}
}
可以看到,在BridgeInterceptor 的intercept方法中又调用了RealInterceptorChain的process方法。这样子就来事继续执行interceptors集合中的下一个拦截器,直到所有拦截器都执行完毕。
4.OKHttp中的五大拦截器
OkHttp使用责任链模式来驱动拦截器的运行,并且在getResponseWithInterceptorChain中添加了五个默认的拦截器:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor以及CallServerInterceptor。这五大默认拦截器分别来负责请求的重试重定向、桥接、缓存、以及调用服务器等功能。
使用责任链模式的特点是最先被添加的拦截器最先被执行,但是最晚收到响应数据。因此拦截器的添加顺序非常重要。
(1)RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor负责失败重试和重定向,是被第一个添加进来的拦截器,因此会首先被执行,但是确实最后一个收到响应数据的。在这个拦截器中,主要功能是判断是否需要重试与重定向。
重试的前提是收到RouteException或者IOException,一旦在后续的拦截器的执行过程中出现这两个异常,就会通过recover方法进行判断是否需要进行重新连接。
重定向发生在重试判定之后,如果不满足条件还需要进一步调用followUpRequest根据Response的响应码,followUp最大发生20次。
(2)BridgeInterceptor
负责把用户请求转换为发送到服务器的请求,并把服务器的响应转化为用户需要的响应。它的执行步骤如下:
1.将用户的 Request 构造为发送给服务器的 Reuquest,该过程会添加各种请求报头(包括 Host、Connection、cookie 等)
2.构造完成后,将新的 Request 交给下一拦截器来处理
3.得到服务器的 Response 后,先保存 cookies,接着将服务器的 Response 转换为用户需要的 Response 并返回(如果使用了 gzip 压缩并且服务器的 Response 有 body 的话,还要给用户的 Response 设置相应 body)
(3)CacheInterceptor
负责读取缓存、更新缓存。执行步骤如下:
1.从 Cache 中得到 Request 对应的缓存,默认没有设置 Cache,需要用户自己配置
2.得到缓存策略
3.如果通过缓存策略没有得到缓存,则关闭缓存
4.如果缓存策略设置了禁用网络,看得到的缓存是否为空,如果缓存为空,则构建一个返回码为 504 的 Response,说明返回失败。如果缓存不为空,则返回缓存
5.如果可以使用网络,就交给下一拦截器执行请求,执行请求的过程发生异常,及时关闭缓存,并抛出异常,让上一拦截器处理。
6.当缓存和网络返回的 Response 同时存在时,如果返回的状态码为 304(说明服务器的文件未更新,可以使用缓存),则返回缓存。否则更新缓存,并返回网络请求后的 Response
(4)ConnectInterceptor
负责和服务器建立连接。执行步骤如下:
1.找到一个可用的 RealConnection, 再利用这个 RealConnection 的输入输出(BufferSource 和 BufferSink)创建 HttpCodec。( HttpCodec 有两个实现:Http1Codec 和 Http2Codec,分别对应 HTTP/1.1 和 HTTP/2 版本)
2.调用下一拦截器进行后续请求操作。
(5)CallServerInterceptor
1.向服务器写入请求头,如果请求头有 Expect: 100-continue,需要根据服务器返回的结果决定是否可以继续写入请求体。
2.得到响应头并构建带有响应头的 Response,接着为 Response 构建响应体并返回。
5.拦截器实战示例
了解了 OkHttp 拦截器的工作原理后,我们可以通过自定义拦截器来扩展 OkHttp 的功能。下面是一个完整的自定义拦截器实战示例,展示如何创建一个打印请求/响应日志的拦截器,并说明如何将其添加到 OkHttpClient 中。
5.1 自定义日志拦截器实现
kotlin
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.platform.Platform
import java.io.IOException
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit
/**
* 自定义日志拦截器,用于打印 HTTP 请求和响应的详细信息
* 包括:请求方法、URL、请求头、请求体、响应状态码、响应头、响应体等
*/
class LoggingInterceptor : Interceptor {
// 日志级别控制
enum class Level {
/** 不记录日志 */
NONE,
/** 仅记录请求和响应的基本信息(方法、URL、状态码) */
BASIC,
/** 记录请求和响应的基本信息及头部信息 */
HEADERS,
/** 记录请求和响应的所有信息(包括请求体和响应体) */
BODY
}
// 默认日志级别为 BODY
@Volatile
var level: Level = Level.BODY
// 日志标签
private val tag = "OkHttp-Logging"
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 记录请求开始时间
val startNs = System.nanoTime()
// 打印请求信息
logRequest(request)
// 执行请求,获取响应
val response: Response
try {
response = chain.proceed(request)
} catch (e: Exception) {
// 请求失败时记录异常信息
Platform.get().log("$tag: <-- HTTP FAILED: $e", Platform.WARN, e)
throw e
}
// 计算请求耗时
val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
// 打印响应信息
logResponse(response, tookMs)
return response
}
/**
* 记录请求信息
*/
private fun logRequest(request: Request) {
if (level == Level.NONE) return
val logBody = level == Level.BODY
val logHeaders = level == Level.HEADERS || level == Level.BODY
// 记录请求基本信息
val requestStartMessage = "$tag: --> ${request.method} ${request.url}"
Platform.get().log(requestStartMessage, Platform.INFO, null)
// 记录请求头
if (logHeaders) {
request.headers.forEach { name, value ->
Platform.get().log("$tag: $name: $value", Platform.INFO, null)
}
// 记录请求体
if (logBody && request.body != null) {
val buffer = okio.Buffer()
request.body!!.writeTo(buffer)
val contentType = request.body!!.contentType()
val charset = contentType?.charset(Charset.forName("UTF-8")) ?: Charset.forName("UTF-8")
if (isPlaintext(buffer)) {
Platform.get().log("$tag: ${buffer.readString(charset)}", Platform.INFO, null)
Platform.get().log("$tag: --> END ${request.method} (${request.body!!.contentLength()}-byte body)",
Platform.INFO, null)
} else {
Platform.get().log("$tag: --> END ${request.method} (binary ${request.body!!.contentLength()}-byte body omitted)",
Platform.INFO, null)
}
} else {
Platform.get().log("$tag: --> END ${request.method}", Platform.INFO, null)
}
}
}
/**
* 记录响应信息
*/
private fun logResponse(response: Response, tookMs: Long) {
if (level == Level.NONE) return
val logBody = level == Level.BODY
val logHeaders = level == Level.HEADERS || level == Level.BODY
// 记录响应基本信息
val responseMessage = "$tag: <-- ${response.code} ${response.message} ${response.request.url} (${tookMs}ms)"
Platform.get().log(responseMessage, Platform.INFO, null)
// 记录响应头
if (logHeaders) {
response.headers.forEach { name, value ->
Platform.get().log("$tag: $name: $value", Platform.INFO, null)
}
// 记录响应体
if (logBody && response.body != null) {
val source = response.body!!.source()
source.request(Long.MAX_VALUE) // 缓冲整个响应体
val buffer = source.buffer()
val contentType = response.body!!.contentType()
val charset = contentType?.charset(Charset.forName("UTF-8")) ?: Charset.forName("UTF-8")
if (isPlaintext(buffer)) {
Platform.get().log("$tag: ${buffer.clone().readString(charset)}", Platform.INFO, null)
Platform.get().log("$tag: <-- END HTTP (${buffer.size}-byte body)", Platform.INFO, null)
} else {
Platform.get().log("$tag: <-- END HTTP (binary ${buffer.size}-byte body omitted)",
Platform.INFO, null)
}
} else {
Platform.get().log("$tag: <-- END HTTP", Platform.INFO, null)
}
}
}
/**
* 判断缓冲区内容是否为纯文本
*/
private fun isPlaintext(buffer: okio.Buffer): Boolean {
try {
val prefix = okio.Buffer()
val byteCount = if (buffer.size < 64) buffer.size else 64
buffer.copyTo(prefix, 0, byteCount)
for (i in 0 until 16) {
if (prefix.exhausted()) {
break
}
val codePoint = prefix.readUtf8CodePoint()
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false
}
}
return true
} catch (e: Exception) {
return false // 非 UTF-8 序列
}
}
}
5.2 将自定义拦截器添加到 OkHttpClient
创建好自定义拦截器后,需要将其添加到 OkHttpClient 的拦截器列表中。根据拦截器的添加位置,可以分为两种类型:
5.2.1 应用拦截器(Application Interceptors)
应用拦截器在 RetryAndFollowUpInterceptor 之前执行,适合处理与业务逻辑相关的操作,如添加认证头、记录日志等。
kotlin
import okhttp3.OkHttpClient
import okhttp3.Request
fun main() {
// 创建 OkHttpClient 并添加自定义日志拦截器
val client = OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor().apply {
// 设置日志级别
level = LoggingInterceptor.Level.BODY
})
.build()
// 创建请求
val request = Request.Builder()
.url("https://api.example.com/data")
.get()
.build()
// 执行请求
val response = client.newCall(request).execute()
// 处理响应
if (response.isSuccessful) {
println("请求成功: ${response.body?.string()}")
} else {
println("请求失败: ${response.code}")
}
response.close()
}
5.2.2 网络拦截器(Network Interceptors)
网络拦截器在 ConnectInterceptor 之后执行,适合处理与网络传输相关的操作,如重试、压缩等。
kotlin
import okhttp3.OkHttpClient
fun main() {
// 创建 OkHttpClient 并添加网络拦截器
val client = OkHttpClient.Builder()
.addNetworkInterceptor(LoggingInterceptor().apply {
// 设置日志级别为 HEADERS,避免记录大量响应体数据
level = LoggingInterceptor.Level.HEADERS
})
.build()
// 使用 client 发起请求...
}
5.3 日志输出示例
当使用 LoggingInterceptor 并设置 level = Level.BODY 时,日志输出类似如下:
OkHttp-Logging: --> GET https://api.example.com/data
OkHttp-Logging: User-Agent: OkHttp/4.10.0
OkHttp-Logging: Host: api.example.com
OkHttp-Logging: --> END GET
OkHttp-Logging: <-- 200 OK https://api.example.com/data (245ms)
OkHttp-Logging: Content-Type: application/json
OkHttp-Logging: Content-Length: 127
OkHttp-Logging: {"status": "success", "data": {"id": 123, "name": "示例数据"}}
OkHttp-Logging: <-- END HTTP (127-byte body)
5.4 自定义拦截器的应用场景
- 请求/响应日志记录:如上面的示例,用于调试和监控
- 认证与授权:自动添加认证令牌到请求头
- 请求重试:在特定失败情况下自动重试请求
- 请求/响应转换:自动序列化/反序列化 JSON 数据
- 性能监控:记录请求耗时、统计成功率等
- 缓存控制:根据业务逻辑自定义缓存策略
5.5 注意事项
- 性能考虑:拦截器中的操作会影响请求性能,特别是涉及 I/O 操作时
- 内存使用:记录完整响应体时要注意内存使用,特别是大文件下载
- 线程安全:确保拦截器的实现是线程安全的
- 异常处理:合理处理拦截器中的异常,避免影响正常请求流程
- 日志级别控制 :生产环境建议使用
Level.BASIC或Level.HEADERS,避免记录敏感信息
通过自定义拦截器,开发者可以灵活地扩展 OkHttp 的功能,满足各种复杂的业务需求,同时保持代码的模块化和可维护性。