一、前言
【1.1】OkHttp系列其他篇章:
- 同步请求的实现流程。
- 异步请求的实现流程
- 重要拦截器:CacheInterceptor 的解析。
- 重要拦截器:ConnectInterceptor 的解析。
- 重要拦截器:CallServerInterceptor 的解析。
【1.2】陈述
经过前面几章的准备工作,我们终于可以和服务器进行正式的交流了。而与服务器进行正式的数据通信就发生在最后一个拦截器:ServerIntercepter.java。在解析这个类之前,需要先看一下其他的类:ExchangeCodec,这个类在上篇文章中就有涉及到,不过没有仔细讲下去。在这章中会讲一下。
二、ExchangeCodec
【2.1】简介
ExchageCodec是一个接口,他是来规范网络请求的编码行为和网络回复的解码行为。他有2个子类 Http1ExchangeCodec 和 Http2ExchangeCodec。从名字上一看就知道,他们分别对应了Http1协议和Http2协议。下面是ExchageCodec主要的接口规范:
public interface ExchangeCodec {
...
/** 将请求体转化为输出流*/
Sink createRequestBody(Request request, long contentLength) throws IOException;
/** 写请求头*/
void writeRequestHeaders(Request request) throws IOException;
/** 将在缓存区的请求刷新到输出流 */
void flushRequest() throws IOException;
/** 通知已经完成请求动作 */
void finishRequest() throws IOException;
/** 读取响应体 */
Source openResponseBodySource(Response response) throws IOException;
/** 读取响应头 */
@Nullable Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
...
/** 取消请求 */
void cancel();
}
总结: 总的来说,ExchageCodec.java规范了网络交互过程中的写请求和读响应的动作。具体的如下:
- 将请求体转化为输出流。
- 写请求头。
- 将在请求刷新到底层的Socket。
- 通知完成请求动作。
- 读响应头。
- 读响应体。
- 取消请求。
三、Http1ExchangeCodec
【3.1】createRequestBody()
Http1ExchageCodec.java
@Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
if (request.body() != null && request.body().isDuplex()) {
throw new ProtocolException("Duplex connections are not supported for HTTP/1");
}
//创建一个不知长度的输出流。
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
return newChunkedSink();
}
//创建一个知道长度的输出流。
if (contentLength != -1L) {
return newKnownLengthSink();
}
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}
总结: 该方法总的来说就是根据请求的长度的确定性生成响应的流类型
【3.2】 writeRequestHeaders():写入请求头
@Override public void writeRequestHeaders(Request request) throws IOException {
String requestLine = RequestLine.get(
request, realConnection.route().proxy().type());
writeRequest(request.headers(), requestLine);
}
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
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;
}
【3.3】flushRequest()/finishRequest()
@Override public void flushRequest() throws IOException {
sink.flush();
}
@Override public void finishRequest() throws IOException {
sink.flush();
}
总结: 他们调用的都是flush方法。所以做的都是同一件是,把缓存区的数据刷新到底层Socket。
【3.4】openResponseBodySource():读取响应体
@Override public Source openResponseBodySource(Response response) {
//1. 如果没有响应体,那么构建一个读取长度为0的输入流
if (!HttpHeaders.hasBody(response)) {
return newFixedLengthSource(0);
}
//2. 不确定长度的输入流
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return newChunkedSource(response.request().url());
}
//3. 确定长度的输入流
long contentLength = HttpHeaders.contentLength(response);
if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
return newUnknownLengthSource();
}
【3.5】readResponseHeaders():读取响应头
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
...
try {
//解析响应头的String。
StatusLine statusLine = StatusLine.parse(readHeaderLine());
//构建响应体
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());
if (expectContinue && statusLine.code == HTTP_CONTINUE) {
return null;
} else if (statusLine.code == HTTP_CONTINUE) {
state = STATE_READ_RESPONSE_HEADERS;
return responseBuilder;
}
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
} catch (EOFException e) {
...
}
}
///读取响应头输入流
private String readHeaderLine() throws IOException {
String line = source.readUtf8LineStrict(headerLimit);
headerLimit -= line.length();
return line;
}
三、CallServerIntercepter.java: 最后一个拦截器
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//1. 写入请求头
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
Response.Builder responseBuilder = null;
//2. 是否为有请求体的请求
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//3. 若请求头里有"100-continue",代表先只有请求头的向服务器请求。
// 需要等待服务器的响应头再进一步请求。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
//4. 可以继续发出请求数据
if (responseBuilder == null) {
//5. 将请求体写入socket
if (request.body().isDuplex()) {
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.connection().isMultiplexed()) {
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
//6. 通知结束请求。
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
//7. 读取响应头
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
//8. 构建响应体
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//9. 如果响应码=100,需要再请求一次。
int code = response.code();
if (code == 100) {
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) {
//空连接
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//10.读取响应体详细
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build();
}
//11. 如果有close头,那么关闭连接。
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
...
//12. 返回请求
return response;
}
总结: CallServerIntercepter的拦截逻辑很简单,总的来说就是将请求头,请求体写入Socket,然后读取Socket的响应头和响应体。而具体的IO操作,OkHttp是采用的okio,这是个优秀的IO库,具体的逻辑这里就不深挖了。具体的流程如下:
- 写入请求头。
- 如果请求头里有"100-continue", 代表先将请求头发送给服务器,看服务器的响应决定是否进行下一步请求体的发送。
- 写入请求体,并发送请求。
- 读取响应体,并构建一个Resonse
- 如果响应码为100,需要再请求一次。
- 读取详细的响应体。
- 如果响应头有“close”,那么关闭这条连接。
- 返回响应。
OkHttp的几个重要部分讲解就到这里全部结束了。回顾一下,我们从网络的同步/异步请求,降到它的拦截链模式。然后着重讲了几个重要的拦截器:cacheIntercepter、ConnectInterpcet和CallServerIntercepter。这几篇文章是本人在自学中,总结记录。有不对的地方欢迎指出。最后,放上一张总体架构图,有助于整体理解:
(图片来源感谢:https://yq.aliyun.com/articles/78105?spm=5176.100239.blogcont78104.10.FlPFWr)
最后,在这里需要鸣谢以下博文:
https://www.jianshu.com/p/82f74db14a18
https://www.jianshu.com/p/7624b45fbdc1
https://www.jianshu.com/p/227cee9c8d15
本文引用的图片如有涉权,请联系本人删除,谢谢!