概述
HTTP是现代应用常用的一种应用程序与服务器交换网络数据的方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:
- 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接;
- 连接池减少请求时握手延时;
- 透明的GZIP压缩减少响应数据的大小;
- 缓存响应内容,避免一些完全重复的请求;
当网络出现问题的时候OkHttp会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试来接你配置的其他IP地址,OkHttp默认使用现代TLS技术(TLS 1.3,ALPN,certificate pinning)初始化新的连接,当握手失败时会回退到TLS 1.0,这样做能实现更广泛的连接。
OSI和TCP参考模型图
总体流程
整体流程:OkHttpClient构建的Request转换为Call,然后在RealCall中进行异步(AsyncCall)或同步任务,最后通过一系列的拦截器(interceptor)发出网络请求然后递归返回的response,大概就是这么个流程,,下面是OKHttp总体请求的流程时序图,包括同步和异步请求:
上面的时序图是包括同步和异步的请求,而同步和异步请求的区别在于异步会开启线程池去请求服务器,其后面的逻辑就是基本一致都是调用getResponseWithInterceptorChain方法通过责任链模式的方式分别去通过每个链的节点处理Request,经过前面的节点处理过的Request会交给最后一个节点CallServerInterceptor去做真正的请求服务器的处理,最终拿到Response然后经过每个节点的处理,最终返回给client。
我们这里先看一个简单的同步的GET请求。
public class GetExample {
OkHttpClient client = new OkHttpClient.Builder()
.dispatcher(
new Dispatcher()
).build();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
public static void main(String[] args) throws IOException {
GetExample example = new GetExample();
String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
System.out.println(response);
}
}
Okhttp的请求无非就是构造OkHttpClient 和Request,然后直接发起请求,Response response = client.newCall(request).execute()这是一个同步的请求,会阻塞当前线程,并返回Response,那么看看client.newCall(request).execute()到底做了什么?
//OkHttpClient
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//RealCall
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.transmitter = new Transmitter(client, call);
return call;
}
开始OkHttpClient .newCall方法创建Call,Call是一个接口,它的实现类是RealCall,接着RealCall调用execute方法,代码如下所示。
//RealCall
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
client.dispatcher().executed(this);
return getResponseWithInterceptorChain();//拦截器链的开始点
} finally {
client.dispatcher().finished(this);
}
}
在execute方法中,首先判断该Call是否已经被执行了,如果已经被执行将抛出异常,然后调用dispatcher的executed方法,将call放入Dispatcher的同步队列runningSyncCalls中,接着在finish块中将call移除runningSyncCalls,即请求结束,那整个网络请求的一系列操作到底在哪,接着看RealCall.getResponseWithInterceptorChain方法?
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
//重试和重定向的拦截器
interceptors.add(new RetryAndFollowUpInterceptor(client));
//处理请求头的拦截器,如gzip
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, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//拦截器链的开始
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
在getResponseWithInterceptorChain方法中,主要是构造拦截器链RealInterceptorChain,紧接着 调用chain.proceed(originalRequest)
方法开始执行拦截器链,代码如下。
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, transmitter, exchange);
}
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);
//省略很多代码..................
//一条链节点结束
return response;
}
可看到在proceed方法中,又重新构造新的拦截器链RealInterceptorChain实例next ,接着取出拦截器并调用了interceptor方法,并将next 传进去,我们选择第一个拦截器RetryAndFollowUpInterceptor
看看intercept方法到底做了什么?
RetryAndFollowUpInterceptor 负责失败重试以及重定向
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
//重复请求响应,如果是重定向,就会有这个priorResponse,HTTP redirect or authorization
Response priorResponse = null;
while (true) {
//预创建一个stream来承载request。 如果已经存在了connection,则优先使用现有connection。
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
//为true说明建立连接是成功的,并且返回了response
boolean success = false;
try {
//当执行后面的操作抛出异常,可能我们需要重试连接
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
//如果是Router连接失败,那么请求将不会再发送,直接把异常跑到上层应用
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
//那么继续重新连接
continue;
} catch (IOException e) {
// 与服务器尝试通信失败,请求不会再发送。
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//判断需不需要重试
if (!recover(e, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// //抛出未检查的异常,释放资源.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 满足先前存在的priorResponse,也就是第一次请求服务器时,服务器返回的响应就仅仅是状态码没有任何的数据。这样的response从来就没有body
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
/**
* 计算出响应接收userResponse的HTTP请求。
* 这将添加身份验证标头,遵循重定向或处理客户端请求超时。
* 如果后续操作不必要或不适用,则返回null。
*
* 注释说那么多,其实就是根据Http状态码决定重定向
*/
Request followUp = followUpRequest(response, route);
//不需要重定向
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
1、第一步,根据 response = realChain.proceed(request, transmitter, null);中直接调用了下一个拦截器,然后捕获可能的异常来进行相应的重试操作或者请求失败。
2、根据第一步请求得到response,调用Request followUp = followUpRequest(response, route);followUp是Request类型,判断followUp 是否有重定向的操作,即followUp 不为null,就是有重定向,如果为null,说明不需要重定向,直接返回response。
3、根据第二步,followUp 如果不为null,说明需要进行重定向,并把response赋值给priorResponse和把followUp赋值给request,而priorResponse是指上一次循环的响应,在方法体最下面有赋值。主要是为了重定向。如果发现上一次request的响应有重定向,则给当前响应的priorResponse赋值,并且把response的body置为空,接着继续下一步的请求。
BridgeInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//检查request。将用户的request转换为发送到server的请求
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
//自动增添加请求头 Content-Type
requestBuilder.header("Content-Type", contentType.toString());
}
//请求体内容的长度
long contentLength = body.contentLength();
//如果传输长度不为-1,则表示完整传输
if (contentLength != -1) {
//设置头信息 Content-Length
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
//如果传输长度为-1,则表示分块传输,自动设置头信息
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
//如果没有设置头信息 Connection,则自动设置为 Keep-Alive
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
//如果我们添加“Accept-Encoding:gzip”头字段,我们还负责解压缩传输流。实际上默认Okhttp已经添加的,告诉服务器接收数据使用gzip
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
//如果我们没有在请求头信息里增加Accept-Encoding,在这里会自动设置头信息 Accept-Encoding = gzip
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//Cookie的处理
List cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent);
}
//执行下一步操作,得到Response
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//如果response的头信息里Content-Encoding = gzip,并且我们没有手动在请求头信息里设置 Accept-Encoding = gzip,
// 则会进行 gzip 解压数据流
if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
//使用了GzipSource
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();
}
在BridgeInterceptor中主要是对request和response的header做处理,比如对request body做gzip压缩,这里需要注意的是,如果我们在代码里没有手动设置 Accept-Encoding = gzip ,那么 OkHttp 会自动处理 gzip 的解压缩;反之,你需要手动对返回的数据流进行 gzip 解压缩。
CacheInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
//获取上一次缓存的response,有可能为null
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
//获取到当前时间
long now = System.currentTimeMillis();
//long nowMillis, Request request, Response cacheResponse
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//networkRequest 不为null,说明强制缓存规则不生效
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.
//从缓存策略上来说强制缓存生效,应该直接取上次缓存结果,但由于未知原因缓存的结果没有了或者上次返回就没有,这里直接返回了失败的Response
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.
//强制缓存生效,按道理来说我们直接返回上次的cacheResponse就可以了,
// 当然这里对Response的cacheResponse的body做了置空处理。
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're doing a conditional get.
//按照对比缓存规则,取完之后应该判断是否返回304,如果是应该使用缓存结果:
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);
}
//过滤不能做缓存的请求method
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
http缓存分为两种:
- 一种强制缓存,一种对比缓存,强制缓存生效时直接使用以前的请求结果,无需发起网络请求。
- 对比缓存生效时,无论怎样都会发起网络请求,如果请求结果未改变,即服务端会返回304,但不会返回数据,数据从缓存中取,如果改变了会返回数据,那么就要对比缓存了。
在CacheInterceptor中,networkRequest如果不为null,则说明强制缓存生效,也就意味着这次请求直接使用上次的结果,无需发起网络请求。而cacheResponse则是上次的缓存结果。整个缓存处理其实就是这两种情况的组合,当然这里就不展开讲,因为我们是将的请求过程。
ConnectInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
代码很少,其实它所有的连接逻辑都放到了transmitter.newExchange方法中,这里细节有点复杂,反正就是建立socket连接各种机制,这会在下一篇文章中讲,这里不展开讲。
CallServerInterceptor
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//封装请求头,即conent-length,method,编码等等
Exchange exchange = realChain.exchange();
Request request = realChain.request();
//当前时间
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
/**
* http 100-continue用于客户端在发送POST数据给服务器前,征询服务器情况,
* 看服务器是否处理POST的数据,如果不处理,客户端则不上传POST数据,如果处理,
* 则POST上传数据。在现实应用中,通过在POST大数据时,才会使用100-continue协议。
*
* 如果客户端有POST数据要上传,可以考虑使用100-continue协议。加入头{"Expect":"100-continue"}
* 如果没有POST数据,不能使用100-continue协议,因为这会让服务端造成误解。
* 并不是所有的Server都会正确实现100-continue协议,如果Client发送Expect:100-continue消息后,在timeout时间内无响应,Client需要立马上传POST数据。
* 有些Server会错误实现100-continue协议,在不需要此协议时返回100,此时客户端应该忽略。
*
*
*/
boolean responseHeadersStarted = false;
//当前request需要请求体,即post请求等方式,如果有,则进行封装
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// 判断服务器是否允许发送body
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//sink写入请求
exchange.flushRequest();
responseHeadersStarted = true;
//这里只是发送的header
exchange.responseHeadersStart();
//读取response
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
//向服务器发送requestbody,还判断了是否多路复用,不是全双工通讯,智能请求第一次就释放资源
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
//不是全双工通讯,智能请求第一次就释放资源
bufferedRequestBody.close();
}
} else {//不发送RequestBody
exchange.noRequestBody();
if (!exchange.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.
exchange.noNewExchangesOnConnection();
}
}
} else {//不发送RequestBody
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
//就是请求
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
//返回response
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
//100的状态码的处理继续发送请求,继续接受数据
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(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(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
//返回为空的处理
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
判断是否允许上传requestBody, 写入http请求头信息;
判断header中Expect域是否为100-continue,这个请求头字段的作用是在发送RequestBody前向服务器确认是否接受RequestBody,如果没有,则正常请求。如果有,则相当于一次简单的握手操作,则等待服务器返回的ResponseHeaders之后再继续,如果服务器接收RequestBody,会返回null;
如果RequestBuilder为null,说明Expect不为100-continue或者服务器同意接收RequestBody。这时就开始向流中写入RequestBody;
读取响应头信息 构建Response,写入原请求、握手情况、请求时间、得到结果的时间等;
针对204/205状态码处理;
最后就结束了,可能后面还会两篇文章介绍CacheInterceptor和ConnectionInterceptor这两个拦截器,看一下整个网络请求的时序图:
这张图是画的同步请求的,其实同步和异步请求其实请求过程的处理是一样的,只不过异步请求前期会做一些并发的处理,其余在拦截器那部分都是一样的,感兴趣的话,可以去看看执行异步的源码,谢谢。