转载
OkHttpClient.Builder > OkHttpClient > + Request > RealCall > execute / enqueue > 拦截器链 > CallServerInterceptor请求网络 Okhttp 内部 socket 连接成功后,会获取 socket 的输入、输出流,用Okio包装, 封装成一个 HttpCodeC 对象,用于操作请求信息的写入、和返回信息的读取
设置超时时间添加自定义拦截器配置Https调用 build 方法构建 OkHttpClient对象 OkHttpClient#newCall 传入Request构建 RealCall 对象 同步请求 RealCall # execute 异步请求 RealCall # enqueue 异步请求 running 请求队列已满最多同时64个请求同一个host最多同时5个请求 异步请求 running 请求队列未满 从 ready队列移除放入 running队列 core==0 max==Integer#MAX_VALUE 的线程池中执行 OkHttpClient.Builder OkHttpClientokhttp3 # Call # Factory 的实现类 用于创建 RealCall 对象 Request url/method/header/body RealCall Diapatcher#execute Diapatcher#enqueue 塞入等待执行队列 readyAsyncCalls 塞入正在执行队列 runningAsyncCalls 用线程池执行请求 拦截器#责任链模式调用 自定义拦截器 重试拦截器 RetryAndFollowUpInterceptor 转换拦截器 BridgeInterceptor 包含gzip压缩与解压处理 缓存拦截器 CacheInterceptor 解析 请求头-响应头-Cache-Control 控制缓存 连接拦截器 ConnectInterceptor 内部连接池 实现 连接复用 网络拦截器 networkInterceptors 配置 OkHttpClient 时设置的 请求拦截器 CallServerInterceptor Response code/message/header/body Response会顺着拦截器链逐个往上返回责任链模式: 它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象, 它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。 该模式还描述了往该处理链的末尾添加新的处理对象的方法。
Interceptor 把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。
1)首先调用的是,在配置 OkHttpClient 时设置的 interceptors;
2)RetryAndFollowUpInterceptor:负责失败重试以及重定向的
3)BridgeInterceptor:负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的
4)CacheInterceptor:负责读取缓存直接返回、更新缓存的
5)ConnectInterceptor:负责和服务器建立连接的
6)networkInterceptors:配置 OkHttpClient 时设置的
7)CallServerInterceptor:负责向服务器发送请求数据、从服务器读取响应数据的
拦截器链会逐个调用,如果中间没有拦截器完成处理,会一直往下调用 CallServerInterceptor 执行真正的网络请求 请求的结果会返回给上一个调用的拦截器,逐级返回。 这个过程类似View的事件机制。
1、同步请求
String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string(); } final class RealCall implements Call { @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); // (1) executed = true; } try { client.dispatcher().executed(this); // (2) Response result = getResponseWithInterceptorChain(); // (3) if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); // (4) } } }1)检查这个 call 是否已经被执行了,每个 call 只能被执行一次, 如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。
2)利用 client.dispatcher().executed(this) 来进行实际执行, dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一, 它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。
3)调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果, 从函数名可以看出,这一步还会进行一系列“拦截”操作。
4)最后还要通知 dispatcher 自己已经执行完毕。
dispatcher 这里我们不过度关注,在同步执行的流程中, 涉及到 dispatcher 的内容只不过是告知它我们的执行状态, 比如开始执行了(调用 executed),比如执行完毕了(调用 finished),在异步执行流程中它会有更多的参与。
2、建立连接 ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); }建立连接就是创建了一个 HttpCodec 对象,它将在后面的步骤中被使用。 Http1Codec:对应 HTTP/1.1 的实现 Http2Codec:对应 HTTP/2 的实现
在 Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装, Okio 对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。
1)首先获取 StreamAllocation 对象,StreamAllocation中封装了 连接池 ConnectionPool ConnectionPool 连接池中有一个队列,保存了 RealConnection 对象
2)根据请求信息,先从连接池中找到能用的 RealConnection, 找不到则创建新的 RealConnection对象并返回
3)利用 RealConnection 的输入输出(BufferedSource 和 BufferedSink)创建 HttpCodec 对象
4)RealConnection的 BufferedSource 和 BufferedSink 是在 Socket 连接成功后创建的
public class RealConnection extends xxx implements Connection { private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException { Proxy proxy = route.proxy(); Address address = route.address(); rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP ? address.socketFactory().createSocket() : new Socket(proxy); eventListener.connectStart(call, route.socketAddress(), proxy); rawSocket.setSoTimeout(readTimeout); try { Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout); } catch (ConnectException e) { ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress()); ce.initCause(e); throw ce; } // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0 // More details: // https://github.com/square/okhttp/issues/3245 // https://android-review.googlesource.com/#/c/271775/ try { //【BufferedSource】内部封装的是 socket.getInputStream(),用于接收服务器返回的数据 source = Okio.buffer(Okio.source(rawSocket)); //【BufferedSink】内部封装的是 socket.getOutputStream(),用于向服务器发送数据 sink = Okio.buffer(Okio.sink(rawSocket)); } catch (NullPointerException npe) { if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) { throw new IOException(npe); } } } }HttpCodec 中,持有了RealConnection 的输入输出(BufferedSource 和 BufferedSink) 就可以 发送数据到服务器 、 接收服务器数据 了。
3、发送和接收数据 CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException { HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream(); StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation(); Request request = chain.request(); long sentRequestMillis = System.currentTimeMillis(); /*【1】httpCodec中持有 RealConnection的 BufferedSource 和 BufferedSink 对象 * 可以通过 BufferedSink把数据发送给服务器 * 这里是把 request header 发送给服务器 */ httpCodec.writeRequestHeaders(request); //【2】如果有 requetsBody,就向服务器发送 if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } httpCodec.finishRequest(); //【3】读取 response header,先构造一个 Response 对象 Response response = httpCodec.readResponseHeaders() .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); //【4】如果有 response body,就在【3】的基础上加上body,构造一个新的 Response 对象 if (!forWebSocket || response.code() != 101) { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } // 省略部分检查代码 return response; }核心工作都由 HttpCodec 对象完成,HttpCodec 实际上是用Okio
核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒 readyAsyncCalls 等待执行请求队列 runningAsyncCalls 正在执行的请求队列 Okhttp:默认最大同时请求数 Okhttp:一个 host 最多能同时有 5个请求
private int maxRequests = 64;// 默认最大同时请求数 private int maxRequestsPerHost = 5;//一个 host 最多能同时有 5个请求 client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } }); // RealCall#enqueue @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.dispatcher().enqueue(new AsyncCall(responseCallback)); } // Dispatcher#enqueue public final class Dispatcher { //核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒。 public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } synchronized void enqueue(AsyncCall call) { /* dispatcher 在异步执行时发挥的作用了, * 如果当前还能执行一个并发请求,那就立即执行,否则加入 readyAsyncCalls 队列。 * 而正在执行的请求执行完毕之后,会调用 promoteCalls() 函数, * 来把 readyAsyncCalls 队列中的 AsyncCall “提升”为 runningAsyncCalls,并开始执行。 */ if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } }Okhttp默认线程池:核心线程 0,最大线程数 Integer.MAX_VALUE,闲置时间60秒。
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } ... //execute() 是在父类的 run()中调用的 @Override protected void execute() { boolean signalledCallback = false; try { //这里开始调用拦截器链,一个个拦截器开始执行 Response response = getResponseWithInterceptorChain(); //执行完成后,把结果通过 callback 回调到上层 if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { ... } finally { /* 执行完毕后会调用 Diaspatcher的 finish() * finish()方法中会把当前请求从 runningAsyncCalls 队列中移除 */ client.dispatcher().finished(this); } } }同步(Call#execute() 执行之后)或者异步(Callback#onResponse() 回调中)请求完成之后, 我们就可以从 Response 对象中获取到响应数据了, 包括 HTTP status code,status message,response header,response body 等。
这里 body 部分最为特殊,因为服务器返回的数据可能非常大, 所以 必须通过数据流的方式 来进行访问 (当然也提供了诸如 string() 和 bytes() 这样的方法将流内的数据一次性读取完毕), 而响应中其他部分则可以随意获取。
响应 body 被封装到 ResponseBody 类中,该类主要有两点需要注意: 1)每个 body 只能被消费一次,多次消费会抛出异常 2)body 必须被关闭,否则会发生资源泄漏
由 HttpCodec#openResponseBody 提供具体 HTTP 协议版本的响应 body, 而 HttpCodec 则是利用 Okio 实现具体的数据 IO 操作
这里有一点值得一提,OkHttp 对响应的校验非常严格, HTTP status line 不能有任何杂乱的数据,否则就会抛出异常。
CacheInterceptor 位于 ConnectInterceptor 之前, 在建立 连接之前,会先检查相应是否已经被缓存、缓存是否可用。 如果是,则直接返回缓存的数据。否则进行后面的流程,并在返回之前把网络数据写入缓存。
OkHttp内置封装了一个 Cache 类, 它会解析 请求头、响应头 Cache-Control,来控制是否使用缓存、以及缓存是否有效
它利用 DiskLruCache ,用磁盘上的有限大小空间进行缓存,按 LRU算法进行缓存淘汰。
如果要自定义缓存策略,实现 InternalCache 接口,在构造 OkHttpClient 时设置。
Builder模式、责任链模式、外观模式(门面模式)、策略模式(缓存这块)
Socket
在 RealConnection#connect() 中会创建并连接 socket
使用自己信任的dns Http服务器,做dns域名解析,降低被劫持的风险。
HttpDns,是对DNS解析的另一种实现方式,将域名解析的协议由 DNS协议 换成 Http协议。
客户端直接访问HttpDNS接口,获取业务在域名配置管理系统上配置的访问延迟最优的IP (基于容灾考虑,还是保留次选使用运营商LocalDNS解析域名的方式)
绕过运营商的本地域名解析服务器,避免本地域名解析服务器缓存/劫持/插入广告
1)优点 对Dns的控制偏上层,可更加细化,控制灵活。 容灾处理更容易
2)缺点 一切跟域名有关的处理全部失效 在Https下处理SSL证书会出现校验问题 ip访问时出现Cookie校验问题。
Https下不会存在证书校验问题,保证流程正常执行 种 Cookie 时不会存在问题
时机过于底层,容灾控制都不方便。 okhttp自身存在缓存,一旦dns自身ttl过期,okhttp缓存有可能还在使用,会存在一定的风险。
推荐阅读: 拆轮子系列-拆OkHttp