One, foreword
[1.1] Other chapters in OkHttp series:
- The implementation flow of synchronous requests.
- The implementation flow of asynchronous requests
- Important interceptor: parsing of the CacheInterceptor.
- Important interceptor: Resolution of the ConnectInterceptor.
- Important interceptor: resolution of the CallServerInterceptor.
[1.2] Statement
After the preparatory work of the previous chapters, we were finally able to communicate formally with the server. The formal data communication with the server takes place in the last interceptor: ServerIntercepter.java. Before parsing this class, you need to take a look at another class: ExchangeCodec, which was covered in the previous article but not in detail. We’ll talk about that in this chapter.
Second, the ExchangeCodec
[2.1] Introduction
ExchageCodec is an interface that regulates the encoding behavior of network requests and decoding behavior of network replies. It has two subclasses Http1ExchangeCodec and Http2ExchangeCodec. They correspond to the Http1 protocol and the Http2 protocol respectively. Here are the main interface specifications of ExchageCodec:
public interface ExchangeCodec { ... Request Request, long contentLength throws IOException. */ Sink createRequestBody(Request Request, long contentLength) throws IOException; /** writeRequestHeaders */ void writeRequestHeaders(Request Request) throws IOException; /* flushes requests in the cache to the output stream */ void flushRequest() throws IOException; Void finishRequest() throws IOException; void finishRequest() throws IOException; /** Read the Response body */ Source openResponseBodySource(Response Response) throws IOException; /* / @nullable Response.builderreadResponseHeaders(boolean expectContinue) throws IOException; . /** Cancel the request */ void cancel(); }Copy the code
Summary: In general, Exchagecodec. Java regulates the actions of write requests and read responses during network interactions. The details are as follows:
- Convert the request body to an output stream.
- Write the request header.
- Will flush the request to the underlying Socket.
- Notify completion of the requested action.
- Read the response header.
- Read the response body.
- Cancel the request.
Third, 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"); } // Create an output stream of unknown length.if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
returnnewChunkedSink(); } // Create an output stream that knows the length.if(contentLength ! = -1L) {return newKnownLengthSink();
}
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}
Copy the code
Summary: The approach is basically to generate the flow type of response based on the deterministic length of the request
[3.2] writeRequestHeaders() : 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;
}
Copy the code
【 3.3 】 flushRequest ()/finishRequest ()
@Override public void flushRequest() throws IOException {
sink.flush();
}
@Override public void finishRequest() throws IOException {
sink.flush();
}
Copy the code
Summary: They all call flush. So all you have to do is flush the cache to the underlying Socket.
[3.4] openResponseBodySource() : Reads the response body
@Override public Source openResponseBodySource(Response response) { //1. If there is no response body, build an input stream with a read length of 0if(! HttpHeaders.hasBody(response)) {returnnewFixedLengthSource(0); } //2. Input stream of uncertain lengthif ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
returnnewChunkedSource(response.request().url()); } / / 3. Determine the length of the input stream long contentLength = HttpHeaders. ContentLength (response);if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
return newUnknownLengthSource();
}
Copy the code
[3.5] readResponseHeaders() : Reads the response headers
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException { ... Try {// Parse the String of the response header. StatusLine statusLine = StatusLine.parse(readHeaderLine()); ResponseBuilder responseBuilder ().protocol(statusline.protocol).code(statusline.code) responseBuilder = new Response.builder ().protocol(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;
returnresponseBuilder; } catch (EOFException e) { ... }} // read the response header input stream private StringreadHeaderLine() throws IOException {
String line = source.readUtf8LineStrict(headerLimit);
headerLimit -= line.length();
return line;
}
Copy the code
Third, CallServerIntercepter. Java: the last interceptor
@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. Write requests head exchange. WriteRequestHeaders (request); boolean responseHeadersStarted =false; Response.Builder responseBuilder = null; //2. Whether the request has a request bodyif(HttpMethod.permitsRequestBody(request.method()) && request.body() ! = null) {//3"100-continue", which means that only the request header requests the server first. // Need to wait for the server's response header to request further.if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true); } //4. You can continue to send request dataif(responseBuilder == null) { //5. Write the request body to the socketif (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. Notify end request.if(request.body() == null || ! request.body().isDuplex()) { exchange.finishRequest(); }if(! responseHeadersStarted) { exchange.responseHeadersStart(); } //7. Read the response headerif (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false); Responsebuilder.request (request).Handshake (exchange.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); //9. If response code =100, request again. 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) {// Null connection Response = response.newBuilder().body(util.empty_response).build(); }else{/ / 10. Read detailed response = response. The response body newBuilder (). The body (exchange. OpenResponseBody (response)). The build (); } //11. If there is a close header, close the connection.if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) { exchange.noNewExchangesOnConnection(); }... //12. Return requestreturn response;
}
Copy the code
Summary: The intercepting logic of the CallServerIntercepter is very simple. Generally, it is to write the request header and the request body into the Socket, and then read the response header and the response body of the Socket. The specific IO operations, OkHttp is using OKio, which is an excellent IO library, the specific logic will not be dug here. The specific process is as follows:
- Write the request header.
- If “100-continue” is displayed in the header, the request is sent to the server, and the server’s response determines whether to send the next request body.
- Writes the request body and sends the request.
- Read the response body and build a Resonse
- If the response code is 100, request again.
- Read the detailed response body.
- If the response header has “close”, the connection is closed.
- Return the response.
That concludes the important parts of OkHttp. To review, we went from synchronous/asynchronous requests on the network to its interceptor chain mode. I then focused on a few important interceptors: cacheIntercepter, ConnectInterpcet, and CallServerIntercepter. These articles are summarized and recorded in my self-study. You are welcome to point out any mistakes. Finally, there is an overall architecture diagram to help you understand the whole picture:
Yq.aliyun.com/articles/78…
Finally, here need to acknowledge the following posts: www.jianshu.com/p/82f74db14… www.jianshu.com/p/7624b45fb… www.jianshu.com/p/227cee9c8… If the picture quoted in this article has rights, please contact me to delete, thank you!