I'm working on an HTTP/2 client, and now i realised, that i do not understand how it must work at all.
HTTP/2 allows many parallel streams, but the HPACK encoder/decoder (one for all connections) seems to introduce a dependency on the order of headers being sent and received. Specifically, when I send headers on one stream, I cannot start encoding the next headers for another stream until I receive and decode the response headers from the server. This is because HPACK uses a dynamic table, which needs to be in sync between the client and the server.
Given this, how can HTTP/2 streams be processed in parallel? If I need to wait for the server’s response to update the dynamic table before encoding the next set of headers, doesn’t that introduce a blocking behavior between streams, effectively serializing header transmission?
The HPACK encoder/decoder (one for all connections) seems to introduce a dependency on the order of headers being sent and received.
The HPACK encoder/decoder is per connection, not one for all connections.
Specifically, when I send headers on one stream, I cannot start encoding the next headers for another stream until I receive and decode the response headers from the server.
Each side maintains two "tables" per connection.
The client maintains an encoder table, which must be in sync with the decoder table on the server. The server maintains an encoder table, which must be in sync with the decoder table on the client.
The client's implementation (as well as the server's) must be careful in handling concurrency.
In particular, the client must send the streams in strict monotonically increasing stream ids, so the HPACK encoding and the write over the network of that stream's headers must be isolated from other threads.
For example, encoding stream1
, then encoding stream3
, but then writing stream3
bytes before stream1
bytes would be incorrect.
In summary, HTTP/2 streams can be initiated concurrently by isolating stream id generation + HPACK encoding + network write; this can be achieved with a coarse lock, or queueing, or some more efficient mechanism.
The receiving code may run concurrently with the sending code, since they operate on different HPACK "tables"; the receiving side is naturally single-threaded since typically only one thread reads from a socket. The concurrency on the receiving side stems from the server interleaving frames for different streams, so that different streams may proceed "in parallel" (whether they are really running at the same time also depends on the implementation).