1# -*- coding: utf-8 -*- 2""" 3h2/events 4~~~~~~~~~ 5 6Defines Event types for HTTP/2. 7 8Events are returned by the H2 state machine to allow implementations to keep 9track of events triggered by receiving data. Each time data is provided to the 10H2 state machine it processes the data and returns a list of Event objects. 11""" 12import binascii 13 14from .settings import ChangedSetting, _setting_code_from_int 15 16 17class Event(object): 18 """ 19 Base class for h2 events. 20 """ 21 pass 22 23 24class RequestReceived(Event): 25 """ 26 The RequestReceived event is fired whenever request headers are received. 27 This event carries the HTTP headers for the given request and the stream ID 28 of the new stream. 29 30 .. versionchanged:: 2.3.0 31 Changed the type of ``headers`` to :class:`HeaderTuple 32 <hpack:hpack.HeaderTuple>`. This has no effect on current users. 33 34 .. versionchanged:: 2.4.0 35 Added ``stream_ended`` and ``priority_updated`` properties. 36 """ 37 def __init__(self): 38 #: The Stream ID for the stream this request was made on. 39 self.stream_id = None 40 41 #: The request headers. 42 self.headers = None 43 44 #: If this request also ended the stream, the associated 45 #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available 46 #: here. 47 #: 48 #: .. versionadded:: 2.4.0 49 self.stream_ended = None 50 51 #: If this request also had associated priority information, the 52 #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` 53 #: event will be available here. 54 #: 55 #: .. versionadded:: 2.4.0 56 self.priority_updated = None 57 58 def __repr__(self): 59 return "<RequestReceived stream_id:%s, headers:%s>" % ( 60 self.stream_id, self.headers 61 ) 62 63 64class ResponseReceived(Event): 65 """ 66 The ResponseReceived event is fired whenever response headers are received. 67 This event carries the HTTP headers for the given response and the stream 68 ID of the new stream. 69 70 .. versionchanged:: 2.3.0 71 Changed the type of ``headers`` to :class:`HeaderTuple 72 <hpack:hpack.HeaderTuple>`. This has no effect on current users. 73 74 .. versionchanged:: 2.4.0 75 Added ``stream_ended`` and ``priority_updated`` properties. 76 """ 77 def __init__(self): 78 #: The Stream ID for the stream this response was made on. 79 self.stream_id = None 80 81 #: The response headers. 82 self.headers = None 83 84 #: If this response also ended the stream, the associated 85 #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available 86 #: here. 87 #: 88 #: .. versionadded:: 2.4.0 89 self.stream_ended = None 90 91 #: If this response also had associated priority information, the 92 #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` 93 #: event will be available here. 94 #: 95 #: .. versionadded:: 2.4.0 96 self.priority_updated = None 97 98 def __repr__(self): 99 return "<ResponseReceived stream_id:%s, headers:%s>" % ( 100 self.stream_id, self.headers 101 ) 102 103 104class TrailersReceived(Event): 105 """ 106 The TrailersReceived event is fired whenever trailers are received on a 107 stream. Trailers are a set of headers sent after the body of the 108 request/response, and are used to provide information that wasn't known 109 ahead of time (e.g. content-length). This event carries the HTTP header 110 fields that form the trailers and the stream ID of the stream on which they 111 were received. 112 113 .. versionchanged:: 2.3.0 114 Changed the type of ``headers`` to :class:`HeaderTuple 115 <hpack:hpack.HeaderTuple>`. This has no effect on current users. 116 117 .. versionchanged:: 2.4.0 118 Added ``stream_ended`` and ``priority_updated`` properties. 119 """ 120 def __init__(self): 121 #: The Stream ID for the stream on which these trailers were received. 122 self.stream_id = None 123 124 #: The trailers themselves. 125 self.headers = None 126 127 #: Trailers always end streams. This property has the associated 128 #: :class:`StreamEnded <h2.events.StreamEnded>` in it. 129 #: 130 #: .. versionadded:: 2.4.0 131 self.stream_ended = None 132 133 #: If the trailers also set associated priority information, the 134 #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` 135 #: event will be available here. 136 #: 137 #: .. versionadded:: 2.4.0 138 self.priority_updated = None 139 140 def __repr__(self): 141 return "<TrailersReceived stream_id:%s, headers:%s>" % ( 142 self.stream_id, self.headers 143 ) 144 145 146class _HeadersSent(Event): 147 """ 148 The _HeadersSent event is fired whenever headers are sent. 149 150 This is an internal event, used to determine validation steps on 151 outgoing header blocks. 152 """ 153 pass 154 155 156class _ResponseSent(_HeadersSent): 157 """ 158 The _ResponseSent event is fired whenever response headers are sent 159 on a stream. 160 161 This is an internal event, used to determine validation steps on 162 outgoing header blocks. 163 """ 164 pass 165 166 167class _RequestSent(_HeadersSent): 168 """ 169 The _RequestSent event is fired whenever request headers are sent 170 on a stream. 171 172 This is an internal event, used to determine validation steps on 173 outgoing header blocks. 174 """ 175 pass 176 177 178class _TrailersSent(_HeadersSent): 179 """ 180 The _TrailersSent event is fired whenever trailers are sent on a 181 stream. Trailers are a set of headers sent after the body of the 182 request/response, and are used to provide information that wasn't known 183 ahead of time (e.g. content-length). 184 185 This is an internal event, used to determine validation steps on 186 outgoing header blocks. 187 """ 188 pass 189 190 191class _PushedRequestSent(_HeadersSent): 192 """ 193 The _PushedRequestSent event is fired whenever pushed request headers are 194 sent. 195 196 This is an internal event, used to determine validation steps on outgoing 197 header blocks. 198 """ 199 pass 200 201 202class InformationalResponseReceived(Event): 203 """ 204 The InformationalResponseReceived event is fired when an informational 205 response (that is, one whose status code is a 1XX code) is received from 206 the remote peer. 207 208 The remote peer may send any number of these, from zero upwards. These 209 responses are most commonly sent in response to requests that have the 210 ``expect: 100-continue`` header field present. Most users can safely 211 ignore this event unless you are intending to use the 212 ``expect: 100-continue`` flow, or are for any reason expecting a different 213 1XX status code. 214 215 .. versionadded:: 2.2.0 216 217 .. versionchanged:: 2.3.0 218 Changed the type of ``headers`` to :class:`HeaderTuple 219 <hpack:hpack.HeaderTuple>`. This has no effect on current users. 220 221 .. versionchanged:: 2.4.0 222 Added ``priority_updated`` property. 223 """ 224 def __init__(self): 225 #: The Stream ID for the stream this informational response was made 226 #: on. 227 self.stream_id = None 228 229 #: The headers for this informational response. 230 self.headers = None 231 232 #: If this response also had associated priority information, the 233 #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` 234 #: event will be available here. 235 #: 236 #: .. versionadded:: 2.4.0 237 self.priority_updated = None 238 239 def __repr__(self): 240 return "<InformationalResponseReceived stream_id:%s, headers:%s>" % ( 241 self.stream_id, self.headers 242 ) 243 244 245class DataReceived(Event): 246 """ 247 The DataReceived event is fired whenever data is received on a stream from 248 the remote peer. The event carries the data itself, and the stream ID on 249 which the data was received. 250 251 .. versionchanged:: 2.4.0 252 Added ``stream_ended`` property. 253 """ 254 def __init__(self): 255 #: The Stream ID for the stream this data was received on. 256 self.stream_id = None 257 258 #: The data itself. 259 self.data = None 260 261 #: The amount of data received that counts against the flow control 262 #: window. Note that padding counts against the flow control window, so 263 #: when adjusting flow control you should always use this field rather 264 #: than ``len(data)``. 265 self.flow_controlled_length = None 266 267 #: If this data chunk also completed the stream, the associated 268 #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available 269 #: here. 270 #: 271 #: .. versionadded:: 2.4.0 272 self.stream_ended = None 273 274 def __repr__(self): 275 return ( 276 "<DataReceived stream_id:%s, " 277 "flow_controlled_length:%s, " 278 "data:%s>" % ( 279 self.stream_id, 280 self.flow_controlled_length, 281 _bytes_representation(self.data[:20]), 282 ) 283 ) 284 285 286class WindowUpdated(Event): 287 """ 288 The WindowUpdated event is fired whenever a flow control window changes 289 size. HTTP/2 defines flow control windows for connections and streams: this 290 event fires for both connections and streams. The event carries the ID of 291 the stream to which it applies (set to zero if the window update applies to 292 the connection), and the delta in the window size. 293 """ 294 def __init__(self): 295 #: The Stream ID of the stream whose flow control window was changed. 296 #: May be ``0`` if the connection window was changed. 297 self.stream_id = None 298 299 #: The window delta. 300 self.delta = None 301 302 def __repr__(self): 303 return "<WindowUpdated stream_id:%s, delta:%s>" % ( 304 self.stream_id, self.delta 305 ) 306 307 308class RemoteSettingsChanged(Event): 309 """ 310 The RemoteSettingsChanged event is fired whenever the remote peer changes 311 its settings. It contains a complete inventory of changed settings, 312 including their previous values. 313 314 In HTTP/2, settings changes need to be acknowledged. hyper-h2 automatically 315 acknowledges settings changes for efficiency. However, it is possible that 316 the caller may not be happy with the changed setting. 317 318 When this event is received, the caller should confirm that the new 319 settings are acceptable. If they are not acceptable, the user should close 320 the connection with the error code :data:`PROTOCOL_ERROR 321 <h2.errors.ErrorCodes.PROTOCOL_ERROR>`. 322 323 .. versionchanged:: 2.0.0 324 Prior to this version the user needed to acknowledge settings changes. 325 This is no longer the case: hyper-h2 now automatically acknowledges 326 them. 327 """ 328 def __init__(self): 329 #: A dictionary of setting byte to 330 #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing 331 #: the changed settings. 332 self.changed_settings = {} 333 334 @classmethod 335 def from_settings(cls, old_settings, new_settings): 336 """ 337 Build a RemoteSettingsChanged event from a set of changed settings. 338 339 :param old_settings: A complete collection of old settings, in the form 340 of a dictionary of ``{setting: value}``. 341 :param new_settings: All the changed settings and their new values, in 342 the form of a dictionary of ``{setting: value}``. 343 """ 344 e = cls() 345 for setting, new_value in new_settings.items(): 346 setting = _setting_code_from_int(setting) 347 original_value = old_settings.get(setting) 348 change = ChangedSetting(setting, original_value, new_value) 349 e.changed_settings[setting] = change 350 351 return e 352 353 def __repr__(self): 354 return "<RemoteSettingsChanged changed_settings:{%s}>" % ( 355 ", ".join(repr(cs) for cs in self.changed_settings.values()), 356 ) 357 358 359class PingAcknowledged(Event): 360 """ 361 The PingAcknowledged event is fired whenever a user-emitted PING is 362 acknowledged. This contains the data in the ACK'ed PING, allowing the 363 user to correlate PINGs and calculate RTT. 364 """ 365 def __init__(self): 366 #: The data included on the ping. 367 self.ping_data = None 368 369 def __repr__(self): 370 return "<PingAcknowledged ping_data:%s>" % ( 371 _bytes_representation(self.ping_data), 372 ) 373 374 375class StreamEnded(Event): 376 """ 377 The StreamEnded event is fired whenever a stream is ended by a remote 378 party. The stream may not be fully closed if it has not been closed 379 locally, but no further data or headers should be expected on that stream. 380 """ 381 def __init__(self): 382 #: The Stream ID of the stream that was closed. 383 self.stream_id = None 384 385 def __repr__(self): 386 return "<StreamEnded stream_id:%s>" % self.stream_id 387 388 389class StreamReset(Event): 390 """ 391 The StreamReset event is fired in two situations. The first is when the 392 remote party forcefully resets the stream. The second is when the remote 393 party has made a protocol error which only affects a single stream. In this 394 case, Hyper-h2 will terminate the stream early and return this event. 395 396 .. versionchanged:: 2.0.0 397 This event is now fired when Hyper-h2 automatically resets a stream. 398 """ 399 def __init__(self): 400 #: The Stream ID of the stream that was reset. 401 self.stream_id = None 402 403 #: The error code given. Either one of :class:`ErrorCodes 404 #: <h2.errors.ErrorCodes>` or ``int`` 405 self.error_code = None 406 407 #: Whether the remote peer sent a RST_STREAM or we did. 408 self.remote_reset = True 409 410 def __repr__(self): 411 return "<StreamReset stream_id:%s, error_code:%s, remote_reset:%s>" % ( 412 self.stream_id, self.error_code, self.remote_reset 413 ) 414 415 416class PushedStreamReceived(Event): 417 """ 418 The PushedStreamReceived event is fired whenever a pushed stream has been 419 received from a remote peer. The event carries on it the new stream ID, the 420 ID of the parent stream, and the request headers pushed by the remote peer. 421 """ 422 def __init__(self): 423 #: The Stream ID of the stream created by the push. 424 self.pushed_stream_id = None 425 426 #: The Stream ID of the stream that the push is related to. 427 self.parent_stream_id = None 428 429 #: The request headers, sent by the remote party in the push. 430 self.headers = None 431 432 def __repr__(self): 433 return ( 434 "<PushedStreamReceived pushed_stream_id:%s, parent_stream_id:%s, " 435 "headers:%s>" % ( 436 self.pushed_stream_id, 437 self.parent_stream_id, 438 self.headers, 439 ) 440 ) 441 442 443class SettingsAcknowledged(Event): 444 """ 445 The SettingsAcknowledged event is fired whenever a settings ACK is received 446 from the remote peer. The event carries on it the settings that were 447 acknowedged, in the same format as 448 :class:`h2.events.RemoteSettingsChanged`. 449 """ 450 def __init__(self): 451 #: A dictionary of setting byte to 452 #: :class:`ChangedSetting <h2.settings.ChangedSetting>`, representing 453 #: the changed settings. 454 self.changed_settings = {} 455 456 def __repr__(self): 457 return "<SettingsAcknowledged changed_settings:{%s}>" % ( 458 ", ".join(repr(cs) for cs in self.changed_settings.values()), 459 ) 460 461 462class PriorityUpdated(Event): 463 """ 464 The PriorityUpdated event is fired whenever a stream sends updated priority 465 information. This can occur when the stream is opened, or at any time 466 during the stream lifetime. 467 468 This event is purely advisory, and does not need to be acted on. 469 470 .. versionadded:: 2.0.0 471 """ 472 def __init__(self): 473 #: The ID of the stream whose priority information is being updated. 474 self.stream_id = None 475 476 #: The new stream weight. May be the same as the original stream 477 #: weight. An integer between 1 and 256. 478 self.weight = None 479 480 #: The stream ID this stream now depends on. May be ``0``. 481 self.depends_on = None 482 483 #: Whether the stream *exclusively* depends on the parent stream. If it 484 #: does, this stream should inherit the current children of its new 485 #: parent. 486 self.exclusive = None 487 488 def __repr__(self): 489 return ( 490 "<PriorityUpdated stream_id:%s, weight:%s, depends_on:%s, " 491 "exclusive:%s>" % ( 492 self.stream_id, 493 self.weight, 494 self.depends_on, 495 self.exclusive 496 ) 497 ) 498 499 500class ConnectionTerminated(Event): 501 """ 502 The ConnectionTerminated event is fired when a connection is torn down by 503 the remote peer using a GOAWAY frame. Once received, no further action may 504 be taken on the connection: a new connection must be established. 505 """ 506 def __init__(self): 507 #: The error code cited when tearing down the connection. Should be 508 #: one of :class:`ErrorCodes <h2.errors.ErrorCodes>`, but may not be if 509 #: unknown HTTP/2 extensions are being used. 510 self.error_code = None 511 512 #: The stream ID of the last stream the remote peer saw. This can 513 #: provide an indication of what data, if any, never reached the remote 514 #: peer and so can safely be resent. 515 self.last_stream_id = None 516 517 #: Additional debug data that can be appended to GOAWAY frame. 518 self.additional_data = None 519 520 def __repr__(self): 521 return ( 522 "<ConnectionTerminated error_code:%s, last_stream_id:%s, " 523 "additional_data:%s>" % ( 524 self.error_code, 525 self.last_stream_id, 526 _bytes_representation( 527 self.additional_data[:20] 528 if self.additional_data else None) 529 ) 530 ) 531 532 533class AlternativeServiceAvailable(Event): 534 """ 535 The AlternativeServiceAvailable event is fired when the remote peer 536 advertises an `RFC 7838 <https://tools.ietf.org/html/rfc7838>`_ Alternative 537 Service using an ALTSVC frame. 538 539 This event always carries the origin to which the ALTSVC information 540 applies. That origin is either supplied by the server directly, or inferred 541 by hyper-h2 from the ``:authority`` pseudo-header field that was sent by 542 the user when initiating a given stream. 543 544 This event also carries what RFC 7838 calls the "Alternative Service Field 545 Value", which is formatted like a HTTP header field and contains the 546 relevant alternative service information. Hyper-h2 does not parse or in any 547 way modify that information: the user is required to do that. 548 549 This event can only be fired on the client end of a connection. 550 551 .. versionadded:: 2.3.0 552 """ 553 def __init__(self): 554 #: The origin to which the alternative service field value applies. 555 #: This field is either supplied by the server directly, or inferred by 556 #: hyper-h2 from the ``:authority`` pseudo-header field that was sent 557 #: by the user when initiating the stream on which the frame was 558 #: received. 559 self.origin = None 560 561 #: The ALTSVC field value. This contains information about the HTTP 562 #: alternative service being advertised by the server. Hyper-h2 does 563 #: not parse this field: it is left exactly as sent by the server. The 564 #: structure of the data in this field is given by `RFC 7838 Section 3 565 #: <https://tools.ietf.org/html/rfc7838#section-3>`_. 566 self.field_value = None 567 568 def __repr__(self): 569 return ( 570 "<AlternativeServiceAvailable origin:%s, field_value:%s>" % ( 571 self.origin.decode('utf-8', 'ignore'), 572 self.field_value.decode('utf-8', 'ignore'), 573 ) 574 ) 575 576 577def _bytes_representation(data): 578 """ 579 Converts a bytestring into something that is safe to print on all Python 580 platforms. 581 582 This function is relatively expensive, so it should not be called on the 583 mainline of the code. It's safe to use in things like object repr methods 584 though. 585 """ 586 if data is None: 587 return None 588 589 hex = binascii.hexlify(data) 590 591 # This is moderately clever: on all Python versions hexlify returns a byte 592 # string. On Python 3 we want an actual string, so we just check whether 593 # that's what we have. 594 if not isinstance(hex, str): # pragma: no cover 595 hex = hex.decode('ascii') 596 597 return hex 598