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
adviseable 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.