HTTP 2.0 is an extension of rather than a replacement for 1.x, and is “2.0” because it changes the way data is exchanged between clients and servers. HTTP 2.0 adds a new layer of binary frame data that is not compatible with the previous HTTP 1.x server and client — called 2.0. Before formally introducing HTTP 2.0, we need to understand a few concepts.

  • Stream, a bidirectional byte stream over an established connection.
  • Messages, and logical messages (Request,Response) to a complete series of data frames.
  • The frame,The HTTP 2.0The smallest unit of communication, asHeaderFrames (store theHeader),DATAFrames (which store the content or part of the content sent).
Introduction to HTTP 2.0

As you know, HTTP 1.x has many disadvantages, such as queue blocking, no multiplexing, and Header compression. While many solutions have been proposed to address these shortcomings, such as long connections, connect and merge requests, and HTTP pipelines, none of them dealt with the symptoms of the problem until THE advent of HTTP 2.0, which added the following designs to fundamentally address many of the problems faced by HTTP 1.x.

  • Binary frame layer, it isThe HTTP 2.0Performance enhancements that change the way the client and server interact with data will transfer information (Header,BodyEtc.) into smaller messages and frames, and encoded in binary format.
  • Parallel request and response, the client and server can beHTTPMessages are broken up into separate frames, sent out of order, and then combined at the other end.
  • Request priority (0 indicates the highest priority and -1 indicates the lowest priority). Each stream can carry a priority value, which allows the client and server to adopt different policies when processing different streams to send streams, messages, and frames in an optimal manner. However, the priority should be handled carefully, otherwise it may introduce the problem of queue head blocking.
  • Single TCP connection.The HTTP 2.0This allows all data streams to share a single connection for more efficient useTCPThe connection
  • Flow control, controls the resources consumed by each flow, andTCPThe flow control implementation is exactly the same.
  • Server push.The HTTP 2.0Multiple responses can be sent to a client request, meaning that in addition to the initial request response, the server can push additional resources to the client without the client explicitly requesting them.
  • The Header is compressed.The HTTP 2.0The header table is used on both the client and server to track and store previously sent key-value pairs, and the same data is not sent on each request and response. The header table exists for the duration of the connection and is updated incrementally by both the client and the server. Each new header key-value pair is either appended to the end of the current table or replaces the values in the table.

While HTTP 2.0 solves many of the problems in 1.x, it also has the following problems.

  • Although eliminatedHTTPThe head of the queue is blocked, butTCPThere is still the phenomenon of head of queue blocking on the level. To solve the problem completely, it needs to be abandoned completelyTCP, define the protocol yourself. Look at Google’sQUIC.
  • ifTCPWindow scaling is disabled, so the broadband delay product effect may limit the throughput of the connection.
  • Packet loss,TCPThe congestion window shrinks.
2. Introduction to binary framing

The fundamental improvement in HTTP 2.0 is the addition of the binary frame layer. Unlike HTTP 1.x, which uses newlines to split plain text, the binary frame layer is much simpler and more efficient to process through code.

Image from HTTP/2 introduction

After an HTTP 2.0 connection is established, the client and server communicate by exchanging frames, the minimum unit of communication based on the new protocol. All frames share an 8-byte header, which includes the frame’s length, type, and flag, as well as a reserved bit and a 31-bit stream identifier.

  • The 16-bit length prefix means that a frame can carry about 64KB of data, not including the 8-byte header
  • The 8-bit type field determines how to interpret the rest of the frame
  • The 8-bit flag field allows different frame types to define frame-specific message flags
  • A 1-bit reserved field is always set to 0
  • A 31 bit stream identifier uniquely identifies the streamThe HTTP 2.0The flow of

HTTP 2.0 specifies the following frame types.

  • DATA, for transmissionHTTPThe message body
  • HEADERSWhich is used to transmit additional header fields about the stream (Header)
  • PRIORITY, which specifies or respecifies the PRIORITY of a stream
  • RST_STREAM, which is used to notify an abnormal termination of a stream
  • SETTINGS is used to inform the configuration data of the communication mode between the two ends
  • PUSH_PROMISE, used to make an offer to create a stream and a server reference resource
  • PING: calculates the round-trip time and performs the “live” check
  • GOAWAY, which notifies the client/server to stop creating streams in the current connection
  • WINDOW_UPDATE, for flow control for individual streams or individual connections
  • CONTINUATION, which continues a series of header block fragments
2.1, the HEADER frame

Before the application data can be sent, a new stream must be created and the corresponding metadata, such as the priority of the stream, the HTTP header, and so on, must be sent with it. The HTTP 2.0 protocol states that both the client and the server can initiate new streams, so there are two possibilities.

  • The client sendsHEADERSFrame to initiate a new stream. This frame contains a common header with the new stream ID, an optional 31-bit priority value, and a set ofHTTPHeader of a key-value pair
  • The server sendsPUSH_PROMISEFrame to initiate a push streamHEADERFrame equivalent, but it contains the “offer stream ID” and has no preference value

2.2, the DATA frame

Application DATA can be divided into multiple DATA frames, and the last frame flips the END_STREAM field at the beginning of the frame.

The data load will not be separately encoded or compressed. The encoding of DATA frames depends on the application or server, and can be plain text, GZIP, image, or video. The entire frame consists of a common 8-byte header and HTTP load. Technically, the length field of a DATA frame determines the maximum net load of DATA per frame-1 (65535) bytes. However, to reduce headline congestion,The HTTP 2.0The standard requires that the number of DATA frames cannot exceed(16383) bytes. Data that exceeds this threshold is sent as a frame.

3. Application of HTTP 2.0 in OKHttp

HTTP 2.0 is enabled through the startHttp2 method of a RealConnection, which creates an Http2Connection object and then calls the Start method of Http2Connection.

  private void startHttp2(int pingIntervalMillis) throws IOException {
    socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
    // Create an Http2Connection object
    http2Connection = new Http2Connection.Builder(true)
        .socket(socket, route.address().url().host(), source, sink)
        .listener(this)
        .pingIntervalMillis(pingIntervalMillis)
        .build();
    // Enable HTTP 2.0
    http2Connection.start();
  }
Copy the code

In the start method, the string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n is first sent to the server to finalize the protocol and to establish the initial setup of the HTTP/2 connection. Then a SETTINGS type Header frame is sent to the server. This frame mainly tells the server the maximum capacity of each frame, the size of the Header table, whether push is enabled, and other information. If the size of the Window changes, you also need to update the size of the Window (the default Window size for HTTP 2.0 is 64KB, and the client needs to change this size to 16MB to avoid frequent updates). Finally, a child thread is started to read the data returned from the server.

  public void start(a) throws IOException {
    start(true);
  }
  void start(boolean sendConnectionPreface) throws IOException {
    if (sendConnectionPreface) {
      // Send a string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n to make the final protocol determination, that is, the prologer frame
      writer.connectionPreface();
      // Tell the server the local configuration information
      writer.settings(okHttpSettings);
      // The Window size is set to 16M in okHttpSetting
      int windowSize = okHttpSettings.getInitialWindowSize();
      // The default is 64KB, but you need to reset it to 16M on the client
      if(windowSize ! = Settings.DEFAULT_INITIAL_WINDOW_SIZE) {// Update the window size
        writer.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE); }}// The child thread listens for messages returned by the server
    new Thread(readerRunnable).start(); // Not a daemon thread.
  }

Copy the code

As you can see from the name of the ReaderRunnable, it is used to read the various types of data returned from the server.

  class ReaderRunnable extends NamedRunnable implements Http2Reader.Handler {...@Override protected void execute(a) {
      ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
      ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
      try {
        // Read the prologue frame returned by the server
        reader.readConnectionPreface(this);
        // The next frame is read continuously and all messages are distributed from here
        while (reader.nextFrame(false.this)) {
        }
        connectionErrorCode = ErrorCode.NO_ERROR;
        streamErrorCode = ErrorCode.CANCEL;
      } catch (IOException e) {
        ...
      } finally{... }}// Read the returned DATA of type DATA
    @Override public void data(boolean inFinished, int streamId, BufferedSource source, int length)
        throws IOException {... }// Read the returned HEADERS data
    @Override public void headers(boolean inFinished, int streamId, int associatedStreamId,
        List<Header> headerBlock) {... }// Read the returned data of type RST_TREAM
    @Override public void rstStream(int streamId, ErrorCode errorCode) {... }// Read the returned SETTINGS data
    @Override public void settings(boolean clearPrevious, Settings newSettings) {... }// Return the ackSettings returned by the server
    private void applyAndAckSettings(final Settings peerSettings). }// Restore the SETTING data sent by the client, which is not implemented by default
    @Override public void ackSettings(a) {... }// Read the returned PING data
    @Override public void ping(boolean reply, int payload1, int payload2) {... }// Read the GOAWAY data returned by the server
    @Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {... }// Read the WINDOW_UPDATE data returned by the server
    @Override public void windowUpdate(int streamId, long windowSizeIncrement) {... }// Read the PRIORITY data returned by the server
    @Override public void priority(int streamId, int streamDependency, int weight,
        boolean exclusive) {... }// Read the returned data of type PUSH_PROMISE
    @Override
    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {... }
    / / standby Service
    @Override public void alternateService(int streamId, String origin, ByteString protocol,
        String host, int port, long maxAge) {...}
  }
Copy the code

The above describes how to enable the HTTP 2.0 protocol in OkHttp. The client and server use HTTP 2.0 protocol to read and write data.

3.1 write Headers to the server

To write the Header is the server through httpCodec. WriteRequestHeaders (request), httpCodec under the HTTP 2.0 protocol implementation class is Http2Codec. The writeRequestHeaders method creates a new stream, Http2Stream, and sends Headers data to the server when the stream is created successfully.

    booleanhasRequestBody = request.body() ! =null;
    List<Header> requestHeaders = http2HeadersList(request);
    // Create a new stream
    stream = connection.newStream(requestHeaders, hasRequestBody);
    // We may be asked to cancel when we create a new stream and send Headers, but there is still no stream to close.
    if (canceled) {
      stream.closeLater(ErrorCode.CANCEL);
      throw new IOException("Canceled"); }... }// The following methods are in the Http2Connection class
  public Http2Stream newStream(List<Header> requestHeaders, boolean out) throws IOException {
    return newStream(0, requestHeaders, out);
  }

  private Http2Stream newStream(
      int associatedStreamId, List<Header> requestHeaders, boolean out) throws IOException {...synchronized (writer) {
      synchronized (this) {
        // The number of streams per TCP connection cannot exceed integer.max_value
        if (nextStreamId > Integer.MAX_VALUE / 2) {
          shutdown(REFUSED_STREAM);
        }
        if (shutdown) {
          throw new ConnectionShutdownException();
        }
        // ID of each stream
        streamId = nextStreamId;
        // The next stream ID is the current stream ID plus 2
        nextStreamId += 2;
        // Create a new stream
        stream = new Http2Stream(streamId, this, outFinished, inFinished, null); flushHeaders = ! out || bytesLeftInWriteWindow ==0L || stream.bytesLeftInWriteWindow == 0L;
        if(stream.isOpen()) { streams.put(streamId, stream); }}if (associatedStreamId == 0) {
        // Write Headers to the server
        writer.headers(outFinished, streamId, requestHeaders);
      } else if (client) {
        throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
      } else {// For the serverwriter.pushPromise(associatedStreamId, streamId, requestHeaders); }}/ / refresh
    if (flushHeaders) {
      writer.flush();
    }

    return stream;
  }
Copy the code

On the client, the stream ID is all odd numbers starting with 3, and on the server, the stream ID is all even numbers. The initial value that defines the stream ID is defined in the Http2Connection constructor.

  Http2Connection(Builder builder) {
    ....
    // If it is a client, the stream ID starts at 1
    nextStreamId = builder.client ? 1 : 2;
    if (builder.client) {
      // In HTTP2, 1 is reserved for upgrading
      nextStreamId += 2; }... }Copy the code
3.2 Read Headers returned by the server

ReadResponseHeaders reads Headers data from the server. This method is in the Http2Codec.

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    // Get Headers from the stream,
    Headers headers = stream.takeHeaders();
    Response.Builder responseBuilder = readHttp2HeadersList(headers, protocol);
    if (expectContinue && Internal.instance.code(responseBuilder) == HTTP_CONTINUE) {
      return null;
    }
    return responseBuilder;
  }
  // The method is in Http2Stream
  public synchronized Headers takeHeaders(a) throws IOException {
    readTimeout.enter();
    try {
      // Wait if there is no data in the queue
      while (headersQueue.isEmpty() && errorCode == null) { waitForIo(); }}finally {
      readTimeout.exitAndThrowIfTimedOut();
    }
    // Get Headers data from the queue
    if(! headersQueue.isEmpty()) {return headersQueue.removeFirst();
    }
    throw new StreamResetException(errorCode);
  }
Copy the code

HeadersQueue is a dual-ended queue that stores the Headers returned by the server. When the server returns Headers, the list is updated.

Read/write the Body

When a stream is created, a FramingSink and FramingSource objects are created. The FramingSink is used to write data to the server, and the FramingSource reads the data returned by the server. If you are not familiar with Okio, you can learn more about Okio.

  // Write data to the server
  final class FramingSink implements Sink {
    private static final long EMIT_BUFFER_SIZE = 16384; .@Override public void write(Buffer source, long byteCount) throws IOException {
      assert(! Thread.holdsLock(Http2Stream.this));
      sendBuffer.write(source, byteCount);
      while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {
        emitFrame(false); }}//
    private void emitFrame(boolean outFinished) throws IOException {...try {
        // Write the DATA type to the server
        connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);
      } finally{ writeTimeout.exitAndThrowIfTimedOut(); }}... }// Read data from the server
  private final class FramingSource implements Source {
    // Write data read from the network to this Buffer, which is accessible only by the reader thread
    private final Buffer receiveBuffer = new Buffer();

    / / to read buffer
    private final Buffer readBuffer = new Buffer();

    // The maximum number of bytes buffered
    private final longmaxByteCount; .// Read data from the receiveBuffer
    @Override public long read(Buffer sink, long byteCount) throws IOException {...}
    ...
    // Receive the data passed by the server, called only in the ReaderRunnable
    void receive(BufferedSource in, long byteCount) throws IOException {...}
    ...
  }
Copy the code
Http2Reader and Http2Writer

You’ve seen reading and writing data from the server, but it’s the Http2Reader and Http2Writer classes that do the reading and writing to the server. Let’s start with writing data to the server.

final class Http2Writer implements Closeable {...// Write the prologue frame to finalize the protocol
  public synchronized void connectionPreface(a) throws IOException {... }// Send PUSH_PROMISE data
  public synchronized void pushPromise(int streamId, int promisedStreamId,
      List<Header> requestHeaders) throws IOException {...}
  ...
  // Send RST_TREAM data
  public synchronized void rstStream(int streamId, ErrorCode errorCode)
      throws IOException {... }// Send DATA of type DATA
  public synchronized void data(boolean outFinished, int streamId, Buffer source, int byteCount)
      throws IOException {... }// Send data of type SETTINGS
  public synchronized void settings(Settings settings) throws IOException {... }// Send PING data
  public synchronized void ping(boolean ack, int payload1, int payload2) throws IOException {... }// Send data of type GOAWAY
  public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
      throws IOException {... }// Send data of type WINDOW_UPDATE for Window update
  public synchronized void windowUpdate(int streamId, long windowSizeIncrement) throws IOException {... }// Send HEADERS data
  public void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {... }@Override public synchronized void close(a) throws IOException {
    closed = true; sink.close(); }...// Write CONTINUATION data
  private void writeContinuationFrames(int streamId, long byteCount) throws IOException {... }/ / write headers
  void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {...}
}
Copy the code

Now look at reading data from the server, which is basically distributing data based on the type of data.

final class Http2Reader implements Closeable {...// Read the data
  public boolean nextFrame(boolean requireSettings, Handler handler) throws IOException {
    try {
      source.require(9); // Frame header size
    } catch (IOException e) {
      return false; // This might be a normal socket close.
    }

    // 0 1 2 3
    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    // | Length (24) |
    // +---------------+---------------+---------------+
    // | Type (8) | Flags (8) |
    // +-+-+-----------+---------------+-------------------------------+
    // |R| Stream Identifier (31) |
    / / + + = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = +
    // | Frame Payload (0...) .
    // +---------------------------------------------------------------+
    int length = readMedium(source);
    if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
      throw ioException("FRAME_SIZE_ERROR: %s", length);
    }
    byte type = (byte) (source.readByte() & 0xff);
    if(requireSettings && type ! = TYPE_SETTINGS) {throw ioException("Expected a SETTINGS frame but was %s", type);
    }
    byte flags = (byte) (source.readByte() & 0xff);
    int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
    if (logger.isLoggable(FINE)) logger.fine(frameLog(true, streamId, length, type, flags));
    // This handler is a ReaderRunnable object
    switch (type) {
      case TYPE_DATA:
        readData(handler, length, flags, streamId);
        break;

      case TYPE_HEADERS:
        readHeaders(handler, length, flags, streamId);
        break;

      case TYPE_PRIORITY:
        readPriority(handler, length, flags, streamId);
        break;

      case TYPE_RST_STREAM:
        readRstStream(handler, length, flags, streamId);
        break;

      case TYPE_SETTINGS:
        readSettings(handler, length, flags, streamId);
        break;

      case TYPE_PUSH_PROMISE:
        readPushPromise(handler, length, flags, streamId);
        break;

      case TYPE_PING:
        readPing(handler, length, flags, streamId);
        break;

      case TYPE_GOAWAY:
        readGoAway(handler, length, flags, streamId);
        break;

      case TYPE_WINDOW_UPDATE:
        readWindowUpdate(handler, length, flags, streamId);
        break;

      default:
        // Implementations MUST discard frames that have unknown or unsupported types.
        source.skip(length);
    }
    return true; }... }Copy the code

In both Http2Reader and Http2Writer, data is read or written in the form of frames (binary), which is more efficient than strings. Of course, the Huffman algorithm (which OkHttp supports) can be used to compress frames for better performance. Remember that network optimization under HTTP 1.x uses Protocol Buffer (binary) as an alternative to string passing, whereas Protocol Buffer is not required with HTTP 2.0.

4, summarize

At this point, you must have a general understanding of HTTP 2.0, and more needs to be done. Of course, to use HTTP 2.0, you need both the client and the server. Note: Currently OKHttp only supports HTTP 2.0 for HTTPS requests.

HTTP2.0 protocol Overview HTTP2.0 Protocol Exploration (6) : H2 frame detailed explanation and HTTP optimization HTTP/2 note connection establishment