?OKHTTP和httpclient 现在默认的网络请求底层就是okhttp? 缓存拦截器详解?
文字描述整个流程
- Okhttpclient类主要进行网络配置,和配置eventListenerFactory,用来生产回调
- client.newBuilder()可以为特定的请求进行单独的配置,还是使用共同的线程池,连接池和其他配置
- client.newCall是创建一个在未来将要被执行的request,并为这个request创建一个eventListener,eventListener用来回调请求过程中的几乎所有状态,eventListener中的每一个回调都有一个Call参数,其中包含Request,Request中包含这个请求的方法参数和path等信息,可以用来区分不同的request
- client.dispatcher().enqueue(new AsyncCall(responseCallback))
-
enqueue 一个call不能被执行两次,会抛出异常,有一个synchronized方法监控
- ==eventListener.callStart(this);==
- Dispatcher 有关何时请求异步网络的策略
- client.dispatcher().enqueue 判断当前请求数是否达到最大值或者当前host请求数是否达到最大值,没有的话加入runnig 列表并交给线程池执行
-
AsyncCall--executorService().execute(call)-- enqueue方法中调用线程池执行方法,真正执行的传入参数是一个runnable ;这里的AsyncCall的父类就是runnable,父类进行了封装,run方法调用了AsyncCall中的 execute方法
-
- ==AsyncCall== 的execute方法,==核心==
- 最外部调用enqueue方法时传入成功失败回调responseCallback,这个回调的调用地方就在execute方法中
- execute方法核心行 Response response = ==getResponseWithInterceptorChain==();
- OkHttpClient整个是一个配置类,用来配置各种数据和参数
- 日了狗,写了一个多小时没有了
- ==getResponseWithInterceptorChain==
- ==在拦截器中 以cChain==.proceed为分割线,
所有连接值的都是http连接或者ssl连接,没有http连接 ctrl+alt+b 接口
google收了okhttp 主要做下层支持
OkHttpClient client = new OkHttpClient();
client.newCall(new Request.Builder()
.url("")
.build())
.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
}
});
看newcall
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//websocket 对http做了扩展 , 可以让服务器对客户端做推送
//用的少,一般做股票交易平台需要用,
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
所有的连接都是tcp连接,或ssl连接,没有所谓的http连接,因为连接指的是双方能把对方记住,发消息时不需要声明自己是谁,http本身是无连接的 eventListener是连接建立,开始请求,返回响应等状态的响应回调
下面看enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {//一个call只能被执行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
下面看dispatcher、enqueue、AsyncCall
Dispatcher 是管理线程的,每个request和response需要使用单独的线程,这样才不会互相影响,这里用的ExecutorService 线程池
public final class Dispatcher {
private int maxRequests = 64;//同一时间最大请求数,达到这个上限时,不做新的请求了,等一等,在队列里存一村
private int maxRequestsPerHost = 5;//同一个主机(服务器)最大同时请求数,达到这个数时也等一等,避免对同一个主机过大的压力
这个数是可以自己设置的
回到上面的enqueue
client.dispatcher().enqueue(new AsyncCall(responseCallback));
Dispatcher中的enqueue 没超限制,继续执行,超限制后加入队列
synchronized void enqueue(AsyncCall call) {
//数量没有超过上方的限制是,我就直接去执行。 executorService().execute(call);
//如果我超过限制了,就把自己加到ready队列, readyAsyncCalls.add(call);
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//记录正在进行的请求
runningAsyncCalls.add(call);
//注意这里,把call放到线程池中去执行,也就是说会调用call的run方法,也就到了下方提到的excute方法中
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
下面看AsyncCall做了什么,线程控制都是在Runnable中执行,都有run方法,它的run方法在父类中,
client.dispatcher().enqueue(new AsyncCall(responseCallback));
//NamedRunnable 是AsyncCall 的父类 ,run方法实际调用了NamedRunnable的execute方法
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
在外部调用enqueue,最终会调用到下方的execute中的getResponseWithInterceptorChain()
,返回Response
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
//唯一重点行 请求和响应都在这个方法里 getResponseWithInterceptorChain(), 通过拦截器链获取相应,
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
==到这里第一部分就完整了==
这时候如果我们已经在后台线程,可以使用同步的网络请求,使用excute方法,不切换线程
Response response = okHttpClient.newCall(new Request.Builder().url().build()).execute();
response.body()
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();//注意这里,不需要切线程,直接请求
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
为了理解okHttp http等,都需要先了解下Okhttpclient中的配置项,然后是去解读
getResponseWithInterceptorChain()
这个方法,是技术上的核心
getResponseWithInterceptorChain是面试核心
OkHttpClient的配置
public class OkHttpClient{
//调度控制线程,平衡性能
final Dispatcher dispatcher;
//自己设置的代理,中介机器,比如国外有一个网站我无法直接访问到,但是可以通过访问其他机器,把自己想要做的事情交给中间机器,由中间机器去访问目标服务器
final @Nullable Proxy proxy;
//所支持的http版本列表 http1.0 http1.1 http2等,
final List<Protocol> protocols;
//配置是使用http还是https,也是个列表(如果使用https的话,这里有支持的tls版本、算法等,这些算法是和服务端进行加密通讯使用)
// CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
// 非对称加密算法 堆成加密算法 hash算法
//加密、解密、hash 这些okhttp都给做了
// public static final ConnectionSpec CLEARTEXT = new Builder(false).build(); 明文,相当于使用http
final List<ConnectionSpec> connectionSpecs;
//可以在chain中插入的拦截器,插在最前面
final List<Interceptor> interceptors;
//可以在chain中插入的拦截器,插在倒数第二个,就是数据回来的进行处理的第一层
final List<Interceptor> networkInterceptors;
//请求状态监听 ????????????
final EventListener.Factory eventListenerFactory;
//没有用过,注释说明是代理服务器
final ProxySelector proxySelector;
//cookie浏览器和服务器之间一个完整的机制 jar 罐子 cookie 饼干
//就是一个cookie的存储器, okhttp本身没有实现,可以自己实现,因为okhttp默认客户端本身不需要实现cookie
//可以自己选择存在本地还是内存,存取都自己来定,内部有存取两个接口
final CookieJar cookieJar;
//就是http的cache,下面两个都是做cache的
final @Nullable Cache cache;
final @Nullable InternalCache internalCache;
//socket 是tcp的端口 比如8080(android用的不多)
//192.168.1.1:8080 前面的是ip那一层的(从下往上数第二层的地址),后面端口是tcp层的(从下往上数第三层的,从上数第二层的)
//socketFactory就是用来创建端口的,获取到那个可以往服务器写东西,可以从服务器读东西的端口,socket本身是tcp的东西,tcp是三次握手,ssl的建立是https那一大堆东西
final SocketFactory socketFactory;
//ssl是ssl的东西,和上方的都不是一层,原理也不一样
//http没有端口这个东西,因为http本身不面向连接,端口实际上指的是tcp端口
final @Nullable SSLSocketFactory sslSocketFactory;
//整理证书,把下发的证书整理成一个证书链,第一个为服务器的证书,最后一个为信任的本地根证书
final @Nullable CertificateChainCleaner certificateChainCleaner;
//主机名验证器,给https用的,验证主机host是否是要访问的host,验证第一个证书的host是否是自己要访问的服务器域名,防止中间人攻击
final HostnameVerifier hostnameVerifier;
//用来做自签名 ,因为服务器的证书可能没有在证书机构登记。这样就不会使用本地根证书验证。这样也可以防止证书被hack的风险。但服务器的证书不能更换了。
//只需要把公钥保存下来,不需要都保存下来,在tls连接的时候(还没到http)就可以比较公钥和服务端的证书的公钥是否一致
//证书固定器 可以把证书的信息记录在本地,到时候服务端下发证书后可以直接和本地证书进行对比,如果一样,就选择相信,这样可以不考虑根证书
final CertificatePinner certificatePinner;
//用来写授权header的 authenization ,如果权限不足会报错,前面有讲,
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
//线程池 连接池 有空闲时不需要现场创建即可直接使用,但有数量限制
//默认此池最多可容纳 5 个空闲连接,这些连接将在 5 分钟不活动后被驱逐。
//private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
// Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
// new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
final ConnectionPool connectionPool;
//根据域名拿到ip列表,是一个列表,一个域名可能对应多个ip
final Dns dns;
//followRedirects重定向时是否需要跳转 默认是true, followSslRedirects是否允许http https互跳,
final boolean followSslRedirects;
final boolean followRedirects;
//失败时是否重连(不只是tcp连接,其他情况也算)
final boolean retryOnConnectionFailure;
//tcp连接时间,超过就报错
final int connectTimeout;
//下载 响应时的等待时间 作业帮是30秒
final int readTimeout;
//写入请求时的等待时间
final int writeTimeout;
//websocket是一个可以双向通信的通道,需要长连接,需要发送小的心跳包,
//这个是ping的间隔 ping pong 是回复的
final int pingInterval;
自签名证书验证的使用,担心证书机构被hack也可以使用
虽然对方签了个假证书,但是假证书的hash和本地的不一致
但风险是服务器的证书不能更换了,一旦本地配置了,就会放弃了本地根证书的验证,全部使用
authenticator使用,没有权限时,会回调,需要去网络请求新的token,然后添加进request 的header
getResponseWithInterceptorChain
这里可以看到responseCallback回调请求成功和失败
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
getResponseWithInterceptorChain()
方法是整体的一个请求流程,
这个方法时整个技术上的核心 ,每一行就是核心的一部分。
final class RealCall implements Call {
...
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//自己配,在OkhttpClient中配置
interceptors.addAll(client.interceptors());
//重试 跟进重定向的连接 关注每一个interceptor的intercept方法
//每一个intercept方法都是做三件事,1事情准备,2交给下一个并等待回来,3回来后做后续处理
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterc
eptor(client));
if (!forWebSocket) {
//自己配,在OkhttpClient中配置
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
...
}
先看下最后一行chain.proceed(),确认下执行的流程。在proceed方法中,获取了index+1开始的新的RealInterceptorChain,获取了当前index的Interceptor, interceptors.get(index);然后执行当前Interceptord intercept方法,并把chain当做参数传入。这样每一个Interceptor都持有这从他开始后面的所有Interceptor。
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
下面看下getResponseWithInterceptorChain中具体的Interceptor
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
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));
先看下RealInterceptorChain的proceed方法
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
...
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...
// Call the next interceptor in the chain.
//注意这里的index + 1
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//获取当前拦截器
Interceptor interceptor = interceptors.get(index);
//执行当前拦截器的intercept方法,并传进去新创建的RealInterceptorChain,指针指向下一个拦截器
Response response = interceptor.intercept(next);
...
return response;
}
}
看具体的拦截器时,重点关注intercept方法的执行,及proceed方法的调用。
1. retryAndFollowUpInterceptor
interceptors.add(retryAndFollowUpInterceptor);
Okhttp有三个关键概念
Call:
访问url时,进行一个request,返回一个response,这就是一个call
connection:
我现在和服务器之间的一个socket连接
stream:
我现在做一次请求可能给服务端发消息,可能从服务端接收消息,这样一个对是stream
连接:到远程服务器的物理套接字连接。
这些连接建立起来可能很慢,因此有必要能够取消当前正在连接的连接。
流:位于连接上的逻辑HTTP请求/响应对。
每个连接都有其自己的分配限制,该限制定义了该连接可以承载的并发流数。 HTTP / 1.x连接一次只能传送1个流,HTTP / 2通常会传送多个流。
调用:流的逻辑顺序,通常是初始请求及其后续请求。
我
们希望将单个呼叫的所有流都保留在同一连接上,以实现更好的行为和局部性。
retryAndFollowUpInterceptor 不需要前置工作,所以直接调用proceed
realChain.proceed前是前置工作,后面是后置工作
是一个链式工作结构。
一层一层的对Request进行包装并发给下一层,返回时一层一层的对Response进行处理并返回给上一层
Response proceed(Request request)
RetryAndFollowUpInterceptor: 主要是处理后置逻辑,处理失败重试
@Override public Response intercept(Chain chain) throws IOException {
...
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
...
//在满足重试条件的情况,会一直触发重试
while (true) {
...
try {
//调用下一个拦截器的intercept方法
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will ...
//如果重定向返回的不为空,则把request进行替换,进行循环
Request followUp = followUpRequest(response, streamAllocation.route());
...
request = followUp;
priorResponse = response;
}
}
2.BridgeInterceptor
interceptors.add(new BridgeInterceptor(client.cookieJar()));
BridgeInterceptor ,是从应用程序代码到网络代码的桥梁,准备要发送的数据,和后续返回数据的解压缩。
数据发送前的准备工作,根据发送请求时创建的Request对象中配置的内容,给request添加header,对于没有配置的header字段添加默认字段。
给request添加header:
- Content-Type
- Content-Length
- Transfer-Encoding
- Host
- Connection keep-alive
- Accept-Encoding gzip 支持的压缩方式
- Cookie 从Cookiejar中取的cookie
- User-Agent
proceed方法后是对返回回来的数据进行处理,会处理返回的cookie, 解压缩数据,放到body中。
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
//内容类型
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
//内容长度
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
//添加host
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
//编码格式,默认添加gzip,表示可以接收gzip格式的数据,okhttp本身对gzip做了支持,节省带宽
requestBuilder.header("Accept-Encoding", "gzip");
}
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
//需要自己配置cookiejar
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//前面是前置工作
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//如果是gzip ,接收数据后,判断格式后,支持解压缩,如果我们要增加自己的压缩方式,需要添加新的intercepter
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
[待]自己实现CookieJar
CacheInterceptor
这个需要细看,面试会问
处理来自缓存的请求并将响应写入缓存,缓存的数据是解压缩前的
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
先看 CacheStrategy
CacheStrategy.Factory
会根据收到的cache的日期阿,最后修改时间
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
only-if-cached
CacheStrategy类
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
onlyIfCached:
该字段的名称“only-if-cached”具有误导性。它实际上意味着“不要使用网络”。它是由客户端设置的,该客户端只希望在缓存完全满足时才发出请求。如果设置了此标头,则不允许需要验证(即条件获取)的缓存响应。
CacheInterceptor
//配置了onlyIfCached 为true
// If we're forbidden from using the network and the cache is insufficient, fail.
//如果我们被禁止使用网络并且缓存不足,失败。
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//满足缓存条件,缓存不为空,则返回缓存的数据
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
...
如果满足缓存条件,则不继续进行网络请求了,直接将缓存的数据进行返回。
ConnectInterceptor
不花时间读不懂。这里是和tcp/https/ssl做交互了
总结 ConnectInterceptor 中干的事情就是建立tcp连接或者在tcp连接上面在叠加一个tls连接。没有后置工作
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
//我们需要网络来满足这个要求。可能用于验证有条件的GET。
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//下方两行为关键
//HttpCodec 编码解码器
//创建连接
//主要核心就是下面这一行 newStream
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
看streamAllocation.newStream 和tcp/ssl 交互 httpCodec是编码解码器
newStream方法中 的findHealthyConnection 尝试获取一个可用的connection
再进入findConnection方法中的result.connect line258
进入,找到connectSocket 和tcp连接的就是socket
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
进入connect
//socket 和tcp连接的
connectSocket(connectTimeout, readTimeout, call, eventListener);
==进入connectSocket==,到这步连接就建立完成了 这里可以看出okhttp自己做了**==tcp连==**接,没有使用httpclient httpurlconnection
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 {
//
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
往回倒退
可以看到是先连接socket,再https握手
接着看刚才的connect方法
//protocal 是http http1.1
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
if (route.address().sslSocketFactory() == null) {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
return;
}
eventListener.secureConnectStart(call);
//看这行 真正建立https连接的地方 进入
connectTls(connectionSpecSelector);
eventListener.secureConnectEnd(call, handshake);
if (protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.pingIntervalMillis(pingIntervalMillis)
.build();
http2Connection.start();
}
}
之前的配置在这里很多都用到了,建立一个tls连接 (https连接)
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// Configure the socket's ciphers, TLS versions, and extensions.
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
// Force handshake. This can throw!
//关注
sslSocket.startHandshake();
// block for session establishment
SSLSession sslSocketSession = sslSocket.getSession();
if (!isValid(sslSocketSession)) {
throw new IOException("a valid ssl session was not established");
}
Handshake unverifiedHandshake = Handshake.get(sslSocketSession);
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
}
// Check that the certificate pinner is satisfied by the certificates presented.
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}
总结 ConnectInterceptor 中干的事情就是建立tcp连接或者在tcp连接上面在叠加一个tls连接。没有后置工作
CallServerInterceptor
最终的,不需要proceed交给下一个节点了,只需要返回
这里做的都是**==实质工作==**,写完数据再读响应的数据,最后返回response,执行前面的后置方法
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
//编码解码器的工作 写请求头 看着行 GET/1.1
//直接写到网络上,发送到了网络对岸
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
//往socket里写请求行 往网络
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
retryAndFollowUpInterceptor中有try/catch ,底层出现错误可以直接抛异常,由这个拦截器接收到会,回调给上层
retry
把每个拦截器用I 表示,整体就是一个chain
前置proceed执行完,执行后置的
生产汉堡的例子,
ssl和tls只是换了主人之后不同的名字