HTTP protocol correlation
An HTTP request process
- Enter the URL and parameters
- ->DNS resolution (access port 53 of DNS server, obtain IP based on the domain name, may obtain several IP)
- -> Connect to the socket based on the IP and port number (TCP three-way handshake is wrapped inside the SOCKET API and transparent to developers)
- -> After the socket connection is successful, HTTP packets are written to the socket output stream
The receiving of a response
- The server accepts the request, processes it, and issues a response
- The client reads HTTP packets from the socket input stream
Note: Request/status lines and message headers are characters, while request and response bodies can be characters or binary streams
Format of the HTTP request packet
Note: The first line in the following figure is incorrectly named as the request line. The status line is the name in the response message.
Special: Format of HTTP packets for file uploading:
Note: The following request parameters are: key value pair: “uploadFile555″,”1474363536041.jpg”,” API_secret777 “,” 898767hJK “file: “uploadFile”,”/storage/emulated/0/qxinli.apk”
Format of the HTTP response packet
Common json response:
Code execution flow in OKHTTP
Look directly at the synchronous execution code in GetExample: the execute() method at the heart of RealCall
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
Call.execute (), which implements the Call interface as RealCall
returnresponse.body().string(); }}Copy the code
RealCall the execute () :
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);// Just add the call object to Deque
runningSyncCalls, just add a reference
Response result = getResponseWithInterceptorChain();// Where the actual execution takes place
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this); }}Copy the code
GetResponseWithInterceptorChain () : involves the interceptor mechanism, see: okhttp source code parsing – coding skills – interceptor mechanism
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());// User-set interceptor
interceptors.add(retryAndFollowUpInterceptor);// Redirect and retry interceptors
interceptors.add(new BridgeInterceptor(client.cookieJar()));//
interceptors.add(new CacheInterceptor(client.internalCache()));// Cache managed interceptor
interceptors.add(new ConnectInterceptor(client));//Dns queries and retrieves socket connections
if(! forWebSocket) { interceptors.addAll(client.networkInterceptors());// User-set network interceptor
}
interceptors.add(new CallServerInterceptor(forWebSocket));// Where the socket is actually read and written
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null.null.null.0, originalRequest);
return chain.proceed(originalRequest);
}Copy the code
ConnectInterceptor and CallServerInterceptor are the two main interceptors in the request flow. They are :ConnectInterceptor and CallServerInterceptor
ConnectInterceptor For details about DNS resolution and obtaining a network connection, see okHTTP source code resolution – Network Framework Service Processing – Connection establishment and Connection Pool
@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, doExtensiveHealthChecks);//httpCodec encapsulates the input and output streams
RealConnection connection = streamAllocation.connection();//RealConnection encapsulates the "network connection" object
return realChain.proceed(request, streamAllocation, httpCodec, connection);// Encapsulate the above objects into the chain
}Copy the code
CallServerInterceptor: Where socket streams are actually read and written
Sink is an OutputStream for writing requests and Source is an InputStream for reading responses
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();// Take the wrapper object of the input/output stream out of the chain
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);// Write the request header to the stream
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);// Read the response header from the stream
}
// 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);// Write the request body to the output stream
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))// Read the response body from the stream
.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;
}Copy the code
See a few methods from above:
httpCodec.writeRequestHeaders(request);// Write the request header to the stream
httpCodec.createRequestBody(request, request.body().contentLength())
httpCodec.readResponseHeaders(true);// Read the response header from the stream
httpCodec.openResponseBody(response)Copy the code
The Http1Codec interface implementation classes are Http1Codec and Http2Codec.
writeRequestHeaders:
@Override public void writeRequestHeaders(Request request) throws IOException {
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
writeRequest(request.headers(), requestLine);
}
/** Returns bytes of a request header for sending on an HTTP transport. */
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
Read response header:
@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);
throwexception; }}/** Reads headers or trailers. */
public Headers readHeaders() throws IOException {
Headers.Builder headers = new Headers.Builder();
// parse the result headers until the first blank line
for (Stringline; (line = source.readUtf8LineStrict()).length() ! =0;) { Internal.instance.addLenient(headers, line); }return headers.build();
}Copy the code
Request body :createRequestBody
@Override public Sink createRequestBody(Request request, long contentLength) {
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
// Stream a request body of unknown length.
return newChunkedSink();
}
if(contentLength ! =- 1) {
// Stream a request body of a known length.
return newFixedLengthSink(contentLength);
}
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}
// Read and write: in the upper intercept, passed in
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();Copy the code
Read response body:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}Copy the code
RequestBody and ResponseBody:
Internally encapsulates the input and output streams, and the specific reads and writes are encapsulated in specific subclasses.
Subclasses of RequestBody are
FormBody for form submission and MultipartBody for file upload
FormBody Code for writing data to the stream:
Key and the value to advance the url encoded into encodedName, encodedValue, into the two list, and then according to the index in writing as the output stream
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) {
long byteCount = 0L;
Buffer buffer;
if (countBytes) {
buffer = new Buffer();
} else {
buffer = sink.buffer();
}
for (int i = 0, size = encodedNames.size(); i < size; i++) {
if (i > 0) buffer.writeByte('&');
buffer.writeUtf8(encodedNames.get(i));
buffer.writeByte('=');
buffer.writeUtf8(encodedValues.get(i));
}
if (countBytes) {
byteCount = buffer.size();
buffer.clear();
}
return byteCount;
}Copy the code
MultipartBody code to write data:
Notice that the body also has a content-Type, which is different from the content-Type in the header, which identifies what class the body is and what type the HTTP request is. Parse (type + “; boundary=” + boundary.utf8())
MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
this.boundary = boundary;
this.originalType = type;
this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
this.parts = Util.immutableList(parts);
}
private static final byte[] COLONSPACE = {':'.' '};
private static final byte[] CRLF = {'\r'.'\n'};
private static final byte[] DASHDASH = {The '-'.The '-'};
private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException {
long byteCount = 0L;
Buffer byteCountBuffer = null;
if (countBytes) {
sink = byteCountBuffer = new Buffer();
}
for (int p = 0, partCount = parts.size(); p < partCount; p++) {
Part part = parts.get(p);
Headers headers = part.headers;
RequestBody body = part.body;
sink.write(DASHDASH);
sink.write(boundary);
sink.write(CRLF);
if(headers ! =null) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
sink.writeUtf8(headers.name(h))
.write(COLONSPACE)
.writeUtf8(headers.value(h))
.write(CRLF);
}
}
MediaType contentType = body.contentType();
if(contentType ! =null) {
sink.writeUtf8("Content-Type: ")
.writeUtf8(contentType.toString())
.write(CRLF);
}
long contentLength = body.contentLength();
if(contentLength ! =- 1) {
sink.writeUtf8("Content-Length: ")
.writeDecimalLong(contentLength)
.write(CRLF);
} else if (countBytes) {
// We can't measure the body's size without the sizes of its components.
byteCountBuffer.clear();
return - 1L;
}
sink.write(CRLF);
if (countBytes) {
byteCount += contentLength;
} else {
body.writeTo(sink);
}
sink.write(CRLF);
}
sink.write(DASHDASH);
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF);
if (countBytes) {
byteCount += byteCountBuffer.size();
byteCountBuffer.clear();
}
return byteCount;
}Copy the code
Subclasses of ResponseBody are:
RealResponseBody and CacheResponseBody. ResponseBody (ResponseBody);
There are several common methods provided:
- Bytes (): Returns an array of bytes
- String (): Returns a string
- ByteStream (): Returns a Java inputStream that can be used for file downloads
public final byte[] bytes() throws IOException {
long contentLength = contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
}
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source);
}
if(contentLength ! =- 1&& contentLength ! = bytes.length) {throw new IOException("Content-Length ("
+ contentLength
+ ") and stream length ("
+ bytes.length
+ ") disagree");
}
return bytes;
}
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
public final InputStream byteStream() {
return source().inputStream();
}Copy the code
Response: The final return object that the user gets
The response is parsed into a memory object, and the parts of the HTTP packet are encapsulated separately. And provide some corresponding methods
public final class Response implements Closeable {
final Request request;
final Protocol protocol;
final int code;
final String message;
final Handshake handshake;
final Headers headers;
final ResponseBody body;
final Response networkResponse;
final Response cacheResponse;
final Response priorResponse;
final long sentRequestAtMillis;
final long receivedResponseAtMillis;
private volatile CacheControl cacheControl; // Lazily initialized.. public boolean isSuccessful() {return code >= 200 && code < 300;
}
public int code() {
return code;
}
public ResponseBody body() {
return body;
}
public List<String> headers(String name) {
return headers.values(name);
}
public String header(String name) {
return header(name, null);
}
public String header(String name, String defaultValue) {
String result = headers.get(name);
returnresult ! =null ? result : defaultValue;
}
public Headers headers() {
returnheaders; }...Copy the code
Conclusion:
- From the source code analysis above, we know that this part of OKHTTP is essentially an implementation of the HTTP protocol. Understanding the HTTP protocol is a prerequisite for reading the source code.
- When using OKhttp directly, two classes are involved: Request and Response
- Okhttp provides two implementations of request bodies: FormBody for form submission and MultiPartBody for file upload
- Okhttp provides several ways to receive the response body: as a string; the string() method as a byte array; the bytes() method as a stream; the byteStream() method
HTTP protocol details (really classic)
Network library to the extreme:
HttpUtilForAndroid