OkHttp作为一个HTTP客户端,拦截器是其强大的功能之一,它允许用户在请求和响应的声明周期中拦截并修改它们,利用拦截器,我们可以很方便的实现如日志记录、请求加密/签名、响应解密、异常重试等功能。本文将详细介绍一下拦截器的使用方法及其原理
如何使用拦截器
首先我们先来看下拦截器使用的具体场景感受一下
监控打点拦截器
在服务端掉用外部系统的接口,我们一般需要统计一下成功率,TP99等信息,假设我们使用 cat 来支持此监控功能,那么我们就可以定义一个拦截器进行统计上报
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CatInterceptor implements Interceptor { @NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { final Request request = chain.request();
Transaction t = Cat.newTransaction("URL", request.url().encodedPath()); try { Response response = chain.proceed(request); t.setStatus(Transaction.SUCCESS); return response; } catch (Exception e) { t.setStatus(e); Cat.logError(e); throw e; } finally { t.complete(); } } }
|
自动重试拦截器
如果是查询接口或者请求支持幂等的,我们可以实现一个在返回非2XX或者网络异常时自动重试的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class RetryInterceptor implements Interceptor {
private int maxRetry; public RetryInterceptor(int maxRetry) { this.maxRetry = maxRetry; }
@NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { final Request request = chain.request(); int currentRetry = 0; while (true) { try { final Response response = chain.proceed(request); if (!response.isSuccessful() && isRecoverable(currentRetry)) { response.close(); currentRetry++; continue; } return response; } catch (IOException e) { if (!isRecoverable(currentRetry)) { throw e; } currentRetry++; } } } private boolean isRecoverable(int currentRetry) { return currentRetry <= maxRetry; } }
|
有些情况如鉴权等需要,可能需要设置一些自定义的header,这个也可以通过拦截器来实现
1 2 3 4 5 6 7 8 9 10 11
| public class TokenHeaderInterceptor implements Interceptor { @NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest.newBuilder() .header("token", "this is token") .build(); return chain.proceed(requestWithUserAgent); } }
|
之后在构造客户端时配置上拦截器即可
1 2 3 4 5
| OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new RetryInterceptor(3)) .addInterceptor(new CatInterceptor()) .addInterceptor(new TokenHeaderInterceptor()) .build();
|
拦截器的原理
拦截器主要是基于责任链模式,每个拦截器都是一个独立的处理单元,在其中可以选择修改请求/响应报文、可以终止请求返回自定义的结果
OkHttp内部实际都是使用拦截器进行的实现,比如最后一个节点发起真实网络调用的是 CallServerInterceptor 拦截器
一个最简单的使用OkHttp的代码大致如下,我们可以依据这个为入口看一下大致流程
1 2 3 4 5 6 7
| val okHttpClient = OkHttpClient() val request = Request.Builder().url("http://localhost:8080/").build() val call = okHttpClient.newCall(request)
val execute = call.execute() println(execute)
|
构造请求的部分先略过,我们直接看发起调用的部分,这部分对应的实际类是 RealCall.kt,对应execute代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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) } }
|
这里我们重点看一下通过拦截器获取结果这部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| @Throws(IOException::class) internal 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)
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 { val response = chain.proceed(originalRequest) if (isCanceled()) { response.closeQuietly() throw IOException("Canceled") } return response } catch (e: IOException) { calledNoMoreExchanges = true throw noMoreExchanges(e) as Throwable } finally { if (!calledNoMoreExchanges) { noMoreExchanges(null) } } }
|
这时候请求就进入到了 RealInterceptorChain 方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| override fun proceed(request: Request): Response { check(index < interceptors.size)
calls++
if (exchange != null) { check(exchange.finder.routePlanner.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" } }
val next = copy(index = index + 1, request = request) val interceptor = interceptors[index]
@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" } }
return response }
|
合理使用拦截器可以帮助我们设计出更健壮、更高效的程序,无论是添加公共头部、日志记录、缓存控制还是重试机制,拦截器都能提供简洁而高效的解决方案。希望本文在大家使用OkHttp的过程中能够有所帮助,如果有任何问题或建议,欢迎交流讨论~