关于Okhttp3(八)-CallServerInterceptor

本系列文章终于要到尾声了。上篇我们已经成功连接到服务了,那解析来应该做什么呢,相比聪明的你已经猜到了,那就是发送接收数据。

读写数据

  1. 第一步,写入请求头
  2. 第二步,写入请求头
  3. 第三步,读取响应头
  4. 第四步,读取响应体

前面我们已经说过,okhttp的流程是完全独立的,同样读写数据也是交给相关的类来处理,这个类就是HttpCodec(解码器)

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
// 第一步,写入请求头
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);

Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}

//第二步,写入请求头
// Write the request body, unless an "Expect: 100-continue" expectation failed.
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
}

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();
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();
}

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;
}

写入请求头

每一次的网络请求必然携带有请求头,这是http协议里面的东西,建议大家对http相关知识要熟悉掌握,这对开发工作大有好处。

这里我们看上面第一步,调用到了,httpcodec的写入方法

1
httpCodec.writeRequestHeaders(request);

我们跟进去看看

1
2
3
4
5
6
7
@Override public void writeRequestHeaders(Request request) throws IOException {
// 创建一个请求行 比如GET /index.html HTTP/1.1。
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
// 写入请求头
writeRequest(request.headers(), requestLine);
}

继续看writeRequest()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 此处有状态的判断
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;
}

sink是Okio里面的内容,可以理解为outputstream,

  1. 首先写入请求行
  2. 循环写入请求头

完成后,会判断是否需要请求体,比如get就没有

写入请求体

1
2
3
4
5
6
7
// 其他略
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}

写入请求体比较简单,对于post的请求体本质上就是一个写入流,当然这里是Okio的东西,再次封装而已,通过连接成功后获取到的读写流,我们就可以把请求体写出。

写完后会有一个,

1
finishRequest()// 时间上就是flush(),把流中的数据刷出去

读响应头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
throw new IllegalStateException("state: " + state);
}

try {
// 首先解析相应行,协议,状态吗,响应头
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

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;
}

state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
} catch (EOFException e) {
// Provide more context if the server ends the stream before sending a response.
IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
exception.initCause(e);
throw exception;
}
}

解析相应头

1
2
3
4
5
6
7
8
9
10
public Headers readHeaders() throws IOException {
// 创建一个builder
Headers.Builder headers = new Headers.Builder();
// parse the result headers until the first blank line
// 一行行读出数据
for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
Internal.instance.addLenient(headers, line);
}
return headers.build();
}

此处存放在一个list中

1
2
3
4
5
6
7
0     1    2    3   4 5 6
key value key value ...
Builder addLenient(String name, String value) {
namesAndValues.add(name);
namesAndValues.add(value.trim());
return this;
}

最后调用build(),转化成Headers。

读取响应体

解析完成头后会根据状态判断响应是否成功,

1
2
3
4
5
6
7
8
9
10
11
12
13
int code = response.code();
// web socket
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();
}

打开响应体

1
2
3
4
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}

注意:这里并没有解析,响应结果,而是关联起了一个Source,okio知识

至此整个请求过程完成。

然后往上一层拦截器返回结果,即回到上一个拦截器的intercept()方法,把响应结果交给上一个拦截器处理,比如缓存拦截器,会处理是不是要缓存下来等。

最后getResponseWithInterceptorChain()返回,我们再来看看这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
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);//返回结果
}

关于拦截器: 拦截器有点像上楼梯,一层层上去,让后在一层层倒回来,拿到结果给调用入口。

调用入口

我们再回过头来看看调用入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  @Override protected void execute() {
boolean signalledCallback = false;
try {
// 通过一系列的拦截器最终难道结果
Response response = getResponseWithInterceptorChain();
// 中途取消, callback回调
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
// 否则调用成功
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
// 其他异常调用失败,
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// 最终一定会调用finished
client.dispatcher().finished(this);
}
}
}

总结

通过本系列文章,你一定对Okhttp有了清晰的认识,至少大体流程会有一个清晰的脉络,至于有些细节,还需要聪明的你去,慢慢研读。本系列文章对细节并没有详细说明,我们学习的并不是具体的实现而是一个思路,真正的技术实现都是各个api的调用而已。Thanks!!!

系列文章

  1. 关于Okhttp(一)-基本使用
  2. 关于Okhttp(二)-如何下载查看源码
  3. 关于Okhttp3(三)-请求流程
  4. 关于Okhttp3(四)-RetryAndFollowUpInterceptor
  5. 关于Okhttp3(五)-BridgeInterceptor
  6. 关于Okhttp3(六)-CacheInterceptor
  7. 关于Okhttp3(七)-ConnectInterceptor
  8. 关于Okhttp3(八)-CallServerInterceptor
坚持原创技术分享,您的支持将鼓励我继续创作!