codekingpro/portable-devtools
114k
1"""2h2/events3~~~~~~~~~4 5Defines Event types for HTTP/2.6 7Events are returned by the H2 state machine to allow implementations to keep8track of events triggered by receiving data. Each time data is provided to the9H2 state machine it processes the data and returns a list of Event objects.10"""11from __future__ import annotations12 13import binascii14import sys15from dataclasses import dataclass16from typing import TYPE_CHECKING, Any17 18from .settings import ChangedSetting, SettingCodes, Settings, _setting_code_from_int19 20if TYPE_CHECKING: # pragma: no cover21 from hpack.struct import Header22 from hyperframe.frame import Frame23 24 from .errors import ErrorCodes25 26 27if sys.version_info < (3, 10): # pragma: no cover28 kw_only: dict[str, bool] = {}29else: # pragma: no cover30 kw_only = {"kw_only": True}31 32 33_LAZY_INIT: Any = object()34"""35Some h2 events are instantiated by the state machine, but its attributes are36subsequently populated by H2Stream. To make this work with strict type annotations37on the events, they are temporarily set to this placeholder value.38This value should never be exposed to users.39"""40 41 42class Event:43 """44 Base class for h2 events.45 """46 47 48@dataclass(**kw_only)49class RequestReceived(Event):50 """51 The RequestReceived event is fired whenever all of a request's headers52 are received. This event carries the HTTP headers for the given request53 and the stream ID of the new stream.54 55 In HTTP/2, headers may be sent as a HEADERS frame followed by zero or more56 CONTINUATION frames with the final frame setting the END_HEADERS flag.57 This event is fired after the entire sequence is received.58 59 .. versionchanged:: 2.3.060 Changed the type of ``headers`` to :class:`HeaderTuple61 <hpack:hpack.HeaderTuple>`. This has no effect on current users.62 63 .. versionchanged:: 2.4.064 Added ``stream_ended`` and ``priority_updated`` properties.65 """66 67 stream_id: int68 """The Stream ID for the stream this request was made on."""69 70 headers: list[Header] = _LAZY_INIT71 """The request headers."""72 73 stream_ended: StreamEnded | None = None74 """75 If this request also ended the stream, the associated76 :class:`StreamEnded <h2.events.StreamEnded>` event will be available77 here.78 79 .. versionadded:: 2.4.080 """81 82 priority_updated: PriorityUpdated | None = None83 """84 If this request also had associated priority information, the85 associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`86 event will be available here.87 88 .. versionadded:: 2.4.089 """90 91 def __repr__(self) -> str:92 return f"<RequestReceived stream_id:{self.stream_id}, headers:{self.headers}>"93 94 95@dataclass(**kw_only)96class ResponseReceived(Event):97 """98 The ResponseReceived event is fired whenever response headers are received.99 This event carries the HTTP headers for the given response and the stream100 ID of the new stream.101 102 .. versionchanged:: 2.3.0103 Changed the type of ``headers`` to :class:`HeaderTuple104 <hpack:hpack.HeaderTuple>`. This has no effect on current users.105 106 .. versionchanged:: 2.4.0107 Added ``stream_ended`` and ``priority_updated`` properties.108 """109 110 stream_id: int111 """The Stream ID for the stream this response was made on."""112 113 headers: list[Header] = _LAZY_INIT114 """The response headers."""115 116 stream_ended: StreamEnded | None = None117 """118 If this response also ended the stream, the associated119 :class:`StreamEnded <h2.events.StreamEnded>` event will be available120 here.121 122 .. versionadded:: 2.4.0123 """124 125 priority_updated: PriorityUpdated | None = None126 """127 If this response also had associated priority information, the128 associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`129 event will be available here.130 131 .. versionadded:: 2.4.0132 """133 134 def __repr__(self) -> str:135 return f"<ResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>"136 137 138@dataclass(**kw_only)139class TrailersReceived(Event):140 """141 The TrailersReceived event is fired whenever trailers are received on a142 stream. Trailers are a set of headers sent after the body of the143 request/response, and are used to provide information that wasn't known144 ahead of time (e.g. content-length). This event carries the HTTP header145 fields that form the trailers and the stream ID of the stream on which they146 were received.147 148 .. versionchanged:: 2.3.0149 Changed the type of ``headers`` to :class:`HeaderTuple150 <hpack:hpack.HeaderTuple>`. This has no effect on current users.151 152 .. versionchanged:: 2.4.0153 Added ``stream_ended`` and ``priority_updated`` properties.154 """155 156 stream_id: int157 """The Stream ID for the stream on which these trailers were received."""158 159 headers: list[Header] = _LAZY_INIT160 """The trailers themselves."""161 162 stream_ended: StreamEnded | None = None163 """164 Trailers always end streams. This property has the associated165 :class:`StreamEnded <h2.events.StreamEnded>` in it.166 167 .. versionadded:: 2.4.0168 """169 170 priority_updated: PriorityUpdated | None = None171 """172 If the trailers also set associated priority information, the173 associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`174 event will be available here.175 176 .. versionadded:: 2.4.0177 """178 179 def __repr__(self) -> str:180 return f"<TrailersReceived stream_id:{self.stream_id}, headers:{self.headers}>"181 182 183class _HeadersSent(Event):184 """185 The _HeadersSent event is fired whenever headers are sent.186 187 This is an internal event, used to determine validation steps on188 outgoing header blocks.189 """190 191 192 193class _ResponseSent(_HeadersSent):194 """195 The _ResponseSent event is fired whenever response headers are sent196 on a stream.197 198 This is an internal event, used to determine validation steps on199 outgoing header blocks.200 """201 202 203 204class _RequestSent(_HeadersSent):205 """206 The _RequestSent event is fired whenever request headers are sent207 on a stream.208 209 This is an internal event, used to determine validation steps on210 outgoing header blocks.211 """212 213 214 215class _TrailersSent(_HeadersSent):216 """217 The _TrailersSent event is fired whenever trailers are sent on a218 stream. Trailers are a set of headers sent after the body of the219 request/response, and are used to provide information that wasn't known220 ahead of time (e.g. content-length).221 222 This is an internal event, used to determine validation steps on223 outgoing header blocks.224 """225 226 227 228class _PushedRequestSent(_HeadersSent):229 """230 The _PushedRequestSent event is fired whenever pushed request headers are231 sent.232 233 This is an internal event, used to determine validation steps on outgoing234 header blocks.235 """236 237 238@dataclass(**kw_only)239class InformationalResponseReceived(Event):240 """241 The InformationalResponseReceived event is fired when an informational242 response (that is, one whose status code is a 1XX code) is received from243 the remote peer.244 245 The remote peer may send any number of these, from zero upwards. These246 responses are most commonly sent in response to requests that have the247 ``expect: 100-continue`` header field present. Most users can safely248 ignore this event unless you are intending to use the249 ``expect: 100-continue`` flow, or are for any reason expecting a different250 1XX status code.251 252 .. versionadded:: 2.2.0253 254 .. versionchanged:: 2.3.0255 Changed the type of ``headers`` to :class:`HeaderTuple256 <hpack:hpack.HeaderTuple>`. This has no effect on current users.257 258 .. versionchanged:: 2.4.0259 Added ``priority_updated`` property.260 """261 262 stream_id: int263 """The Stream ID for the stream this informational response was made on."""264 265 headers: list[Header] = _LAZY_INIT266 """The headers for this informational response."""267 268 priority_updated: PriorityUpdated | None = None269 """270 If this response also had associated priority information, the271 associated :class:`PriorityUpdated <h2.events.PriorityUpdated>`272 event will be available here.273 274 .. versionadded:: 2.4.0275 """276 277 def __repr__(self) -> str:278 return f"<InformationalResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>"279 280 281@dataclass(**kw_only)282class DataReceived(Event):283 """284 The DataReceived event is fired whenever data is received on a stream from285 the remote peer. The event carries the data itself, and the stream ID on286 which the data was received.287 288 .. versionchanged:: 2.4.0289 Added ``stream_ended`` property.290 """291 292 stream_id: int293 """The Stream ID for the stream this data was received on."""294 295 data: bytes = _LAZY_INIT296 """The data itself."""297 298 flow_controlled_length: int = _LAZY_INIT299 """300 The amount of data received that counts against the flow control301 window. Note that padding counts against the flow control window, so302 when adjusting flow control you should always use this field rather303 than ``len(data)``.304 """305 306 stream_ended: StreamEnded | None = None307 """308 If this data chunk also completed the stream, the associated309 :class:`StreamEnded <h2.events.StreamEnded>` event will be available310 here.311 312 .. versionadded:: 2.4.0313 """314 315 def __repr__(self) -> str:316 return (317 "<DataReceived stream_id:{}, "318 "flow_controlled_length:{}, "319 "data:{}>".format(320 self.stream_id,321 self.flow_controlled_length,322 _bytes_representation(self.data[:20]) if self.data else "",323 )324 )325 326 327@dataclass(**kw_only)328class WindowUpdated(Event):329 """330 The WindowUpdated event is fired whenever a flow control window changes331 size. HTTP/2 defines flow control windows for connections and streams: this332 event fires for both connections and streams. The event carries the ID of333 the stream to which it applies (set to zero if the window update applies to334 the connection), and the delta in the window size.335 """336 337 stream_id: int338 """339 The Stream ID of the stream whose flow control window was changed.340 May be ``0`` if the connection window was changed.341 """342 343 delta: int = _LAZY_INIT344 """345 The window delta.346 """347 348 def __repr__(self) -> str:349 return f"<WindowUpdated stream_id:{self.stream_id}, delta:{self.delta}>"350 351 352class RemoteSettingsChanged(Event):353 """354 The RemoteSettingsChanged event is fired whenever the remote peer changes355 its settings. It contains a complete inventory of changed settings,356 including their previous values.357 358 In HTTP/2, settings changes need to be acknowledged. h2 automatically359 acknowledges settings changes for efficiency. However, it is possible that360 the caller may not be happy with the changed setting.361 362 When this event is received, the caller should confirm that the new363 settings are acceptable. If they are not acceptable, the user should close364 the connection with the error code :data:`PROTOCOL_ERROR365 <h2.errors.ErrorCodes.PROTOCOL_ERROR>`.366 367 .. versionchanged:: 2.0.0368 Prior to this version the user needed to acknowledge settings changes.369 This is no longer the case: h2 now automatically acknowledges370 them.371 """372 373 def __init__(self) -> None:374 #: A dictionary of setting byte to375 #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing376 #: the changed settings.377 self.changed_settings: dict[int, ChangedSetting] = {}378 379 @classmethod380 def from_settings(cls,381 old_settings: Settings | dict[int, int],382 new_settings: dict[int, int]) -> RemoteSettingsChanged:383 """384 Build a RemoteSettingsChanged event from a set of changed settings.385 386 :param old_settings: A complete collection of old settings, in the form387 of a dictionary of ``{setting: value}``.388 :param new_settings: All the changed settings and their new values, in389 the form of a dictionary of ``{setting: value}``.390 """391 e = cls()392 for setting, new_value in new_settings.items():393 s = _setting_code_from_int(setting)394 original_value = old_settings.get(s)395 change = ChangedSetting(s, original_value, new_value)396 e.changed_settings[s] = change397 398 return e399 400 def __repr__(self) -> str:401 return "<RemoteSettingsChanged changed_settings:{{{}}}>".format(402 ", ".join(repr(cs) for cs in self.changed_settings.values()),403 )404 405 406@dataclass(**kw_only)407class PingReceived(Event):408 """409 The PingReceived event is fired whenever a PING is received. It contains410 the 'opaque data' of the PING frame. A ping acknowledgment with the same411 'opaque data' is automatically emitted after receiving a ping.412 413 .. versionadded:: 3.1.0414 """415 416 ping_data: bytes417 """The data included on the ping."""418 419 def __repr__(self) -> str:420 return f"<PingReceived ping_data:{_bytes_representation(self.ping_data)}>"421 422 423@dataclass(**kw_only)424class PingAckReceived(Event):425 """426 The PingAckReceived event is fired whenever a PING acknowledgment is427 received. It contains the 'opaque data' of the PING+ACK frame, allowing the428 user to correlate PINGs and calculate RTT.429 430 .. versionadded:: 3.1.0431 432 .. versionchanged:: 4.0.0433 Removed deprecated but equivalent ``PingAcknowledged``.434 """435 436 ping_data: bytes437 """The data included on the ping."""438 439 def __repr__(self) -> str:440 return f"<PingAckReceived ping_data:{_bytes_representation(self.ping_data)}>"441 442 443@dataclass(**kw_only)444class StreamEnded(Event):445 """446 The StreamEnded event is fired whenever a stream is ended by a remote447 party. The stream may not be fully closed if it has not been closed448 locally, but no further data or headers should be expected on that stream.449 """450 451 stream_id: int452 """The Stream ID of the stream that was closed."""453 454 def __repr__(self) -> str:455 return f"<StreamEnded stream_id:{self.stream_id}>"456 457 458@dataclass(**kw_only)459class StreamReset(Event):460 """461 The StreamReset event is fired in two situations. The first is when the462 remote party forcefully resets the stream. The second is when the remote463 party has made a protocol error which only affects a single stream. In this464 case, h2 will terminate the stream early and return this event.465 466 .. versionchanged:: 2.0.0467 This event is now fired when h2 automatically resets a stream.468 """469 470 stream_id: int471 """472 The Stream ID of the stream that was reset.473 """474 475 error_code: ErrorCodes | int = _LAZY_INIT476 """477 The error code given.478 """479 480 remote_reset: bool = True481 """482 Whether the remote peer sent a RST_STREAM or we did.483 """484 485 def __repr__(self) -> str:486 return f"<StreamReset stream_id:{self.stream_id}, error_code:{self.error_code!s}, remote_reset:{self.remote_reset}>"487 488 489class PushedStreamReceived(Event):490 """491 The PushedStreamReceived event is fired whenever a pushed stream has been492 received from a remote peer. The event carries on it the new stream ID, the493 ID of the parent stream, and the request headers pushed by the remote peer.494 """495 496 def __init__(self) -> None:497 #: The Stream ID of the stream created by the push.498 self.pushed_stream_id: int | None = None499 500 #: The Stream ID of the stream that the push is related to.501 self.parent_stream_id: int | None = None502 503 #: The request headers, sent by the remote party in the push.504 self.headers: list[Header] | None = None505 506 def __repr__(self) -> str:507 return (508 f"<PushedStreamReceived pushed_stream_id:{self.pushed_stream_id}, parent_stream_id:{self.parent_stream_id}, "509 f"headers:{self.headers}>"510 )511 512 513class SettingsAcknowledged(Event):514 """515 The SettingsAcknowledged event is fired whenever a settings ACK is received516 from the remote peer. The event carries on it the settings that were517 acknowedged, in the same format as518 :class:`h2.events.RemoteSettingsChanged`.519 """520 521 def __init__(self) -> None:522 #: A dictionary of setting byte to523 #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing524 #: the changed settings.525 self.changed_settings: dict[SettingCodes | int, ChangedSetting] = {}526 527 def __repr__(self) -> str:528 s = ", ".join(repr(cs) for cs in self.changed_settings.values())529 return f"<SettingsAcknowledged changed_settings:{{{s}}}>"530 531 532class PriorityUpdated(Event):533 """534 The PriorityUpdated event is fired whenever a stream sends updated priority535 information. This can occur when the stream is opened, or at any time536 during the stream lifetime.537 538 This event is purely advisory, and does not need to be acted on.539 540 .. versionadded:: 2.0.0541 """542 543 def __init__(self) -> None:544 #: The ID of the stream whose priority information is being updated.545 self.stream_id: int | None = None546 547 #: The new stream weight. May be the same as the original stream548 #: weight. An integer between 1 and 256.549 self.weight: int | None = None550 551 #: The stream ID this stream now depends on. May be ``0``.552 self.depends_on: int | None = None553 554 #: Whether the stream *exclusively* depends on the parent stream. If it555 #: does, this stream should inherit the current children of its new556 #: parent.557 self.exclusive: bool | None = None558 559 def __repr__(self) -> str:560 return (561 f"<PriorityUpdated stream_id:{self.stream_id}, weight:{self.weight}, depends_on:{self.depends_on}, "562 f"exclusive:{self.exclusive}>"563 )564 565 566class ConnectionTerminated(Event):567 """568 The ConnectionTerminated event is fired when a connection is torn down by569 the remote peer using a GOAWAY frame. Once received, no further action may570 be taken on the connection: a new connection must be established.571 """572 573 def __init__(self) -> None:574 #: The error code cited when tearing down the connection. Should be575 #: one of :class:`ErrorCodes <h2.errors.ErrorCodes>`, but may not be if576 #: unknown HTTP/2 extensions are being used.577 self.error_code: ErrorCodes | int | None = None578 579 #: The stream ID of the last stream the remote peer saw. This can580 #: provide an indication of what data, if any, never reached the remote581 #: peer and so can safely be resent.582 self.last_stream_id: int | None = None583 584 #: Additional debug data that can be appended to GOAWAY frame.585 self.additional_data: bytes | None = None586 587 def __repr__(self) -> str:588 return (589 "<ConnectionTerminated error_code:{!s}, last_stream_id:{}, "590 "additional_data:{}>".format(591 self.error_code,592 self.last_stream_id,593 _bytes_representation(594 self.additional_data[:20]595 if self.additional_data else None),596 )597 )598 599 600class AlternativeServiceAvailable(Event):601 """602 The AlternativeServiceAvailable event is fired when the remote peer603 advertises an `RFC 7838 <https://tools.ietf.org/html/rfc7838>`_ Alternative604 Service using an ALTSVC frame.605 606 This event always carries the origin to which the ALTSVC information607 applies. That origin is either supplied by the server directly, or inferred608 by h2 from the ``:authority`` pseudo-header field that was sent by609 the user when initiating a given stream.610 611 This event also carries what RFC 7838 calls the "Alternative Service Field612 Value", which is formatted like a HTTP header field and contains the613 relevant alternative service information. h2 does not parse or in any614 way modify that information: the user is required to do that.615 616 This event can only be fired on the client end of a connection.617 618 .. versionadded:: 2.3.0619 """620 621 def __init__(self) -> None:622 #: The origin to which the alternative service field value applies.623 #: This field is either supplied by the server directly, or inferred by624 #: h2 from the ``:authority`` pseudo-header field that was sent625 #: by the user when initiating the stream on which the frame was626 #: received.627 self.origin: bytes | None = None628 629 #: The ALTSVC field value. This contains information about the HTTP630 #: alternative service being advertised by the server. h2 does631 #: not parse this field: it is left exactly as sent by the server. The632 #: structure of the data in this field is given by `RFC 7838 Section 3633 #: <https://tools.ietf.org/html/rfc7838#section-3>`_.634 self.field_value: bytes | None = None635 636 def __repr__(self) -> str:637 return (638 "<AlternativeServiceAvailable origin:{}, field_value:{}>".format(639 (self.origin or b"").decode("utf-8", "ignore"),640 (self.field_value or b"").decode("utf-8", "ignore"),641 )642 )643 644 645@dataclass(**kw_only)646class UnknownFrameReceived(Event):647 """648 The UnknownFrameReceived event is fired when the remote peer sends a frame649 that h2 does not understand. This occurs primarily when the remote650 peer is employing HTTP/2 extensions that h2 doesn't know anything651 about.652 653 RFC 7540 requires that HTTP/2 implementations ignore these frames. h2654 does so. However, this event is fired to allow implementations to perform655 special processing on those frames if needed (e.g. if the implementation656 is capable of handling the frame itself).657 658 .. versionadded:: 2.7.0659 """660 661 frame: Frame662 663 def __repr__(self) -> str:664 return "<UnknownFrameReceived>"665 666 667def _bytes_representation(data: bytes | None) -> str | None:668 """669 Converts a bytestring into something that is safe to print on all Python670 platforms.671 672 This function is relatively expensive, so it should not be called on the673 mainline of the code. It's safe to use in things like object repr methods674 though.675 """676 if data is None:677 return None678 679 return binascii.hexlify(data).decode("ascii")680 