| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- """
- 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.
- """
- from __future__ import annotations
- import binascii
- import sys
- from dataclasses import dataclass
- from typing import TYPE_CHECKING, Any
- from .settings import ChangedSetting, SettingCodes, Settings, _setting_code_from_int
- if TYPE_CHECKING: # pragma: no cover
- from hpack.struct import Header
- from hyperframe.frame import Frame
- from .errors import ErrorCodes
- if sys.version_info < (3, 10): # pragma: no cover
- kw_only: dict[str, bool] = {}
- else: # pragma: no cover
- kw_only = {"kw_only": True}
- _LAZY_INIT: Any = object()
- """
- Some h2 events are instantiated by the state machine, but its attributes are
- subsequently populated by H2Stream. To make this work with strict type annotations
- on the events, they are temporarily set to this placeholder value.
- This value should never be exposed to users.
- """
- class Event:
- """
- Base class for h2 events.
- """
- @dataclass(**kw_only)
- class RequestReceived(Event):
- """
- The RequestReceived event is fired whenever all of a request's headers
- are received. This event carries the HTTP headers for the given request
- and the stream ID of the new stream.
- In HTTP/2, headers may be sent as a HEADERS frame followed by zero or more
- CONTINUATION frames with the final frame setting the END_HEADERS flag.
- This event is fired after the entire sequence is 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.
- """
- stream_id: int
- """The Stream ID for the stream this request was made on."""
- headers: list[Header] = _LAZY_INIT
- """The request headers."""
- stream_ended: StreamEnded | None = None
- """
- If this request also ended the stream, the associated
- :class:`StreamEnded <h2.events.StreamEnded>` event will be available
- here.
- .. versionadded:: 2.4.0
- """
- priority_updated: PriorityUpdated | None = 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
- """
- def __repr__(self) -> str:
- return f"<RequestReceived stream_id:{self.stream_id}, headers:{self.headers}>"
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """The Stream ID for the stream this response was made on."""
- headers: list[Header] = _LAZY_INIT
- """The response headers."""
- stream_ended: StreamEnded | None = None
- """
- If this response also ended the stream, the associated
- :class:`StreamEnded <h2.events.StreamEnded>` event will be available
- here.
- .. versionadded:: 2.4.0
- """
- priority_updated: PriorityUpdated | None = 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
- """
- def __repr__(self) -> str:
- return f"<ResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>"
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """The Stream ID for the stream on which these trailers were received."""
- headers: list[Header] = _LAZY_INIT
- """The trailers themselves."""
- stream_ended: StreamEnded | None = None
- """
- Trailers always end streams. This property has the associated
- :class:`StreamEnded <h2.events.StreamEnded>` in it.
- .. versionadded:: 2.4.0
- """
- priority_updated: PriorityUpdated | None = 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
- """
- def __repr__(self) -> str:
- return f"<TrailersReceived stream_id:{self.stream_id}, headers:{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.
- """
- 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.
- """
- 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.
- """
- 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.
- """
- 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.
- """
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """The Stream ID for the stream this informational response was made on."""
- headers: list[Header] = _LAZY_INIT
- """The headers for this informational response."""
- priority_updated: PriorityUpdated | None = 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
- """
- def __repr__(self) -> str:
- return f"<InformationalResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>"
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """The Stream ID for the stream this data was received on."""
- data: bytes = _LAZY_INIT
- """The data itself."""
- flow_controlled_length: int = _LAZY_INIT
- """
- 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)``.
- """
- stream_ended: StreamEnded | None = 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
- """
- def __repr__(self) -> str:
- return (
- "<DataReceived stream_id:{}, "
- "flow_controlled_length:{}, "
- "data:{}>".format(
- self.stream_id,
- self.flow_controlled_length,
- _bytes_representation(self.data[:20]) if self.data else "",
- )
- )
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """
- The Stream ID of the stream whose flow control window was changed.
- May be ``0`` if the connection window was changed.
- """
- delta: int = _LAZY_INIT
- """
- The window delta.
- """
- def __repr__(self) -> str:
- return f"<WindowUpdated stream_id:{self.stream_id}, delta:{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) -> None:
- #: A dictionary of setting byte to
- #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing
- #: the changed settings.
- self.changed_settings: dict[int, ChangedSetting] = {}
- @classmethod
- def from_settings(cls,
- old_settings: Settings | dict[int, int],
- new_settings: dict[int, int]) -> RemoteSettingsChanged:
- """
- 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():
- s = _setting_code_from_int(setting)
- original_value = old_settings.get(s)
- change = ChangedSetting(s, original_value, new_value)
- e.changed_settings[s] = change
- return e
- def __repr__(self) -> str:
- return "<RemoteSettingsChanged changed_settings:{{{}}}>".format(
- ", ".join(repr(cs) for cs in self.changed_settings.values()),
- )
- @dataclass(**kw_only)
- 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
- """
- ping_data: bytes
- """The data included on the ping."""
- def __repr__(self) -> str:
- return f"<PingReceived ping_data:{_bytes_representation(self.ping_data)}>"
- @dataclass(**kw_only)
- 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``.
- """
- ping_data: bytes
- """The data included on the ping."""
- def __repr__(self) -> str:
- return f"<PingAckReceived ping_data:{_bytes_representation(self.ping_data)}>"
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """The Stream ID of the stream that was closed."""
- def __repr__(self) -> str:
- return f"<StreamEnded stream_id:{self.stream_id}>"
- @dataclass(**kw_only)
- 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.
- """
- stream_id: int
- """
- The Stream ID of the stream that was reset.
- """
- error_code: ErrorCodes | int = _LAZY_INIT
- """
- The error code given.
- """
- remote_reset: bool = True
- """
- Whether the remote peer sent a RST_STREAM or we did.
- """
- def __repr__(self) -> str:
- return f"<StreamReset stream_id:{self.stream_id}, error_code:{self.error_code!s}, remote_reset:{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) -> None:
- #: The Stream ID of the stream created by the push.
- self.pushed_stream_id: int | None = None
- #: The Stream ID of the stream that the push is related to.
- self.parent_stream_id: int | None = None
- #: The request headers, sent by the remote party in the push.
- self.headers: list[Header] | None = None
- def __repr__(self) -> str:
- return (
- f"<PushedStreamReceived pushed_stream_id:{self.pushed_stream_id}, parent_stream_id:{self.parent_stream_id}, "
- f"headers:{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) -> None:
- #: A dictionary of setting byte to
- #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing
- #: the changed settings.
- self.changed_settings: dict[SettingCodes | int, ChangedSetting] = {}
- def __repr__(self) -> str:
- s = ", ".join(repr(cs) for cs in self.changed_settings.values())
- return f"<SettingsAcknowledged changed_settings:{{{s}}}>"
- 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) -> None:
- #: The ID of the stream whose priority information is being updated.
- self.stream_id: int | None = None
- #: The new stream weight. May be the same as the original stream
- #: weight. An integer between 1 and 256.
- self.weight: int | None = None
- #: The stream ID this stream now depends on. May be ``0``.
- self.depends_on: int | None = 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: bool | None = None
- def __repr__(self) -> str:
- return (
- f"<PriorityUpdated stream_id:{self.stream_id}, weight:{self.weight}, depends_on:{self.depends_on}, "
- f"exclusive:{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) -> None:
- #: 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: ErrorCodes | int | None = 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: int | None = None
- #: Additional debug data that can be appended to GOAWAY frame.
- self.additional_data: bytes | None = None
- def __repr__(self) -> str:
- return (
- "<ConnectionTerminated error_code:{!s}, last_stream_id:{}, "
- "additional_data:{}>".format(
- 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) -> None:
- #: The origin to which the alternative service field value applies.
- #: This field 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 the stream on which the frame was
- #: received.
- self.origin: bytes | None = None
- #: The ALTSVC field value. This contains information about the HTTP
- #: alternative service being advertised by the server. 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: bytes | None = None
- def __repr__(self) -> str:
- return (
- "<AlternativeServiceAvailable origin:{}, field_value:{}>".format(
- (self.origin or b"").decode("utf-8", "ignore"),
- (self.field_value or b"").decode("utf-8", "ignore"),
- )
- )
- @dataclass(**kw_only)
- 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
- """
- frame: Frame
- def __repr__(self) -> str:
- return "<UnknownFrameReceived>"
- def _bytes_representation(data: bytes | None) -> str | None:
- """
- 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")
|