|
|
|
""" |
|
h2/events |
|
~~~~~~~~~ |
|
|
|
Defines Event types for HTTP/2. |
|
|
|
Events are returned by the H2 state machine to allow implementations to keep |
|
track of events triggered by receiving data. Each time data is provided to the |
|
H2 state machine it processes the data and returns a list of Event objects. |
|
""" |
|
import binascii |
|
|
|
from .settings import ChangedSetting, _setting_code_from_int |
|
|
|
|
|
class Event: |
|
""" |
|
Base class for h2 events. |
|
""" |
|
pass |
|
|
|
|
|
class RequestReceived(Event): |
|
""" |
|
The RequestReceived event is fired whenever request headers are received. |
|
This event carries the HTTP headers for the given request and the stream ID |
|
of the new stream. |
|
|
|
.. versionchanged:: 2.3.0 |
|
Changed the type of ``headers`` to :class:`HeaderTuple |
|
<hpack:hpack.HeaderTuple>`. This has no effect on current users. |
|
|
|
.. versionchanged:: 2.4.0 |
|
Added ``stream_ended`` and ``priority_updated`` properties. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
self.headers = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.stream_ended = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.priority_updated = None |
|
|
|
def __repr__(self): |
|
return "<RequestReceived stream_id:%s, headers:%s>" % ( |
|
self.stream_id, self.headers |
|
) |
|
|
|
|
|
class ResponseReceived(Event): |
|
""" |
|
The ResponseReceived event is fired whenever response headers are received. |
|
This event carries the HTTP headers for the given response and the stream |
|
ID of the new stream. |
|
|
|
.. versionchanged:: 2.3.0 |
|
Changed the type of ``headers`` to :class:`HeaderTuple |
|
<hpack:hpack.HeaderTuple>`. This has no effect on current users. |
|
|
|
.. versionchanged:: 2.4.0 |
|
Added ``stream_ended`` and ``priority_updated`` properties. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
self.headers = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.stream_ended = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.priority_updated = None |
|
|
|
def __repr__(self): |
|
return "<ResponseReceived stream_id:%s, headers:%s>" % ( |
|
self.stream_id, self.headers |
|
) |
|
|
|
|
|
class TrailersReceived(Event): |
|
""" |
|
The TrailersReceived event is fired whenever trailers are received on a |
|
stream. Trailers are a set of headers sent after the body of the |
|
request/response, and are used to provide information that wasn't known |
|
ahead of time (e.g. content-length). This event carries the HTTP header |
|
fields that form the trailers and the stream ID of the stream on which they |
|
were received. |
|
|
|
.. versionchanged:: 2.3.0 |
|
Changed the type of ``headers`` to :class:`HeaderTuple |
|
<hpack:hpack.HeaderTuple>`. This has no effect on current users. |
|
|
|
.. versionchanged:: 2.4.0 |
|
Added ``stream_ended`` and ``priority_updated`` properties. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
self.headers = None |
|
|
|
|
|
|
|
|
|
|
|
self.stream_ended = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.priority_updated = None |
|
|
|
def __repr__(self): |
|
return "<TrailersReceived stream_id:%s, headers:%s>" % ( |
|
self.stream_id, self.headers |
|
) |
|
|
|
|
|
class _HeadersSent(Event): |
|
""" |
|
The _HeadersSent event is fired whenever headers are sent. |
|
|
|
This is an internal event, used to determine validation steps on |
|
outgoing header blocks. |
|
""" |
|
pass |
|
|
|
|
|
class _ResponseSent(_HeadersSent): |
|
""" |
|
The _ResponseSent event is fired whenever response headers are sent |
|
on a stream. |
|
|
|
This is an internal event, used to determine validation steps on |
|
outgoing header blocks. |
|
""" |
|
pass |
|
|
|
|
|
class _RequestSent(_HeadersSent): |
|
""" |
|
The _RequestSent event is fired whenever request headers are sent |
|
on a stream. |
|
|
|
This is an internal event, used to determine validation steps on |
|
outgoing header blocks. |
|
""" |
|
pass |
|
|
|
|
|
class _TrailersSent(_HeadersSent): |
|
""" |
|
The _TrailersSent event is fired whenever trailers are sent on a |
|
stream. Trailers are a set of headers sent after the body of the |
|
request/response, and are used to provide information that wasn't known |
|
ahead of time (e.g. content-length). |
|
|
|
This is an internal event, used to determine validation steps on |
|
outgoing header blocks. |
|
""" |
|
pass |
|
|
|
|
|
class _PushedRequestSent(_HeadersSent): |
|
""" |
|
The _PushedRequestSent event is fired whenever pushed request headers are |
|
sent. |
|
|
|
This is an internal event, used to determine validation steps on outgoing |
|
header blocks. |
|
""" |
|
pass |
|
|
|
|
|
class InformationalResponseReceived(Event): |
|
""" |
|
The InformationalResponseReceived event is fired when an informational |
|
response (that is, one whose status code is a 1XX code) is received from |
|
the remote peer. |
|
|
|
The remote peer may send any number of these, from zero upwards. These |
|
responses are most commonly sent in response to requests that have the |
|
``expect: 100-continue`` header field present. Most users can safely |
|
ignore this event unless you are intending to use the |
|
``expect: 100-continue`` flow, or are for any reason expecting a different |
|
1XX status code. |
|
|
|
.. versionadded:: 2.2.0 |
|
|
|
.. versionchanged:: 2.3.0 |
|
Changed the type of ``headers`` to :class:`HeaderTuple |
|
<hpack:hpack.HeaderTuple>`. This has no effect on current users. |
|
|
|
.. versionchanged:: 2.4.0 |
|
Added ``priority_updated`` property. |
|
""" |
|
def __init__(self): |
|
|
|
|
|
self.stream_id = None |
|
|
|
|
|
self.headers = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.priority_updated = None |
|
|
|
def __repr__(self): |
|
return "<InformationalResponseReceived stream_id:%s, headers:%s>" % ( |
|
self.stream_id, self.headers |
|
) |
|
|
|
|
|
class DataReceived(Event): |
|
""" |
|
The DataReceived event is fired whenever data is received on a stream from |
|
the remote peer. The event carries the data itself, and the stream ID on |
|
which the data was received. |
|
|
|
.. versionchanged:: 2.4.0 |
|
Added ``stream_ended`` property. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
self.data = None |
|
|
|
|
|
|
|
|
|
|
|
self.flow_controlled_length = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.stream_ended = None |
|
|
|
def __repr__(self): |
|
return ( |
|
"<DataReceived stream_id:%s, " |
|
"flow_controlled_length:%s, " |
|
"data:%s>" % ( |
|
self.stream_id, |
|
self.flow_controlled_length, |
|
_bytes_representation(self.data[:20]), |
|
) |
|
) |
|
|
|
|
|
class WindowUpdated(Event): |
|
""" |
|
The WindowUpdated event is fired whenever a flow control window changes |
|
size. HTTP/2 defines flow control windows for connections and streams: this |
|
event fires for both connections and streams. The event carries the ID of |
|
the stream to which it applies (set to zero if the window update applies to |
|
the connection), and the delta in the window size. |
|
""" |
|
def __init__(self): |
|
|
|
|
|
self.stream_id = None |
|
|
|
|
|
self.delta = None |
|
|
|
def __repr__(self): |
|
return "<WindowUpdated stream_id:%s, delta:%s>" % ( |
|
self.stream_id, self.delta |
|
) |
|
|
|
|
|
class RemoteSettingsChanged(Event): |
|
""" |
|
The RemoteSettingsChanged event is fired whenever the remote peer changes |
|
its settings. It contains a complete inventory of changed settings, |
|
including their previous values. |
|
|
|
In HTTP/2, settings changes need to be acknowledged. h2 automatically |
|
acknowledges settings changes for efficiency. However, it is possible that |
|
the caller may not be happy with the changed setting. |
|
|
|
When this event is received, the caller should confirm that the new |
|
settings are acceptable. If they are not acceptable, the user should close |
|
the connection with the error code :data:`PROTOCOL_ERROR |
|
<h2.errors.ErrorCodes.PROTOCOL_ERROR>`. |
|
|
|
.. versionchanged:: 2.0.0 |
|
Prior to this version the user needed to acknowledge settings changes. |
|
This is no longer the case: h2 now automatically acknowledges |
|
them. |
|
""" |
|
def __init__(self): |
|
|
|
|
|
|
|
self.changed_settings = {} |
|
|
|
@classmethod |
|
def from_settings(cls, old_settings, new_settings): |
|
""" |
|
Build a RemoteSettingsChanged event from a set of changed settings. |
|
|
|
:param old_settings: A complete collection of old settings, in the form |
|
of a dictionary of ``{setting: value}``. |
|
:param new_settings: All the changed settings and their new values, in |
|
the form of a dictionary of ``{setting: value}``. |
|
""" |
|
e = cls() |
|
for setting, new_value in new_settings.items(): |
|
setting = _setting_code_from_int(setting) |
|
original_value = old_settings.get(setting) |
|
change = ChangedSetting(setting, original_value, new_value) |
|
e.changed_settings[setting] = change |
|
|
|
return e |
|
|
|
def __repr__(self): |
|
return "<RemoteSettingsChanged changed_settings:{%s}>" % ( |
|
", ".join(repr(cs) for cs in self.changed_settings.values()), |
|
) |
|
|
|
|
|
class PingReceived(Event): |
|
""" |
|
The PingReceived event is fired whenever a PING is received. It contains |
|
the 'opaque data' of the PING frame. A ping acknowledgment with the same |
|
'opaque data' is automatically emitted after receiving a ping. |
|
|
|
.. versionadded:: 3.1.0 |
|
""" |
|
def __init__(self): |
|
|
|
self.ping_data = None |
|
|
|
def __repr__(self): |
|
return "<PingReceived ping_data:%s>" % ( |
|
_bytes_representation(self.ping_data), |
|
) |
|
|
|
|
|
class PingAckReceived(Event): |
|
""" |
|
The PingAckReceived event is fired whenever a PING acknowledgment is |
|
received. It contains the 'opaque data' of the PING+ACK frame, allowing the |
|
user to correlate PINGs and calculate RTT. |
|
|
|
.. versionadded:: 3.1.0 |
|
|
|
.. versionchanged:: 4.0.0 |
|
Removed deprecated but equivalent ``PingAcknowledged``. |
|
""" |
|
def __init__(self): |
|
|
|
self.ping_data = None |
|
|
|
def __repr__(self): |
|
return "<PingAckReceived ping_data:%s>" % ( |
|
_bytes_representation(self.ping_data), |
|
) |
|
|
|
|
|
class StreamEnded(Event): |
|
""" |
|
The StreamEnded event is fired whenever a stream is ended by a remote |
|
party. The stream may not be fully closed if it has not been closed |
|
locally, but no further data or headers should be expected on that stream. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
def __repr__(self): |
|
return "<StreamEnded stream_id:%s>" % self.stream_id |
|
|
|
|
|
class StreamReset(Event): |
|
""" |
|
The StreamReset event is fired in two situations. The first is when the |
|
remote party forcefully resets the stream. The second is when the remote |
|
party has made a protocol error which only affects a single stream. In this |
|
case, h2 will terminate the stream early and return this event. |
|
|
|
.. versionchanged:: 2.0.0 |
|
This event is now fired when h2 automatically resets a stream. |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
|
|
self.error_code = None |
|
|
|
|
|
self.remote_reset = True |
|
|
|
def __repr__(self): |
|
return "<StreamReset stream_id:%s, error_code:%s, remote_reset:%s>" % ( |
|
self.stream_id, self.error_code, self.remote_reset |
|
) |
|
|
|
|
|
class PushedStreamReceived(Event): |
|
""" |
|
The PushedStreamReceived event is fired whenever a pushed stream has been |
|
received from a remote peer. The event carries on it the new stream ID, the |
|
ID of the parent stream, and the request headers pushed by the remote peer. |
|
""" |
|
def __init__(self): |
|
|
|
self.pushed_stream_id = None |
|
|
|
|
|
self.parent_stream_id = None |
|
|
|
|
|
self.headers = None |
|
|
|
def __repr__(self): |
|
return ( |
|
"<PushedStreamReceived pushed_stream_id:%s, parent_stream_id:%s, " |
|
"headers:%s>" % ( |
|
self.pushed_stream_id, |
|
self.parent_stream_id, |
|
self.headers, |
|
) |
|
) |
|
|
|
|
|
class SettingsAcknowledged(Event): |
|
""" |
|
The SettingsAcknowledged event is fired whenever a settings ACK is received |
|
from the remote peer. The event carries on it the settings that were |
|
acknowedged, in the same format as |
|
:class:`h2.events.RemoteSettingsChanged`. |
|
""" |
|
def __init__(self): |
|
|
|
|
|
|
|
self.changed_settings = {} |
|
|
|
def __repr__(self): |
|
return "<SettingsAcknowledged changed_settings:{%s}>" % ( |
|
", ".join(repr(cs) for cs in self.changed_settings.values()), |
|
) |
|
|
|
|
|
class PriorityUpdated(Event): |
|
""" |
|
The PriorityUpdated event is fired whenever a stream sends updated priority |
|
information. This can occur when the stream is opened, or at any time |
|
during the stream lifetime. |
|
|
|
This event is purely advisory, and does not need to be acted on. |
|
|
|
.. versionadded:: 2.0.0 |
|
""" |
|
def __init__(self): |
|
|
|
self.stream_id = None |
|
|
|
|
|
|
|
self.weight = None |
|
|
|
|
|
self.depends_on = None |
|
|
|
|
|
|
|
|
|
self.exclusive = None |
|
|
|
def __repr__(self): |
|
return ( |
|
"<PriorityUpdated stream_id:%s, weight:%s, depends_on:%s, " |
|
"exclusive:%s>" % ( |
|
self.stream_id, |
|
self.weight, |
|
self.depends_on, |
|
self.exclusive |
|
) |
|
) |
|
|
|
|
|
class ConnectionTerminated(Event): |
|
""" |
|
The ConnectionTerminated event is fired when a connection is torn down by |
|
the remote peer using a GOAWAY frame. Once received, no further action may |
|
be taken on the connection: a new connection must be established. |
|
""" |
|
def __init__(self): |
|
|
|
|
|
|
|
self.error_code = None |
|
|
|
|
|
|
|
|
|
self.last_stream_id = None |
|
|
|
|
|
self.additional_data = None |
|
|
|
def __repr__(self): |
|
return ( |
|
"<ConnectionTerminated error_code:%s, last_stream_id:%s, " |
|
"additional_data:%s>" % ( |
|
self.error_code, |
|
self.last_stream_id, |
|
_bytes_representation( |
|
self.additional_data[:20] |
|
if self.additional_data else None) |
|
) |
|
) |
|
|
|
|
|
class AlternativeServiceAvailable(Event): |
|
""" |
|
The AlternativeServiceAvailable event is fired when the remote peer |
|
advertises an `RFC 7838 <https://tools.ietf.org/html/rfc7838>`_ Alternative |
|
Service using an ALTSVC frame. |
|
|
|
This event always carries the origin to which the ALTSVC information |
|
applies. That origin is either supplied by the server directly, or inferred |
|
by h2 from the ``:authority`` pseudo-header field that was sent by |
|
the user when initiating a given stream. |
|
|
|
This event also carries what RFC 7838 calls the "Alternative Service Field |
|
Value", which is formatted like a HTTP header field and contains the |
|
relevant alternative service information. h2 does not parse or in any |
|
way modify that information: the user is required to do that. |
|
|
|
This event can only be fired on the client end of a connection. |
|
|
|
.. versionadded:: 2.3.0 |
|
""" |
|
def __init__(self): |
|
|
|
|
|
|
|
|
|
|
|
self.origin = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
self.field_value = None |
|
|
|
def __repr__(self): |
|
return ( |
|
"<AlternativeServiceAvailable origin:%s, field_value:%s>" % ( |
|
self.origin.decode('utf-8', 'ignore'), |
|
self.field_value.decode('utf-8', 'ignore'), |
|
) |
|
) |
|
|
|
|
|
class UnknownFrameReceived(Event): |
|
""" |
|
The UnknownFrameReceived event is fired when the remote peer sends a frame |
|
that h2 does not understand. This occurs primarily when the remote |
|
peer is employing HTTP/2 extensions that h2 doesn't know anything |
|
about. |
|
|
|
RFC 7540 requires that HTTP/2 implementations ignore these frames. h2 |
|
does so. However, this event is fired to allow implementations to perform |
|
special processing on those frames if needed (e.g. if the implementation |
|
is capable of handling the frame itself). |
|
|
|
.. versionadded:: 2.7.0 |
|
""" |
|
def __init__(self): |
|
|
|
self.frame = None |
|
|
|
def __repr__(self): |
|
return "<UnknownFrameReceived>" |
|
|
|
|
|
def _bytes_representation(data): |
|
""" |
|
Converts a bytestring into something that is safe to print on all Python |
|
platforms. |
|
|
|
This function is relatively expensive, so it should not be called on the |
|
mainline of the code. It's safe to use in things like object repr methods |
|
though. |
|
""" |
|
if data is None: |
|
return None |
|
|
|
return binascii.hexlify(data).decode('ascii') |
|
|