系列文章
OKHttp源码解析(2)----拦截器RetryAndFollowUpInterceptor
OKHttp源码解析(3)----拦截器BridgeInterceptor
OKHttp源码解析(4)----拦截器CacheInterceptor
OKHttp源码解析(5)----拦截器ConnectInterceptor
OKHttp源码解析(6)----拦截器CallServerInterceptor
1.简介
Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient,HttpURLConnection相对来说比HttpClient难用,google自从2.3版本之后一直推荐使用HttpURLConnection,并且在6.0版本的sdk中直接删掉了HttpClient类。Android4.4的源码中可以看到OkHttp替换了HttpURLConnection
相比以上两种通信类,OKHttp有巨大的优势:
- 支持HTTP/2,HTTP/2通过使用多路复用技术,在一个单独的TCP链接上支持并发,通过在一个连接上一次性发送多个请求来发送或接收数据
- 如果HTTP/2不可用,链接池减少请求延迟
- 支持GZIP,额可以压缩下载体积
- 响应缓存可以避免重复请求网络
- 会从很多常用的连接问题中自动恢复,如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OKHttp会尝试下一个IP地址
- OKHttp还处理了代理服务器问题和SSL握手失败问题
流程图:
2.使用
2.1 构建OkHttpClient
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher; //调度器,主要用于异步网络请求的控制
this.proxy = builder.proxy; //代理
this.protocols = builder.protocols; //协议
this.connectionSpecs = builder.connectionSpecs; //指定配置(tls版本、密码组等)
this.interceptors = Util.immutableList(builder.interceptors); //用户自定义拦截器
this.networkInterceptors = Util.immutableList(builder.networkInterceptors); //用户自定义网络拦截器
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar; //
this.cache = builder.cache; //缓存
this.internalCache = builder.internalCache; //内部缓存
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool; //链接池
this.dns = builder.dns; //dns
this.followSslRedirects = builder.followSslRedirects; //是否支持http与https之间互相重定向
this.followRedirects = builder.followRedirects; //否是支持重定向
this.retryOnConnectionFailure = builder.retryOnConnectionFailure; //连接失败重试
this.connectTimeout = builder.connectTimeout; //连接超时时间
this.readTimeout = builder.readTimeout; //读取超时时间
this.writeTimeout = builder.writeTimeout; //写入超时时间
this.pingInterval = builder.pingInterval;
}
2.2 get请求:
OkHttpClient client = new OkHttpClient();
public String get(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
2.3 post请求:
OkHttpClient client = new OkHttpClient();
public String post(String url,String json) throws IOException {
RequestBody body = RequestBody.create(JSONObject,json);
Request request = new Request.Builder()
.post(body)
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
3.流程
以下以同步的get请求来分析整体的请求过程
Response response = client.newCall(request).execute();
OkHttpClient 首先创建一个Call(RealCall):
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
查看RealCall的execute()方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this); //记录此请求
Response result = getResponseWithInterceptorChain(); //请求链及拦截器
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
在RealCall的execute()方法中,有两步比较重要:
client.dispatcher().executed(this);OKHttp的分发器在请求的开始会记录此次请求,请求结束后会移除。分发器在OKHttp的异步请求中比较中要,下面再讲。
Response result = getResponseWithInterceptorChain()OKHttp完整的网络请求就是在这步中进行的。下面我们来看下其中做了什么?
Response getResponseWithInterceptorChain() throws IOException {
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));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
OKHttp的完整请求就在这里,请求和返回数据的处理就是由这些拦截器来完成的,各个拦截器由RealInterceptorChain进行串联。如流程图图1。具体的串联实现,可以看下源码。
4.拦截器
OKHttp的拦截器主要是下面几种:
- client.interceptors()
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- client.networkInterceptors()
- CallServerInterceptor
4.1 client.interceptors()
APP层面的拦截器(Application Interception 应用拦截器),是在配置OKHttpClient时设置的interceptors
- 对请求参数进行统一的加密处理
- 拦截不符合规则的URL
- 对请求或者返回参数设置统一的编码方式
- 其他
4.2 RetryAndFollowUpInterceptor
重试与重定向拦截器,负责重试及重定向。现在我们来分析源码:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
//while循环进行重试,请求成功则返回response;请求失败,则对responseCode判断,并更改相应设置,进行重新请求。
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
//进行链式请求,并对请求结果进行判定是否重试
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 路由连接失败,请求将不会被发送
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 服务器连接失败,请求可能已被发送
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We are throwing an unchecked exception. Release any resources.
// 抛出未检查的异常,释放资源
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//对response进行判定,是否进行重定向重试
Request followUp = followUpRequest(response);
//followUp为空,表明不是需要重定向的responseCode,返回结果response(可能是请求成功,也可能是请求失败)
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//释放response.body()
closeQuietly(response.body());
//重定向超过最大次数(20次),抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// response 和 followUp 比较是否为同一个连接
// 若为重定向就销毁旧连接,创建新连接
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()), callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
// 将重定向操作得到的新请求设置给 request
request = followUp;
priorResponse = response;
}
}
4.3 BridgeInterceptor
桥接拦截器(内容拦截器),负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应。
@Override public Response intercept(Chain chain) throws IOException {
// 将用户友好的 request 构造为发送给服务器的 request
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) {
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 are responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 设置 cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
// 构造完后,将 request 交给下一个拦截器去处理。最后又得到服务端响应 networkResponse
Response networkResponse = chain.proceed(requestBuilder.build());
// 保存 networkResponse 的 cookie
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
// 将 networkResponse 构造为对用户友好的 response
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果 networkResponse 使用 gzip 并且有响应体的话,给用户友好的 response 设置响应体
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
4.4 CacheInterceptor
缓存拦截器,负责读取缓存直接返回、更新缓存。当网络请求有符合要求的Cache时,直接返回Cache。如果当前Cache失效,则删除。CacheStrategy:缓存策略,CacheStrategy类是一个非常重要的类,用于控制请求是网络获取还是缓存获取。
@Override public Response intercept(Chain chain) throws IOException {
// 得到 request 对应缓存中的 response
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());
}
}
// If we have a cache response too, then we are doing a conditional get.
// 如果我们同时有缓存和 networkResponse ,根据情况使用
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding
header (as performed by initContentStream()).
// 更新原来的缓存至最新
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 保存之前未缓存的缓存
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
4.5 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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
链接拦截器主要是完成了网络设施的建设,包含连接connection,根据socket生成输出流sink和输入流source。 后面的CallServerInterceptor使用这些设施完成请求的发送以及响应的读取。这些请求过程,我们将在CallServerInterceptor一章中详细解读。
4.6 client.networkInterceptors()
网络请求层面的拦截器,配置OkHttpClient时设置的networkInterceptors:
请求时:
- 可以修改OkHttp框架自动添加的一些属性
- 可以观察最终完整的请求参数
返回结果:
- 能够对中间的响应进行操作,比如重定向和重试
- 当发生网络短路时,不调用缓存的响应结果
- 监控数据,就像数据在网络上传输一样
- 访问承载请求的连接Connection。
4.7 CallServerInterceptor
请求服务拦截器,负责向服务器发送请求数据、从服务器读取响应数据
@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();
// 整理请求头并写入
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
// 检查是否为有 body 的请求方法
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we do not get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
// 如果有 Expect: 100-continue 在请求头中,那么要等服务器的响应
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
// 写入请求体
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} 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) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
// 如果为 web socket 且状态码是 101 ,那么 body 为空
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();
}
// 如果请求头中有 close 那么断开连接
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;
}
小结
本章主要讲了OkHttp的整体流程,并对源码的关键点进行了备注。详细的讲解在各个拦截器的单章讲解中,开发者可以在后面几章查看。
OkHttp使用分层的思想,每一个拦截器就是一个完成相关功能的任务层,并使用链Chain串联整个流程,使用了责任链模式。在整个链中,向用户提供了插入点,用户可以自定义拦截器,完成一些自定义的加密、数据统计等相关功能。