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

Paste_Image.png

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.

Paste_Image.png

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”

upload

Format of the HTTP response packet

Paste_Image.png

Paste_Image.png

Common json response:

Paste_Image.png

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