Source code for h2.events

# -*- coding: utf-8 -*-
"""
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(object):
    """
    Base class for h2 events.
    """
    pass


[docs]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): #: The Stream ID for the stream this request was made on. self.stream_id = None #: The request headers. self.headers = None #: If this request also ended the stream, the associated #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available #: here. #: #: .. versionadded:: 2.4.0 self.stream_ended = None #: If this request also had associated priority information, the #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` #: event will be available here. #: #: .. versionadded:: 2.4.0 self.priority_updated = None def __repr__(self): return "<RequestReceived stream_id:%s, headers:%s>" % ( self.stream_id, self.headers )
[docs]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): #: The Stream ID for the stream this response was made on. self.stream_id = None #: The response headers. self.headers = None #: If this response also ended the stream, the associated #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available #: here. #: #: .. versionadded:: 2.4.0 self.stream_ended = None #: If this response also had associated priority information, the #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` #: event will be available here. #: #: .. versionadded:: 2.4.0 self.priority_updated = None def __repr__(self): return "<ResponseReceived stream_id:%s, headers:%s>" % ( self.stream_id, self.headers )
[docs]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): #: The Stream ID for the stream on which these trailers were received. self.stream_id = None #: The trailers themselves. self.headers = None #: Trailers always end streams. This property has the associated #: :class:`StreamEnded <h2.events.StreamEnded>` in it. #: #: .. versionadded:: 2.4.0 self.stream_ended = None #: If the trailers also set associated priority information, the #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` #: event will be available here. #: #: .. versionadded:: 2.4.0 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
[docs]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): #: The Stream ID for the stream this informational response was made #: on. self.stream_id = None #: The headers for this informational response. self.headers = None #: If this response also had associated priority information, the #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` #: event will be available here. #: #: .. versionadded:: 2.4.0 self.priority_updated = None def __repr__(self): return "<InformationalResponseReceived stream_id:%s, headers:%s>" % ( self.stream_id, self.headers )
[docs]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): #: The Stream ID for the stream this data was received on. self.stream_id = None #: The data itself. self.data = None #: The amount of data received that counts against the flow control #: window. Note that padding counts against the flow control window, so #: when adjusting flow control you should always use this field rather #: than ``len(data)``. self.flow_controlled_length = None #: If this data chunk also completed the stream, the associated #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available #: here. #: #: .. versionadded:: 2.4.0 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]), ) )
[docs]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): #: The Stream ID of the stream whose flow control window was changed. #: May be ``0`` if the connection window was changed. self.stream_id = None #: The window delta. self.delta = None def __repr__(self): return "<WindowUpdated stream_id:%s, delta:%s>" % ( self.stream_id, self.delta )
[docs]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. hyper-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: hyper-h2 now automatically acknowledges them. """ def __init__(self): #: A dictionary of setting byte to #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing #: the changed settings. self.changed_settings = {} @classmethod
[docs] 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()), )
[docs]class PingAcknowledged(Event): """ The PingAcknowledged event is fired whenever a user-emitted PING is acknowledged. This contains the data in the ACK'ed PING, allowing the user to correlate PINGs and calculate RTT. """ def __init__(self): #: The data included on the ping. self.ping_data = None def __repr__(self): return "<PingAcknowledged ping_data:%s>" % ( _bytes_representation(self.ping_data), )
[docs]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): #: The Stream ID of the stream that was closed. self.stream_id = None def __repr__(self): return "<StreamEnded stream_id:%s>" % self.stream_id
[docs]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, Hyper-h2 will terminate the stream early and return this event. .. versionchanged:: 2.0.0 This event is now fired when Hyper-h2 automatically resets a stream. """ def __init__(self): #: The Stream ID of the stream that was reset. self.stream_id = None #: The error code given. Either one of :class:`ErrorCodes #: <h2.errors.ErrorCodes>` or ``int`` self.error_code = None #: Whether the remote peer sent a RST_STREAM or we did. 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 )
[docs]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): #: The Stream ID of the stream created by the push. self.pushed_stream_id = None #: The Stream ID of the stream that the push is related to. self.parent_stream_id = None #: The request headers, sent by the remote party in the push. 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, ) )
[docs]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): #: A dictionary of setting byte to #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing #: the changed settings. self.changed_settings = {} def __repr__(self): return "<SettingsAcknowledged changed_settings:{%s}>" % ( ", ".join(repr(cs) for cs in self.changed_settings.values()), )
[docs]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): #: The ID of the stream whose priority information is being updated. self.stream_id = None #: The new stream weight. May be the same as the original stream #: weight. An integer between 1 and 256. self.weight = None #: The stream ID this stream now depends on. May be ``0``. self.depends_on = None #: Whether the stream *exclusively* depends on the parent stream. If it #: does, this stream should inherit the current children of its new #: parent. 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 ) )
[docs]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): #: The error code cited when tearing down the connection. Should be #: one of :class:`ErrorCodes <h2.errors.ErrorCodes>`, but may not be if #: unknown HTTP/2 extensions are being used. self.error_code = None #: The stream ID of the last stream the remote peer saw. This can #: provide an indication of what data, if any, never reached the remote #: peer and so can safely be resent. self.last_stream_id = None #: Additional debug data that can be appended to GOAWAY frame. 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) ) )
[docs]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 hyper-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. Hyper-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): #: The origin to which the alternative service field value applies. #: This field is either supplied by the server directly, or inferred by #: hyper-h2 from the ``:authority`` pseudo-header field that was sent #: by the user when initiating the stream on which the frame was #: received. self.origin = None #: The ALTSVC field value. This contains information about the HTTP #: alternative service being advertised by the server. Hyper-h2 does #: not parse this field: it is left exactly as sent by the server. The #: structure of the data in this field is given by `RFC 7838 Section 3 #: <https://tools.ietf.org/html/rfc7838#section-3>`_. 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'), ) )
[docs]class UnknownFrameReceived(Event): """ The UnknownFrameReceived event is fired when the remote peer sends a frame that hyper-h2 does not understand. This occurs primarily when the remote peer is employing HTTP/2 extensions that hyper-h2 doesn't know anything about. RFC 7540 requires that HTTP/2 implementations ignore these frames. hyper-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): #: The hyperframe Frame object that encapsulates the received frame. 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 hex = binascii.hexlify(data) # This is moderately clever: on all Python versions hexlify returns a byte # string. On Python 3 we want an actual string, so we just check whether # that's what we have. if not isinstance(hex, str): # pragma: no cover hex = hex.decode('ascii') return hex