Advanced Usage

Priority

New in version 2.0.0.

RFC 7540 has a fairly substantial and complex section describing how to build a HTTP/2 priority tree, and the effect that should have on sending data from a server.

h2 does not enforce any priority logic by default for servers. This is because scheduling data sends is outside the scope of this library, as it likely requires fairly substantial understanding of the scheduler being used.

However, for servers that do want to follow the priority recommendations given by clients, the Hyper project provides an implementation of the RFC 7540 priority tree that will be useful to plug into a server. That, combined with the PriorityUpdated event from this library, can be used to build a server that conforms to RFC 7540’s recommendations for priority handling.

Connections: Advanced

Thread Safety

H2Connection objects are not thread-safe. They cannot safely be accessed from multiple threads at once. This is a deliberate design decision: it is not trivially possible to design the H2Connection object in a way that would be either lock-free or have the locks at a fine granularity.

Your implementations should bear this in mind, and handle it appropriately. It should be simple enough to use locking alongside the H2Connection: simply lock around the connection object itself. Because the H2Connection object does no I/O it should be entirely safe to do that. Alternatively, have a single thread take ownership of the H2Connection and use a message-passing interface to serialize access to the H2Connection.

If you are using a non-threaded concurrency approach (e.g. Twisted), this should not affect you.

Internal Buffers

In order to avoid doing I/O, the H2Connection employs an internal buffer. This buffer is unbounded in size: it can potentially grow infinitely. This means that, if you are not making sure to regularly empty it, you are at risk of exceeding the memory limit of a single process and finding your program crashes.

It is highly recommended that you send data at regular intervals, ideally as soon as possible.

Sending Data

When sending data on the network, it’s important to remember that you may not be able to send an unbounded amount of data at once. Particularly when using TCP, it is often the case that there are limits on how much data may be in flight at any one time. These limits can be very low, and your operating system will only buffer so much data in memory before it starts to complain.

For this reason, it is possible to consume only a subset of the data available when you call data_to_send. However, once you have pulled the data out of the H2Connection internal buffer, it is not possible to put it back on again. For that reason, it is advisable that you confirm how much space is available in the OS buffer before sending.

Alternatively, use tools made available by your framework. For example, the Python standard library socket module provides a sendall method that will automatically block until all the data has been sent. This will enable you to always use the unbounded form of data_to_send, and will help you avoid subtle bugs.

When To Send

In addition to knowing how much data to send (see Sending Data) it is important to know when to send data. For h2, this amounts to knowing when to call data_to_send.

h2 may write data into its send buffer at two times. The first is whenever receive_data is called. This data is sent in response to some control frames that require no user input: for example, responding to PING frames. The second time is in response to user action: whenever a user calls a method like send_headers, data may be written into the buffer.

In a standard design for a h2 consumer, then, that means there are two places where you’ll potentially want to send data. The first is in your “receive data” loop. This is where you take the data you receive, pass it into receive_data, and then dispatch events. For this loop, it is usually best to save sending data until the loop is complete: that allows you to empty the buffer only once.

The other place you’ll want to send the data is when initiating requests or taking any other active, unprompted action on the connection. In this instance, you’ll want to make all the relevant send_* calls, and then call data_to_send.

Headers

HTTP/2 defines several “special header fields” which are used to encode data that was previously sent in either the request or status line of HTTP/1.1. These header fields are distinguished from ordinary header fields because their field name begins with a : character. The special header fields defined in RFC 7540 are:

  • :status

  • :path

  • :method

  • :scheme

  • :authority

RFC 7540 mandates that all of these header fields appear first in the header block, before the ordinary header fields. This could cause difficulty if the send_headers method accepted a plain dict for the headers argument, because dict objects are unordered. For this reason, we require that you provide a list of two-tuples.

Flow Control

HTTP/2 defines a complex flow control system that uses a sliding window of data on both a per-stream and per-connection basis. Essentially, each implementation allows its peer to send a specific amount of data at any time (the “flow control window”) before it must stop. Each stream has a separate window, and the connection as a whole has a window. Each window can be opened by an implementation by sending a WINDOW_UPDATE frame, either on a specific stream (causing the window for that stream to be opened), or on stream 0, which causes the window for the entire connection to be opened.

In HTTP/2, only data in DATA frames is flow controlled. All other frames are exempt from flow control. Each DATA frame consumes both stream and connection flow control window bytes. This means that the maximum amount of data that can be sent on any one stream before a WINDOW_UPDATE frame is received is the lower of the stream and connection windows. The maximum amount of data that can be sent on all streams before a WINDOW_UPDATE frame is received is the size of the connection flow control window.

Working With Flow Control

The amount of flow control window a DATA frame consumes is the sum of both its contained application data and the amount of padding used. h2 shows this to the user in a DataReceived event by using the flow_controlled_length field. When working with flow control in h2, users must use this field: simply using len(datareceived.data) can eventually lead to deadlock.

When data has been received and given to the user in a DataReceived, it is the responsibility of the user to re-open the flow control window when the user is ready for more data. h2 does not do this automatically to avoid flooding the user with data: if we did, the remote peer could send unbounded amounts of data that the user would need to buffer before processing.

To re-open the flow control window, then, the user must call increment_flow_control_window with the flow_controlled_length of the received data. h2 requires that you manage both the connection and the stream flow control windows separately, so you may need to increment both the stream the data was received on and stream 0.

When sending data, a HTTP/2 implementation must not send more than flow control window available for that stream. As noted above, the maximum amount of data that can be sent on the stream is the minimum of the stream and the connection flow control windows. You can find out how much data you can send on a given stream by using the local_flow_control_window method, which will do all of these calculations for you. If you attempt to send more than this amount of data on a stream, h2 will throw a ProtocolError and refuse to send the data.

In h2, receiving a WINDOW_UPDATE frame causes a WindowUpdated event to fire. This will notify you that there is potentially more room in a flow control window. Note that, just because an increment of a given size was received does not mean that that much more data can be sent: remember that both the connection and stream flow control windows constrain how much data can be sent.

As a result, when a WindowUpdated event fires with a non-zero stream ID, and the user has more data to send on that stream, the user should call local_flow_control_window to check if there really is more room to send data on that stream.

When a WindowUpdated event fires with a stream ID of 0, that may have unblocked all streams that are currently blocked. The user should use local_flow_control_window to check all blocked streams to see if more data is available.

Auto Flow Control

New in version 2.5.0.

In most cases, there is no advantage for users in managing their own flow control strategies. While particular high performance or specific-use-case applications may gain value from directly controlling the emission of WINDOW_UPDATE frames, the average application can use a lowest-common-denominator strategy to emit those frames. As of version 2.5.0, h2 now provides this automatic strategy for users, if they want to use it.

This automatic strategy is built around a single method: acknowledge_received_data. This method flags to the connection object that your application has dealt with a certain number of flow controlled bytes, and that the window should be incremented in some way. Whenever your application has “processed” some received bytes, this method should be called to signal that they have been processed.

The key difference between this method and increment_flow_control_window is that the method acknowledge_received_data does not guarantee that it will emit a WINDOW_UPDATE frame, and if it does it will not necessarily emit them for only the stream or only the frame. Instead, the WINDOW_UPDATE frames will be coalesced: they will be emitted only when a certain number of bytes have been freed up.

For most applications, this method should be preferred to the manual flow control mechanism.