Search code examples
http2

Flow Control questions about HTTP/2 RFC Implementation


I'm building a http/2 client and I have a question about the rfc and how I should handle the implementation, particularly with respect to flow control.

I understand that flow control uses a credit based window size system, but I'm a bit unsure about how to handle the scenario of a depleted window.

  1. Do I just block indefinitely until a WINDOW_UPDATE frame frees things up? Or what would a reasonable timeout be for this?

  2. When the window is depleted do I pause sending all frames? The RFC states that order of frames in a stream is significant especially for headers and data frames but it doesn't explicitly state that all frames should be paused while the window is depleted. This is a bit ambiguous to me as data frames are the only ones that count against window size. So should I block sending all frames or just headers/data? Is this answer different for the connection flow control context vs stream flow control context?


Solution

  • There is a flow control window for all data frames in the connection and then one for each stream.

    1.- If the connection window is exhausted, you pause sending DATA frames of any stream until you get a WINDOWS_UPDATE. You can implement a timeout. If the timeout expires, the only remedy that you have is to close the connection and try again.

    2.- If a stream connection window is exhausted, you pause only that stream.

    In all the cases you pause only DATA frames. The other types of frames are not affected by flow control.

    If you are implementing a client, as opposed to a server, you are more concerned with sending the WINDOW_UPDATE frames yourself. Unless you are doing a lot of POST and PUT, i.e., sending data to the server.

    In my experience as developer of an HTTP/2 server, I have found handy to manage HTTP/2 frames in groups that I call "trains". For all frames except HEADERS, PUSH_PROMISE and CONTINUATION, a train is composed of just the frame. For HEADERS and PUSH_PROMISE, a train is composed of that frame and any following CONTINUATION frames. Then you put your trains in a priority queue with the following levels (first have highest priority):

    1. PING frames: you want the peer to accurately determine latency, therefore you process these frames as quickly as you receive them and you send the answer as soon as you can.
    2. SETTINGS, WINDOW_UPDATE, RST_STREAM, GO_AWAY
    3. PUSH_PROMISE and HEADERS trains. The HTTP/2 spec allows for two (actually three) types of HEADERS frames: the ones which are sent as HTTP headers proper and trailers. Here I'm talking about the first.
    4. DATA frames. If you are implementing prioritization, you may want to further prioritize frames here.

    and whenever the channel becomes available for sending, you send the highest priority trains in your priority queue.