系列文章
OKHttp源码解析(2)----拦截器RetryAndFollowUpInterceptor
OKHttp源码解析(3)----拦截器BridgeInterceptor
OKHttp源码解析(4)----拦截器CacheInterceptor
OKHttp源码解析(5)----拦截器ConnectInterceptor
OKHttp源码解析(6)----拦截器CallServerInterceptor
1.简介
This is the last interceptor in the chain. It makes a network call to the server. 这是链中最后一个拦截器,它向服务器发起了一次网络访问
请求服务拦截器,负责向服务器发送请求数据、从服务器读取响应数据
2.源码解析
2.1流程
@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在请求头中,那么要等服务器的响应。100-continue协议需要在post数据前,
征询服务器情况,看服务器是否处理POST的数据
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//发送100-continue的请求
httpCodec.flushRequest();
//如果有中间级的响应,返回null
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 {
//设置响应body,此时并没有读取socket中的输入流
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;
}
复制代码
首先我们要明确Source和Sink是什么: 在JAVA IO中有输入流和输出流,在OKio的IO体系中,Sourc就表示输入流,Sink表示输出流。OKio的IO体系我们将在新的一章中进行讲解,本章不进行展开。
- Source: 输入流,这是一个接口类,其子类有:BufferedSource、PeekSource、ForwardingSource、GzipSource、InputStreamSource、InflaterSource
- Sink: 输出流 ,接口类,其子类有:BufferedSink、ForwardingSink、GzipSink、OutputStreamSink、DeflaterSink、BlackholeSink
在本流程中,主干逻辑有以下4步:
- httpCodec.writeRequestHeaders(request)
- httpCodec.createRequestBody(request, contentLength)
- httpCodec.readResponseHeaders(true)
- httpCodec.openResponseBody(response)
以上4步分别完成了请求头的写入,请求体的写入,响应头的读取,响应体的读取。
2.2流的读取
当时看这里的时候,有个疑惑点,网络请求的数据及输入流中的数据在哪里,其实在第4步中获取到响应的body信息,只是获取一个流对象,只有在应用代码中调用流对象的读方法或者response.body().string()方法等,才会从socket的输入流中读取信息到应用的内存中使用。下面我们来分析一下流的读取过程。
response.body().string()
复制代码
ResponseBody:
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
复制代码
从源码可以看到source.readString(charset);将流转换为String进行输出。下面我们分两步来讨论:
- source的来源
- source.readString()之后的过程
2.2.1 source的来源
source来自于方法source(),ResponseBody中source()是个抽象方法:
public abstract BufferedSource source();
复制代码
其实现类为RealResponseBody:
@Override public BufferedSource source() {
return source;
}
复制代码
方法中的source为构造方法中传递过来的,创建RealResponseBody的是CallServerInterceptor源码中的httpCodec.openResponseBody(response),看一下httpCodec.openResponseBody()的源码:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
......
if (!HttpHeaders.hasBody(response)) {
Source source = newFixedLengthSource(0);
return new RealResponseBody(contentType, 0, Okio.buffer(source));
}
......
}
复制代码
查看newFixedLengthSource(0)方法:
public Source newFixedLengthSource(long length) throws IOException {
if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READING_RESPONSE_BODY;
return new FixedLengthSource(length);
}
复制代码
查看FixedLengthSource()类的read方法:
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (bytesRemaining == 0) return -1;
long read = super.read(sink, Math.min(bytesRemaining, byteCount));
if (read == -1) {
ProtocolException e = new ProtocolException("unexpected end of stream");
endOfInput(false, e); // The server did not supply the promised content length.
throw e;
}
bytesRemaining -= read;
if (bytesRemaining == 0) {
endOfInput(true, null);
}
return read;
}
复制代码
read方法是将source数据写入Buffer中,我們來看source的来源,继续查看父类AbstractSource中的super.read():
@Override public long read(Buffer sink, long byteCount) throws IOException {
try {
long read = source.read(sink, byteCount);
if (read > 0) {
bytesRead += read;
}
return read;
} catch (IOException e) {
endOfInput(false, e);
throw e;
}
}
复制代码
查找source.read(sink, byteCount)中的source的来源,这个source是Http1Codec构造方法传递进行的:
public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source,
BufferedSink sink) {
this.client = client;
this.streamAllocation = streamAllocation;
this.source = source;
this.sink = sink;
}
复制代码
经过上溯代码,我们发现创建Http1Codec是在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, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
复制代码
streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
....
try {
//创建连接connection,根据socket创建输入流sink和输出流source
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//创建HttpCodec,有Http1Codec和Http2Codec两个子类
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
...
}
}
复制代码
其中findHealthyConnection创建了source,resultConnection.newCodec创建了Http1Codec:
-
findHealthyConnection-->findConnection-->result.connect(RealConnection)-->connectSocket()--> source = Okio.buffer(Okio.source(rawSocket))
-
newCodec
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, chain, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(chain.readTimeoutMillis());
source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
复制代码
小结:
okhttp在ConnectInterceptor连接拦截器中,根据socket创建了网络设施connection,输出流sink和输入流source。并在CallServerInterceptor请求服务器拦截器中完成了请求的发送和响应头的读取,此时并没有读取响应体即我们关心的网络请求结果。响应体的读取是用户拿到response后,使用response.body().string()从socket中读取的。
2.2.2 source.readString()之后的过程:
source我们找到了,是RealBufferedSource类包含的引用了socket的source,那么我们在RealBufferedSource()中查看方法readString():
override fun readString(charset: Charset): String {
buffer.writeAll(source)
return buffer.readString(charset)
}
复制代码
流的读取分为两步:
- 将soucre写入缓存buffer
- 从缓存中把数据读取出来,返回。
本章在追源码,写的有点乱,开发者在看的时候,还是需要去读一下源码,自己跟踪一下。下一章将学习一下OKio的IO体系,结合OKio将对okhttp有更深的理解。OKio源码解析