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.0
The smallest unit of communication, asHeader
Frames (store theHeader
),DATA
Frames (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 is
The HTTP 2.0
Performance enhancements that change the way the client and server interact with data will transfer information (Header
,Body
Etc.) into smaller messages and frames, and encoded in binary format. - Parallel request and response, the client and server can be
HTTP
Messages 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.0
This allows all data streams to share a single connection for more efficient useTCP
The connection - Flow control, controls the resources consumed by each flow, and
TCP
The flow control implementation is exactly the same. - Server push.
The HTTP 2.0
Multiple 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.0
The 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 eliminated
HTTP
The head of the queue is blocked, butTCP
There 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. - if
TCP
Window scaling is disabled, so the broadband delay product effect may limit the throughput of the connection. - Packet loss,
TCP
The 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 stream
The HTTP 2.0
The flow of
HTTP 2.0 specifies the following frame types.
- DATA, for transmission
HTTP
The 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 sends
HEADERS
Frame 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 ofHTTP
Header of a key-value pair - The server sends
PUSH_PROMISE
Frame to initiate a push streamHEADER
Frame 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.0
The 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