OkHttp源码剖析(四) 报文读写工具ExchangeCodec
使用示例
本篇主要基于okhttp-4.9.0进行分析。下面是OkHttp主要的使用示例。
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request
.Builder()
.url("https://www.yanfriends.com")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("okhttp response", response.body().string() );
}
});
架构图
OkHttp的流程图大致如下图所示,这里先有个大致感观,每一环的细节都会在接下来的章节中说到,再看完所有章节后回过头来再看下,会对OkHttp的整个流程架构有清晰的认识。
本质理解
http的连接本质上是个socket,根据http协议,通过socket包装发送请求并获得返回结果。
网路连接库一开始的样子如下代码所示,其实只要符合Http协议的请求,就可以和网络进行交互,类似于OkHttp的网络请求库,帮助开发者方便和屏蔽了Http协议中类似于请求头,重连、合并、代理、返回结果解析等等Http协议细节的应用层实现。
val path = "http://www.baidu.com/"
val host = "www.baidu.com"
var socket: Socket? = null
var streamWriter: OutputStreamWriter? = null
var bufferedWriter: BufferedWriter? = null
try {
socket = Socket(host, 80)
streamWriter = OutputStreamWriter(socket.getOutputStream())
bufferedWriter = BufferedWriter(streamWriter)
bufferedWriter.write("GET $path HTTP/1.1\r\n")
bufferedWriter.write("Host: www.baidu.com\r\n")
bufferedWriter.write("\r\n")
bufferedWriter.flush()
val myRequest = BufferedReader(InputStreamReader(socket.getInputStream(), "UTF-8"))
var d = -1
while (myRequest.read().also({ d = it }) != -1) {
print(d.toChar())
}
} catch (e: IOException) {
e.printStackTrace()
}
OkHttpClient
OkHttpClient是整个OkHttp的配置中心。
所有的call请求都会共享OkHttpClient中的配置,如出错重试、连接池、日志、各种拦截器等。
OkHttpClient实现看Call.Factory
接口,是Call的工厂类,生产 Call并通过 Call 来发起 HTTP Request 获取 Response。
OkHttpClient应该被共享,使用时保持单例,因为每个Client 都会有一个自己的连接池和线程池,复用 Client 可以减少资源的浪费。
OkHttpClient 中的配置主要有:
-
Dispatcher dispatcher
:用于调度后台发起的网络请求的调度器, 有后台总请求数和单主机总请求数的控制。 -
List<Protocol> protocols
:支持的应用层协议,即 HTTP/1.1、 HTTP/2 等。 -
List<ConnectionSpec> connectionSpecs
:应用层支持的 Socket 设置,即使用明文传输(用于 HTTP)还是某个版本的 TLS(用于 HTTPS)。 -
List<Interceptor> interceptors
:大多数时候使用的 Interceptor 都应该配置到这里。 -
List<Interceptor> networkInterceptors
:直接和网络请求交互 的 Interceptor 配置到这里,例如如果你想查看返回的 301 报文或者未解压 的 Response Body,需要在这里看。 -
CookieJar cookieJar
:管理 Cookie 的控制器。OkHttp 提供了 Cookie 存取的判断支持(即什么时候需要存 Cookie,什么时候需要读取 Cookie,但没有给出具体的存取实现。如果需要存取 Cookie,你得自己写实现,例如用 Map 存在内存里,或者用别的方式存在本地存储或者数据库。 -
Cache cache
:Cache 存储的配置。默认是没有,如果需要用,得自己配置出 Cache 存储的文件位置以及存储空间上限。 -
HostnameVerifier hostnameVerifier
:用于验证 HTTPS 握手过程 中下载到的证书所属者是否和自己要访问的主机名一致。 -
CertificatePinner certificatePinner
:用于设置 HTTPS 握手 过程中针对某个 Host 额外的的 Certificate Public Key Pinner,即把网站证 书链中的每一个证书公钥直接拿来提前配置进 OkHttpClient 里去,作为正 常的证书验证机制之外的一次额外验证。 -
Authenticator authenticator
:用于自动重新认证。配置之后,在 请求收到 401 状态码的响应是,会直接调用 authenticator ,手动加 入 Authorization header 之后自动重新发起请求。 -
boolean followRedirects
:是否允许重定向 -
boolean followSslRedirects
:不是是否自动 follow HTTPS URL,是在重定向时发生了协议切换 ,是否允许,默认 true。 -
重定向的意思,而是是否自动 follow 在 HTTP 和 HTTPS 之间切换的重定向。
-
boolean retryOnConnectionFailure
:在请求失败的时候是否自动 重试。注意,大多数的请求失败并不属于 OkHttp 所定义的「需要重试」, 这种重试只适用于「同一个域名的多个 IP 切换重试」「Socket 失效重试」 等情况。 -
int connectTimeout
:建立连接(TCP 或 TLS)的超时时间,默认十秒;
int readTimeout
:发起请求到读到响应数据的超时时间,默认十秒;
int writeTimeout
:发起请求并被目标服务器接受的超时时间。(因为有时候对方服务器可能由于某种原因而不读取你的 Request),默认十秒;
粗识okhttp
OkHttp中的细节很多,这里不过分深入,只需有个大致的结构认识即可,一些细节会在接下来的文章中具体分析。
RealCall初始化
使用OkHttpClient.newCall()
创建RealCall和Transmitter类,Transmitter类可以将okhttp和网络层连接起来。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
RealCall.execute()/RealCall.enqueue()
-
调用
Dispatcher.executed()
/enqueue()将call加入到执行的Call队列中,以控制Call的启动、终结和取消。 -
调用
getResponseWithInterceptorChain()
开始向服务器发送请求并获取回应@Override public void enqueue(Callback responseCallback) { ... transmitter.callStart(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
RealCall.getResponseWithInterceptorChain()
OkHttp将各种请求信息的封装写在了不同层的Interceptor中,根据开放方法的参数设置,请求时层层添加http协议规定的信息,最终将请求发出。getResponseWithInterceptorChain()
方法的主要逻辑如下:
-
添加自定义的interceptor
-
依次添加自定义interceptor、RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、自定义的networkInterceptor(如Facebook的stetho),
-
创建RealInterceptorChain对象,之后调用RealCall.RealInterceptorChain.proceed()`方法,按照责任链序依次调用每个拦截器处理逻辑
Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(new RetryAndFollowUpInterceptor(client)); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(); try { Response response = chain.proceed(originalRequest); ... return response; } catch (IOException e) { ... } }
RealInterceptorChain.proceed()
-
检查自定义的interceptor是否合法
-
创建新的RealInterceptorChain,注意index+1
-
获取index当前的interceptor,并调用
interceptor.intercept()
方法交给Interceptor处理,获取返回值,思考一下为什么不能用循环 -
要求所有自定义的interceptor要调用
RealInterceptorChain.proceed()
,否则会造成请求无法发出 -
判断response或者response.body()是否为空
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException { ... // Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange, index + 1, request, call, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed(). if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response; }
参考
https://square.github.io/okhttp/
https://zhuanlan.zhihu.com/p/58093669
https://www.jianshu.com/p/8d69fd920166
https://juejin.cn/post/6844904037154816007