上节我们讲解了Okhttp的简单介绍,请求任务的分发,以及请求响应的拦截。现在我们分析数据的请求是如何进行的。
在阅读http请求数据之前,你可能需要了解http和tcp相关的知识。
http原理
http://www.jianshu.com/p/2efddfaea9c3
http://www.jianshu.com/p/26095e423da0https原理
http://www.jianshu.com/p/33feb2fadb15
TCP/IP详解
本节主要介绍以下内容
- 数据的请求响应大致流程
- 发送请求的过程
- 读取响应的过程
- 输出请求头
- 输出请求体
数据的请求响应大致流程
数据的请求响应操作,是从RealCall的getResponse方法开始的。整个过程是,创建HttpEngine对象,然后在while循环中进行发送请求,读取响应,获取响应数据,判断响应数据是否重定向,身份验证或请求超时等情况,如果是,再将这些响应数据加入请求数据中重新请求操作,直到得到最终的响应数据,或者请求次数超限。同时,如果路由失败或者发送IO异常等情况时,继续重试。
final class RealCall implements Call {
/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forWebSocket) throws IOException {
// 这里负责修正原始的request信息,使request符合请求的格式要求
RequestBody body = request.body();
if (body != null) {
Request.Builder requestBuilder = request.newBuilder();
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");
}
request = requestBuilder.build();
}
// 这里创建请求执行的引擎,交给它去请求数据,同时会去处理重试和重定向情况。
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
int followUpCount = 0;
while (true) {
//请求任务标记取消的话,释放分配的数据,抛异常。
if (canceled) {
engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
boolean releaseConnection = true;
try {
//发送请求
engine.sendRequest();
//读取响应数据
engine.readResponse();
releaseConnection = false;
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// 路由失败,在while循环中重试
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e.getLastConnectException();
} catch (IOException e) {
// 发送IO异常,在while循环中重试
HttpEngine retryEngine = engine.recover(e, null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e;
} finally {
//最后关闭本次请求分配的流数据
if (releaseConnection) {
StreamAllocation streamAllocation = engine.close();
streamAllocation.release();
}
}
//获取响应数据,如果不是重定向等情况,就返回它作为最终的响应数据
Response response = engine.getResponse();
Request followUp = engine.followUpRequest();
if (followUp == null) {
if (!forWebSocket) {
engine.releaseStreamAllocation();
}
return response;
}
StreamAllocation streamAllocation = engine.close();
//超过最大重定向跟踪次数,释放报异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//重定向之后的url和之前请求的url不一致,释放当前的流分配数据
if (!engine.sameConnection(followUp.url())) {
streamAllocation.release();
streamAllocation = null;
}
//重新创建HttpEngine,附带之前的streamAllocation和response响应数据,进行新的请求,重定向或身份验证等情况时需要用到。
request = followUp;
engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
response);
}
}
}
我们看到了其中捕获了RouteException路由异常,在里面进行了切换路由重试的操作,以及处理了IOException异常,这正是Okhttp支持路由切换,出现连接问题时自动重试的原理了。
接下来我们分析数据的请求过程
发送请求的过程
从上面我们看出,发送请求是在HttpEngine.sendRequest()中进行的。这里先提前总结它的流程,再配合代码分析。
- 修正填充请求的Request数据
- 从缓存中获取Request请求对应的响应数据,构建缓存策略CacheStrategy,通过缓存策略计算是使用缓存数据还是继续网络请求。
- 缓存的处理我们后面再讲
- 如果使用缓存数据,则直接返回。如果不使用网络请求,且没有缓存数据,则返回不合理请求响应。
- 如果使用网络请求,则开始获取连接,取得HttpStream流对象。
- HttpStream的实现分为Http1xStream和Http2xStream。Http1xStream是针对http 1.1/1.0协议连接的处理http流对象,Http2xStream是针对http 2/SPDY协议连接的处理http流对象
- 如果请求中存在请求体,且请求方法时Post,put等方法,则使用HttpStream对象开始写入请求头数据,创建请求体输出对,否则就返回,例如get请求,这一步就不做操作。
/**
* Figures out what the response source will be, and opens a socket to that source if necessary.
* Prepares the request headers and gets ready to start writing the request body if it exists.
*
* @throws RequestException if there was a problem with request setup. Unrecoverable.
* @throws RouteException if the was a problem during connection via a specific route. Sometimes
* recoverable. See {@link #recover}.
* @throws IOException if there was a problem while making a request. Sometimes recoverable. See
* {@link #recover(IOException)}.
*/
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
//在请求数据中填充一些默认请求头和cookies
Request request = networkRequest(userRequest);
//获取内部缓存Cache,查找请求对应的响应数据
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
//构建缓存策略类,由策略类判定是采用网络请求还是使用缓存
long now = System.currentTimeMillis();
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
networkRequest = cacheStrategy.networkRequest;
cacheResponse = cacheStrategy.cacheResponse;
if (responseCache != null) {
responseCache.trackResponse(cacheStrategy);
}
//如果不采用缓存,又存在缓存,则关闭缓存
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) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
//如果采用缓存,则用缓存数据构建响应,并进行GZIP解压缩
// If we don't need the network, we're done.
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean success = false;
try {
//获取可用的TCP连接,并在此之上创建HttpStream流对象
httpStream = connect();
httpStream.setHttpEngine(this);
//如果请求中存在请求体,且请求方法是Post,put等方法,则使用HttpStream对象开始写入请求头数据
if (writeRequestHeadersEagerly()) {
//获取请求体的大小
long contentLength = OkHeaders.contentLength(request);
//bufferRequestBody为true表示请求体在输出之前要完整的缓存,false表示请求体可以以流的形式输出
if (bufferRequestBody) {
//完成缓存,不能超过最大限制
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if (contentLength != -1) {
//对于已知请求体大小的情况,输出请求头,创建指定大小的写缓存对象
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
//对于未知请求体大小的情况,不写请求体,先创建一个写缓存对象,待后期指定大小。
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
//输出请求头,并创建可实现流输出的输出对象
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}
接下来我们看看读取响应的过程
读取响应的过程
读取响应是在HttpEngine.readResponse()中进行的,这里总结它的流程。
- 如果userResponse响应数据不为空,表示已经读取过了,直接返回, 如果网络请求和缓存响应为空,表示读取响应之前没有去请求,抛异常。如果网络请求为空,直接返回。接下来执行真正的读取响应操作。
- 如果是WebSocket,输出请求头数据,然后读取响应数据。
- WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
- 如果不是WebSocket,并且不需要写请求体,例如get请求,则交给NetworkInterceptorChain网络拦截链进行处理。
- 如果不是WebSocket,需要写请求体,则交给httpStream写请求头,然后写请求体,然后读取响应数据。
- 保存请求头数据到cookies中。
- 如果存在有效的缓存数据的话,更新该缓存数据。
/**
* Flushes the remaining request header and body, parses the HTTP response headers and starts
* reading the HTTP response body if it exists.
*/
public void readResponse() throws IOException {
//已经读取过了,返回
if (userResponse != null) {
return; // Already ready.
}
//没有预先请求,抛异常
if (networkRequest == null && cacheResponse == null) {
throw new IllegalStateException("call sendRequest() first!");
}
//没有请求网络,返回
if (networkRequest == null) {
return; // No network response to read.
}
Response networkResponse;
if (forWebSocket) {
//如果是WebSocket,写请求头,读取网络响应数据
httpStream.writeRequestHeaders(networkRequest);
networkResponse = readNetworkResponse();
} else if (!callerWritesRequestBody) {
//如果不需要写请求体,交给NetworkInterceptorChain网络拦截链进行处理
networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
} else {
//需要写请求体
// Emit the request body's buffer so that everything is in requestBodyOut.
if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
bufferedRequestBody.emit();
}
// Emit the request headers if we haven't yet. We might have just learned the Content-Length.
if (sentRequestMillis == -1) {
if (OkHeaders.contentLength(networkRequest) == -1
&& requestBodyOut instanceof RetryableSink) {
long contentLength = ((RetryableSink) requestBodyOut).contentLength();
networkRequest = networkRequest.newBuilder()
.header("Content-Length", Long.toString(contentLength))
.build();
}
//交给httpStream写请求头
httpStream.writeRequestHeaders(networkRequest);
}
// Write the request body to the socket.
if (requestBodyOut != null) {
if (bufferedRequestBody != null) {
// This also closes the wrapped requestBodyOut.
bufferedRequestBody.close();
} else {
requestBodyOut.close();
}
//如果请求体输出不为空,则写请求体输出数据
if (requestBodyOut instanceof RetryableSink) {
httpStream.writeRequestBody((RetryableSink) requestBodyOut);
}
}
//读取响应数据
networkResponse = readNetworkResponse();
}
//保存请求头数据到cookies中
receiveHeaders(networkResponse.headers());
//如果有缓存响应数据,取得缓存响应数据
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
releaseStreamAllocation();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
InternalCache responseCache = Internal.instance.internalCache(client);
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, stripBody(userResponse));
userResponse = unzip(userResponse);
return;
} else {
closeQuietly(cacheResponse.body());
}
}
//将缓存响应数据和网络请求的响应数据一起构建用户请求响应
userResponse = networkResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果有响应体数据,则缓存起来,并重新构建用户请求响应
if (hasBody(userResponse)) {
maybeCache();
userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
}
}
接下来分析NetworkInterceptorChain是怎么处理网络请求的。我们分析NetworkInterceptorChain的proceed方法。
- 遍历它链上的所有网络拦截器,对请求和响应进行一层层拦截。
- 在最里层网络请求中,调用httpStream对象写请求头数据,如果请求体有数据,再写请求体数据,然后readNetworkResponse读取网络响应数据。
接下来我们分析请求头数据的写入过程。
输出请求头
分析Http1xStream的writeRequestHeaders方法,简单说就是将请求行和请求头数据写入到输出流中。
/**
* Prepares the HTTP headers and sends them to the server.
*
* <p>For streaming requests with a body, headers must be prepared <strong>before</strong> the
* output stream has been written to. Otherwise the body would need to be buffered!
*
* <p>For non-streaming requests with a body, headers must be prepared <strong>after</strong> the
* output stream has been written to and closed. This ensures that the {@code Content-Length}
* header field receives the proper value.
*/
@Override public void writeRequestHeaders(Request request) throws IOException {
//记录发送请求的时间
httpEngine.writingRequestHeaders();
//生成请求行
String requestLine = RequestLine.get(
request, httpEngine.getConnection().route().proxy().type());
//将请求行和请求头一并写入到输出流中
writeRequest(request.headers(), requestLine);
}
再分析Http2xStream的writeRequestHeaders方法,根据是HTTP2还是SPDY协议得到相应请求头,然后交给FrameConnection(帧连接)去创建一个FrameStream(帧流),然后交给FrameWriter去输出请求头到帧流中。简单介绍下http2和SPDY中流和帧的概念。
http2和SPDY中流和帧的概念
Stream Identifier定义了二进制帧的格式,http2连接上传输的每个帧都关联到一个“流”。流是一个逻辑上的联合,一个独立的,双向的帧序列。这一系列帧在客户端和服务器中通过http2连接进行交换。每个单独的http2连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。流的多路复用意味着在同一连接中来自各个流的数据包被混合在一起。两个(或者更多)独立的“数据列车”被拼凑到了一辆列车上,最终在终点站被分开。
参考 https://www.kancloud.cn/kancloud/http2-explained/49803
public final class Http2xStream implements HttpStream {
@Override public void writeRequestHeaders(Request request) throws IOException {
if (stream != null) return;
//记录发送请求的时间
httpEngine.writingRequestHeaders();
boolean permitsRequestBody = httpEngine.permitsRequestBody(request);
//判断是http2还是SPDY协议,得到相应的请求头
List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
? http2HeadersList(request)
: spdy3HeadersList(request);
boolean hasResponseBody = true;
//创建FramedStream帧流,并输出请求头数据到帧流
stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
stream.readTimeout().timeout(httpEngine.client.readTimeoutMillis(), TimeUnit.MILLISECONDS);
stream.writeTimeout().timeout(httpEngine.client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
}
}
public final class FramedConnection implements Closeable {
private FramedStream newStream(int associatedStreamId, List<Header> requestHeaders, boolean out,
boolean in) throws IOException {
boolean outFinished = !out;
boolean inFinished = !in;
FramedStream stream;
int streamId;
synchronized (frameWriter) {
synchronized (this) {
if (shutdown) {
throw new IOException("shutdown");
}
streamId = nextStreamId;
nextStreamId += 2;
//创建帧流
stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
if (stream.isOpen()) {
streams.put(streamId, stream);
setIdle(false);
}
}
if (associatedStreamId == 0) {
//通过frameWriter将请求头数据写入到streamId指定的帧流中
frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
requestHeaders);
} else if (client) {
throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
} else { // HTTP/2 has a PUSH_PROMISE frame.
//通过frameWriter将请求头数据写入到streamId指定的帧流中
frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
}
}
if (!out) {
//输出缓存的数据帧数据到帧流中
frameWriter.flush();
}
return stream;
}
}
接着分析写请求体的过程
输出请求体
分析Http1xStream的writeRequestBody方法,很简单,直接将数据写入到流中。
@Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READ_RESPONSE_HEADERS;
requestBody.writeToSocket(sink);
}
再分析Http2xStream的writeRequestBody方法,很简单,同样直接将数据写入到流中。
@Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
requestBody.writeToSocket(stream.getSink());
}
下节,我们分析网络连接的获取,创建,管理,缓存回收等。将会涉及RealConnection、ConnectionPool、StreamAllocation这几个核心的类。