Download docker/deezer-spleeter-env/lib/python3.10/site-packages/h2/stream.py from kopyl/norae: direct link, hf CLI and curl.
- Browser
- Download file 54.5 kB
-
https://huggingface.co/kopyl/norae/resolve/main/docker/deezer-spleeter-env/lib/python3.10/site-packages/h2/stream.py
- Command line
-
hf download hf://kopyl/norae/docker/deezer-spleeter-env/lib/python3.10/site-packages/h2/stream.py
-
curl -L -o stream.py https://huggingface.co/kopyl/norae/resolve/main/docker/deezer-spleeter-env/lib/python3.10/site-packages/h2/stream.py
54.5 kB
| # -*- coding: utf-8 -*- | |
| """ | |
| h2/stream | |
| ~~~~~~~~~ | |
| An implementation of a HTTP/2 stream. | |
| """ | |
| from enum import Enum, IntEnum | |
| from hpack import HeaderTuple | |
| from hyperframe.frame import ( | |
| HeadersFrame, ContinuationFrame, DataFrame, WindowUpdateFrame, | |
| RstStreamFrame, PushPromiseFrame, AltSvcFrame | |
| ) | |
| from .errors import ErrorCodes, _error_code_from_int | |
| from .events import ( | |
| RequestReceived, ResponseReceived, DataReceived, WindowUpdated, | |
| StreamEnded, PushedStreamReceived, StreamReset, TrailersReceived, | |
| InformationalResponseReceived, AlternativeServiceAvailable, | |
| _ResponseSent, _RequestSent, _TrailersSent, _PushedRequestSent | |
| ) | |
| from .exceptions import ( | |
| ProtocolError, StreamClosedError, InvalidBodyLengthError, FlowControlError | |
| ) | |
| from .utilities import ( | |
| guard_increment_window, is_informational_response, authority_from_headers, | |
| validate_headers, validate_outbound_headers, normalize_outbound_headers, | |
| HeaderValidationFlags, extract_method_header, normalize_inbound_headers | |
| ) | |
| from .windows import WindowManager | |
| class StreamState(IntEnum): | |
| IDLE = 0 | |
| RESERVED_REMOTE = 1 | |
| RESERVED_LOCAL = 2 | |
| OPEN = 3 | |
| HALF_CLOSED_REMOTE = 4 | |
| HALF_CLOSED_LOCAL = 5 | |
| CLOSED = 6 | |
| class StreamInputs(Enum): | |
| SEND_HEADERS = 0 | |
| SEND_PUSH_PROMISE = 1 | |
| SEND_RST_STREAM = 2 | |
| SEND_DATA = 3 | |
| SEND_WINDOW_UPDATE = 4 | |
| SEND_END_STREAM = 5 | |
| RECV_HEADERS = 6 | |
| RECV_PUSH_PROMISE = 7 | |
| RECV_RST_STREAM = 8 | |
| RECV_DATA = 9 | |
| RECV_WINDOW_UPDATE = 10 | |
| RECV_END_STREAM = 11 | |
| RECV_CONTINUATION = 12 # Added in 2.0.0 | |
| SEND_INFORMATIONAL_HEADERS = 13 # Added in 2.2.0 | |
| RECV_INFORMATIONAL_HEADERS = 14 # Added in 2.2.0 | |
| SEND_ALTERNATIVE_SERVICE = 15 # Added in 2.3.0 | |
| RECV_ALTERNATIVE_SERVICE = 16 # Added in 2.3.0 | |
| UPGRADE_CLIENT = 17 # Added 2.3.0 | |
| UPGRADE_SERVER = 18 # Added 2.3.0 | |
| class StreamClosedBy(Enum): | |
| SEND_END_STREAM = 0 | |
| RECV_END_STREAM = 1 | |
| SEND_RST_STREAM = 2 | |
| RECV_RST_STREAM = 3 | |
| # This array is initialized once, and is indexed by the stream states above. | |
| # It indicates whether a stream in the given state is open. The reason we do | |
| # this is that we potentially check whether a stream in a given state is open | |
| # quite frequently: given that we check so often, we should do so in the | |
| # fastest and most performant way possible. | |
| STREAM_OPEN = [False for _ in range(0, len(StreamState))] | |
| STREAM_OPEN[StreamState.OPEN] = True | |
| STREAM_OPEN[StreamState.HALF_CLOSED_LOCAL] = True | |
| STREAM_OPEN[StreamState.HALF_CLOSED_REMOTE] = True | |
| class H2StreamStateMachine: | |
| """ | |
| A single HTTP/2 stream state machine. | |
| This stream object implements basically the state machine described in | |
| RFC 7540 section 5.1. | |
| :param stream_id: The stream ID of this stream. This is stored primarily | |
| for logging purposes. | |
| """ | |
| def __init__(self, stream_id): | |
| self.state = StreamState.IDLE | |
| self.stream_id = stream_id | |
| #: Whether this peer is the client side of this stream. | |
| self.client = None | |
| # Whether trailers have been sent/received on this stream or not. | |
| self.headers_sent = None | |
| self.trailers_sent = None | |
| self.headers_received = None | |
| self.trailers_received = None | |
| # How the stream was closed. One of StreamClosedBy. | |
| self.stream_closed_by = None | |
| def process_input(self, input_): | |
| """ | |
| Process a specific input in the state machine. | |
| """ | |
| if not isinstance(input_, StreamInputs): | |
| raise ValueError("Input must be an instance of StreamInputs") | |
| try: | |
| func, target_state = _transitions[(self.state, input_)] | |
| except KeyError: | |
| old_state = self.state | |
| self.state = StreamState.CLOSED | |
| raise ProtocolError( | |
| "Invalid input %s in state %s" % (input_, old_state) | |
| ) | |
| else: | |
| previous_state = self.state | |
| self.state = target_state | |
| if func is not None: | |
| try: | |
| return func(self, previous_state) | |
| except ProtocolError: | |
| self.state = StreamState.CLOSED | |
| raise | |
| except AssertionError as e: # pragma: no cover | |
| self.state = StreamState.CLOSED | |
| raise ProtocolError(e) | |
| return [] | |
| def request_sent(self, previous_state): | |
| """ | |
| Fires when a request is sent. | |
| """ | |
| self.client = True | |
| self.headers_sent = True | |
| event = _RequestSent() | |
| return [event] | |
| def response_sent(self, previous_state): | |
| """ | |
| Fires when something that should be a response is sent. This 'response' | |
| may actually be trailers. | |
| """ | |
| if not self.headers_sent: | |
| if self.client is True or self.client is None: | |
| raise ProtocolError("Client cannot send responses.") | |
| self.headers_sent = True | |
| event = _ResponseSent() | |
| else: | |
| assert not self.trailers_sent | |
| self.trailers_sent = True | |
| event = _TrailersSent() | |
| return [event] | |
| def request_received(self, previous_state): | |
| """ | |
| Fires when a request is received. | |
| """ | |
| assert not self.headers_received | |
| assert not self.trailers_received | |
| self.client = False | |
| self.headers_received = True | |
| event = RequestReceived() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def response_received(self, previous_state): | |
| """ | |
| Fires when a response is received. Also disambiguates between responses | |
| and trailers. | |
| """ | |
| if not self.headers_received: | |
| assert self.client is True | |
| self.headers_received = True | |
| event = ResponseReceived() | |
| else: | |
| assert not self.trailers_received | |
| self.trailers_received = True | |
| event = TrailersReceived() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def data_received(self, previous_state): | |
| """ | |
| Fires when data is received. | |
| """ | |
| if not self.headers_received: | |
| raise ProtocolError("cannot receive data before headers") | |
| event = DataReceived() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def window_updated(self, previous_state): | |
| """ | |
| Fires when a window update frame is received. | |
| """ | |
| event = WindowUpdated() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def stream_half_closed(self, previous_state): | |
| """ | |
| Fires when an END_STREAM flag is received in the OPEN state, | |
| transitioning this stream to a HALF_CLOSED_REMOTE state. | |
| """ | |
| event = StreamEnded() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def stream_ended(self, previous_state): | |
| """ | |
| Fires when a stream is cleanly ended. | |
| """ | |
| self.stream_closed_by = StreamClosedBy.RECV_END_STREAM | |
| event = StreamEnded() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def stream_reset(self, previous_state): | |
| """ | |
| Fired when a stream is forcefully reset. | |
| """ | |
| self.stream_closed_by = StreamClosedBy.RECV_RST_STREAM | |
| event = StreamReset() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def send_new_pushed_stream(self, previous_state): | |
| """ | |
| Fires on the newly pushed stream, when pushed by the local peer. | |
| No event here, but definitionally this peer must be a server. | |
| """ | |
| assert self.client is None | |
| self.client = False | |
| self.headers_received = True | |
| return [] | |
| def recv_new_pushed_stream(self, previous_state): | |
| """ | |
| Fires on the newly pushed stream, when pushed by the remote peer. | |
| No event here, but definitionally this peer must be a client. | |
| """ | |
| assert self.client is None | |
| self.client = True | |
| self.headers_sent = True | |
| return [] | |
| def send_push_promise(self, previous_state): | |
| """ | |
| Fires on the already-existing stream when a PUSH_PROMISE frame is sent. | |
| We may only send PUSH_PROMISE frames if we're a server. | |
| """ | |
| if self.client is True: | |
| raise ProtocolError("Cannot push streams from client peers.") | |
| event = _PushedRequestSent() | |
| return [event] | |
| def recv_push_promise(self, previous_state): | |
| """ | |
| Fires on the already-existing stream when a PUSH_PROMISE frame is | |
| received. We may only receive PUSH_PROMISE frames if we're a client. | |
| Fires a PushedStreamReceived event. | |
| """ | |
| if not self.client: | |
| if self.client is None: # pragma: no cover | |
| msg = "Idle streams cannot receive pushes" | |
| else: # pragma: no cover | |
| msg = "Cannot receive pushed streams as a server" | |
| raise ProtocolError(msg) | |
| event = PushedStreamReceived() | |
| event.parent_stream_id = self.stream_id | |
| return [event] | |
| def send_end_stream(self, previous_state): | |
| """ | |
| Called when an attempt is made to send END_STREAM in the | |
| HALF_CLOSED_REMOTE state. | |
| """ | |
| self.stream_closed_by = StreamClosedBy.SEND_END_STREAM | |
| def send_reset_stream(self, previous_state): | |
| """ | |
| Called when an attempt is made to send RST_STREAM in a non-closed | |
| stream state. | |
| """ | |
| self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM | |
| def reset_stream_on_error(self, previous_state): | |
| """ | |
| Called when we need to forcefully emit another RST_STREAM frame on | |
| behalf of the state machine. | |
| If this is the first time we've done this, we should also hang an event | |
| off the StreamClosedError so that the user can be informed. We know | |
| it's the first time we've done this if the stream is currently in a | |
| state other than CLOSED. | |
| """ | |
| self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM | |
| error = StreamClosedError(self.stream_id) | |
| event = StreamReset() | |
| event.stream_id = self.stream_id | |
| event.error_code = ErrorCodes.STREAM_CLOSED | |
| event.remote_reset = False | |
| error._events = [event] | |
| raise error | |
| def recv_on_closed_stream(self, previous_state): | |
| """ | |
| Called when an unexpected frame is received on an already-closed | |
| stream. | |
| An endpoint that receives an unexpected frame should treat it as | |
| a stream error or connection error with type STREAM_CLOSED, depending | |
| on the specific frame. The error handling is done at a higher level: | |
| this just raises the appropriate error. | |
| """ | |
| raise StreamClosedError(self.stream_id) | |
| def send_on_closed_stream(self, previous_state): | |
| """ | |
| Called when an attempt is made to send data on an already-closed | |
| stream. | |
| This essentially overrides the standard logic by throwing a | |
| more-specific error: StreamClosedError. This is a ProtocolError, so it | |
| matches the standard API of the state machine, but provides more detail | |
| to the user. | |
| """ | |
| raise StreamClosedError(self.stream_id) | |
| def recv_push_on_closed_stream(self, previous_state): | |
| """ | |
| Called when a PUSH_PROMISE frame is received on a full stop | |
| stream. | |
| If the stream was closed by us sending a RST_STREAM frame, then we | |
| presume that the PUSH_PROMISE was in flight when we reset the parent | |
| stream. Rathen than accept the new stream, we just reset it. | |
| Otherwise, we should call this a PROTOCOL_ERROR: pushing a stream on a | |
| naturally closed stream is a real problem because it creates a brand | |
| new stream that the remote peer now believes exists. | |
| """ | |
| assert self.stream_closed_by is not None | |
| if self.stream_closed_by == StreamClosedBy.SEND_RST_STREAM: | |
| raise StreamClosedError(self.stream_id) | |
| else: | |
| raise ProtocolError("Attempted to push on closed stream.") | |
| def send_push_on_closed_stream(self, previous_state): | |
| """ | |
| Called when an attempt is made to push on an already-closed stream. | |
| This essentially overrides the standard logic by providing a more | |
| useful error message. It's necessary because simply indicating that the | |
| stream is closed is not enough: there is now a new stream that is not | |
| allowed to be there. The only recourse is to tear the whole connection | |
| down. | |
| """ | |
| raise ProtocolError("Attempted to push on closed stream.") | |
| def send_informational_response(self, previous_state): | |
| """ | |
| Called when an informational header block is sent (that is, a block | |
| where the :status header has a 1XX value). | |
| Only enforces that these are sent *before* final headers are sent. | |
| """ | |
| if self.headers_sent: | |
| raise ProtocolError("Information response after final response") | |
| event = _ResponseSent() | |
| return [event] | |
| def recv_informational_response(self, previous_state): | |
| """ | |
| Called when an informational header block is received (that is, a block | |
| where the :status header has a 1XX value). | |
| """ | |
| if self.headers_received: | |
| raise ProtocolError("Informational response after final response") | |
| event = InformationalResponseReceived() | |
| event.stream_id = self.stream_id | |
| return [event] | |
| def recv_alt_svc(self, previous_state): | |
| """ | |
| Called when receiving an ALTSVC frame. | |
| RFC 7838 allows us to receive ALTSVC frames at any stream state, which | |
| is really absurdly overzealous. For that reason, we want to limit the | |
| states in which we can actually receive it. It's really only sensible | |
| to receive it after we've sent our own headers and before the server | |
| has sent its header block: the server can't guarantee that we have any | |
| state around after it completes its header block, and the server | |
| doesn't know what origin we're talking about before we've sent ours. | |
| For that reason, this function applies a few extra checks on both state | |
| and some of the little state variables we keep around. If those suggest | |
| an unreasonable situation for the ALTSVC frame to have been sent in, | |
| we quietly ignore it (as RFC 7838 suggests). | |
| This function is also *not* always called by the state machine. In some | |
| states (IDLE, RESERVED_LOCAL, CLOSED) we don't bother to call it, | |
| because we know the frame cannot be valid in that state (IDLE because | |
| the server cannot know what origin the stream applies to, CLOSED | |
| because the server cannot assume we still have state around, | |
| RESERVED_LOCAL because by definition if we're in the RESERVED_LOCAL | |
| state then *we* are the server). | |
| """ | |
| # Servers can't receive ALTSVC frames, but RFC 7838 tells us to ignore | |
| # them. | |
| if self.client is False: | |
| return [] | |
| # If we've received the response headers from the server they can't | |
| # guarantee we still have any state around. Other implementations | |
| # (like nghttp2) ignore ALTSVC in this state, so we will too. | |
| if self.headers_received: | |
| return [] | |
| # Otherwise, this is a sensible enough frame to have received. Return | |
| # the event and let it get populated. | |
| return [AlternativeServiceAvailable()] | |
| def send_alt_svc(self, previous_state): | |
| """ | |
| Called when sending an ALTSVC frame on this stream. | |
| For consistency with the restrictions we apply on receiving ALTSVC | |
| frames in ``recv_alt_svc``, we want to restrict when users can send | |
| ALTSVC frames to the situations when we ourselves would accept them. | |
| That means: when we are a server, when we have received the request | |
| headers, and when we have not yet sent our own response headers. | |
| """ | |
| # We should not send ALTSVC after we've sent response headers, as the | |
| # client may have disposed of its state. | |
| if self.headers_sent: | |
| raise ProtocolError( | |
| "Cannot send ALTSVC after sending response headers." | |
| ) | |
| return | |
| # STATE MACHINE | |
| # | |
| # The stream state machine is defined here to avoid the need to allocate it | |
| # repeatedly for each stream. It cannot be defined in the stream class because | |
| # it needs to be able to reference the callbacks defined on the class, but | |
| # because Python's scoping rules are weird the class object is not actually in | |
| # scope during the body of the class object. | |
| # | |
| # For the sake of clarity, we reproduce the RFC 7540 state machine here: | |
| # | |
| # +--------+ | |
| # send PP | | recv PP | |
| # ,--------| idle |--------. | |
| # / | | \ | |
| # v +--------+ v | |
| # +----------+ | +----------+ | |
| # | | | send H / | | | |
| # ,------| reserved | | recv H | reserved |------. | |
| # | | (local) | | | (remote) | | | |
| # | +----------+ v +----------+ | | |
| # | | +--------+ | | | |
| # | | recv ES | | send ES | | | |
| # | send H | ,-------| open |-------. | recv H | | |
| # | | / | | \ | | | |
| # | v v +--------+ v v | | |
| # | +----------+ | +----------+ | | |
| # | | half | | | half | | | |
| # | | closed | | send R / | closed | | | |
| # | | (remote) | | recv R | (local) | | | |
| # | +----------+ | +----------+ | | |
| # | | | | | | |
| # | | send ES / | recv ES / | | | |
| # | | send R / v send R / | | | |
| # | | recv R +--------+ recv R | | | |
| # | send R / `----------->| |<-----------' send R / | | |
| # | recv R | closed | recv R | | |
| # `----------------------->| |<----------------------' | |
| # +--------+ | |
| # | |
| # send: endpoint sends this frame | |
| # recv: endpoint receives this frame | |
| # | |
| # H: HEADERS frame (with implied CONTINUATIONs) | |
| # PP: PUSH_PROMISE frame (with implied CONTINUATIONs) | |
| # ES: END_STREAM flag | |
| # R: RST_STREAM frame | |
| # | |
| # For the purposes of this state machine we treat HEADERS and their | |
| # associated CONTINUATION frames as a single jumbo frame. The protocol | |
| # allows/requires this by preventing other frames from being interleved in | |
| # between HEADERS/CONTINUATION frames. However, if a CONTINUATION frame is | |
| # received without a prior HEADERS frame, it *will* be passed to this state | |
| # machine. The state machine should always reject that frame, either as an | |
| # invalid transition or because the stream is closed. | |
| # | |
| # There is a confusing relationship around PUSH_PROMISE frames. The state | |
| # machine above considers them to be frames belonging to the new stream, | |
| # which is *somewhat* true. However, they are sent with the stream ID of | |
| # their related stream, and are only sendable in some cases. | |
| # For this reason, our state machine implementation below allows for | |
| # PUSH_PROMISE frames both in the IDLE state (as in the diagram), but also | |
| # in the OPEN, HALF_CLOSED_LOCAL, and HALF_CLOSED_REMOTE states. | |
| # Essentially, for h2, PUSH_PROMISE frames are effectively sent on | |
| # two streams. | |
| # | |
| # The _transitions dictionary contains a mapping of tuples of | |
| # (state, input) to tuples of (side_effect_function, end_state). This | |
| # map contains all allowed transitions: anything not in this map is | |
| # invalid and immediately causes a transition to ``closed``. | |
| _transitions = { | |
| # State: idle | |
| (StreamState.IDLE, StreamInputs.SEND_HEADERS): | |
| (H2StreamStateMachine.request_sent, StreamState.OPEN), | |
| (StreamState.IDLE, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.request_received, StreamState.OPEN), | |
| (StreamState.IDLE, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.IDLE, StreamInputs.SEND_PUSH_PROMISE): | |
| (H2StreamStateMachine.send_new_pushed_stream, | |
| StreamState.RESERVED_LOCAL), | |
| (StreamState.IDLE, StreamInputs.RECV_PUSH_PROMISE): | |
| (H2StreamStateMachine.recv_new_pushed_stream, | |
| StreamState.RESERVED_REMOTE), | |
| (StreamState.IDLE, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (None, StreamState.IDLE), | |
| (StreamState.IDLE, StreamInputs.UPGRADE_CLIENT): | |
| (H2StreamStateMachine.request_sent, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.IDLE, StreamInputs.UPGRADE_SERVER): | |
| (H2StreamStateMachine.request_received, | |
| StreamState.HALF_CLOSED_REMOTE), | |
| # State: reserved local | |
| (StreamState.RESERVED_LOCAL, StreamInputs.SEND_HEADERS): | |
| (H2StreamStateMachine.response_sent, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.SEND_WINDOW_UPDATE): | |
| (None, StreamState.RESERVED_LOCAL), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.RECV_WINDOW_UPDATE): | |
| (H2StreamStateMachine.window_updated, StreamState.RESERVED_LOCAL), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.RECV_RST_STREAM): | |
| (H2StreamStateMachine.stream_reset, StreamState.CLOSED), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.SEND_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.send_alt_svc, StreamState.RESERVED_LOCAL), | |
| (StreamState.RESERVED_LOCAL, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (None, StreamState.RESERVED_LOCAL), | |
| # State: reserved remote | |
| (StreamState.RESERVED_REMOTE, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.response_received, | |
| StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.SEND_WINDOW_UPDATE): | |
| (None, StreamState.RESERVED_REMOTE), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.RECV_WINDOW_UPDATE): | |
| (H2StreamStateMachine.window_updated, StreamState.RESERVED_REMOTE), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.RECV_RST_STREAM): | |
| (H2StreamStateMachine.stream_reset, StreamState.CLOSED), | |
| (StreamState.RESERVED_REMOTE, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.recv_alt_svc, StreamState.RESERVED_REMOTE), | |
| # State: open | |
| (StreamState.OPEN, StreamInputs.SEND_HEADERS): | |
| (H2StreamStateMachine.response_sent, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.response_received, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.SEND_DATA): | |
| (None, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.data_received, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.SEND_END_STREAM): | |
| (None, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.OPEN, StreamInputs.RECV_END_STREAM): | |
| (H2StreamStateMachine.stream_half_closed, | |
| StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.OPEN, StreamInputs.SEND_WINDOW_UPDATE): | |
| (None, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_WINDOW_UPDATE): | |
| (H2StreamStateMachine.window_updated, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), | |
| (StreamState.OPEN, StreamInputs.RECV_RST_STREAM): | |
| (H2StreamStateMachine.stream_reset, StreamState.CLOSED), | |
| (StreamState.OPEN, StreamInputs.SEND_PUSH_PROMISE): | |
| (H2StreamStateMachine.send_push_promise, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_PUSH_PROMISE): | |
| (H2StreamStateMachine.recv_push_promise, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.SEND_INFORMATIONAL_HEADERS): | |
| (H2StreamStateMachine.send_informational_response, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_INFORMATIONAL_HEADERS): | |
| (H2StreamStateMachine.recv_informational_response, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.SEND_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.send_alt_svc, StreamState.OPEN), | |
| (StreamState.OPEN, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.recv_alt_svc, StreamState.OPEN), | |
| # State: half-closed remote | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_HEADERS): | |
| (H2StreamStateMachine.response_sent, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_DATA): | |
| (None, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_END_STREAM): | |
| (H2StreamStateMachine.send_end_stream, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_WINDOW_UPDATE): | |
| (None, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_WINDOW_UPDATE): | |
| (H2StreamStateMachine.window_updated, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_RST_STREAM): | |
| (H2StreamStateMachine.stream_reset, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_PUSH_PROMISE): | |
| (H2StreamStateMachine.send_push_promise, | |
| StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_PUSH_PROMISE): | |
| (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_INFORMATIONAL_HEADERS): | |
| (H2StreamStateMachine.send_informational_response, | |
| StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.send_alt_svc, StreamState.HALF_CLOSED_REMOTE), | |
| (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.recv_alt_svc, StreamState.HALF_CLOSED_REMOTE), | |
| # State: half-closed local | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.response_received, | |
| StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.data_received, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_END_STREAM): | |
| (H2StreamStateMachine.stream_ended, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_WINDOW_UPDATE): | |
| (None, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_WINDOW_UPDATE): | |
| (H2StreamStateMachine.window_updated, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_RST_STREAM): | |
| (H2StreamStateMachine.stream_reset, StreamState.CLOSED), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_PUSH_PROMISE): | |
| (H2StreamStateMachine.recv_push_promise, | |
| StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_INFORMATIONAL_HEADERS): | |
| (H2StreamStateMachine.recv_informational_response, | |
| StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.send_alt_svc, StreamState.HALF_CLOSED_LOCAL), | |
| (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (H2StreamStateMachine.recv_alt_svc, StreamState.HALF_CLOSED_LOCAL), | |
| # State: closed | |
| (StreamState.CLOSED, StreamInputs.RECV_END_STREAM): | |
| (None, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.RECV_ALTERNATIVE_SERVICE): | |
| (None, StreamState.CLOSED), | |
| # RFC 7540 Section 5.1 defines how the end point should react when | |
| # receiving a frame on a closed stream with the following statements: | |
| # | |
| # > An endpoint that receives any frame other than PRIORITY after receiving | |
| # > a RST_STREAM MUST treat that as a stream error of type STREAM_CLOSED. | |
| # > An endpoint that receives any frames after receiving a frame with the | |
| # > END_STREAM flag set MUST treat that as a connection error of type | |
| # > STREAM_CLOSED. | |
| (StreamState.CLOSED, StreamInputs.RECV_HEADERS): | |
| (H2StreamStateMachine.recv_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.RECV_DATA): | |
| (H2StreamStateMachine.recv_on_closed_stream, StreamState.CLOSED), | |
| # > WINDOW_UPDATE or RST_STREAM frames can be received in this state | |
| # > for a short period after a DATA or HEADERS frame containing a | |
| # > END_STREAM flag is sent, as instructed in RFC 7540 Section 5.1. But we | |
| # > don't have access to a clock so we just always allow it. | |
| (StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE): | |
| (None, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.RECV_RST_STREAM): | |
| (None, StreamState.CLOSED), | |
| # > A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is | |
| # > neither "open" nor "half-closed (local)" as a connection error of type | |
| # > PROTOCOL_ERROR. | |
| (StreamState.CLOSED, StreamInputs.RECV_PUSH_PROMISE): | |
| (H2StreamStateMachine.recv_push_on_closed_stream, StreamState.CLOSED), | |
| # Also, users should be forbidden from sending on closed streams. | |
| (StreamState.CLOSED, StreamInputs.SEND_HEADERS): | |
| (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.SEND_PUSH_PROMISE): | |
| (H2StreamStateMachine.send_push_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.SEND_RST_STREAM): | |
| (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.SEND_DATA): | |
| (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.SEND_WINDOW_UPDATE): | |
| (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), | |
| (StreamState.CLOSED, StreamInputs.SEND_END_STREAM): | |
| (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), | |
| } | |
| class H2Stream: | |
| """ | |
| A low-level HTTP/2 stream object. This handles building and receiving | |
| frames and maintains per-stream state. | |
| This wraps a HTTP/2 Stream state machine implementation, ensuring that | |
| frames can only be sent/received when the stream is in a valid state. | |
| Attempts to create frames that cannot be sent will raise a | |
| ``ProtocolError``. | |
| """ | |
| def __init__(self, | |
| stream_id, | |
| config, | |
| inbound_window_size, | |
| outbound_window_size): | |
| self.state_machine = H2StreamStateMachine(stream_id) | |
| self.stream_id = stream_id | |
| self.max_outbound_frame_size = None | |
| self.request_method = None | |
| # The current value of the outbound stream flow control window | |
| self.outbound_flow_control_window = outbound_window_size | |
| # The flow control manager. | |
| self._inbound_window_manager = WindowManager(inbound_window_size) | |
| # The expected content length, if any. | |
| self._expected_content_length = None | |
| # The actual received content length. Always tracked. | |
| self._actual_content_length = 0 | |
| # The authority we believe this stream belongs to. | |
| self._authority = None | |
| # The configuration for this stream. | |
| self.config = config | |
| def __repr__(self): | |
| return "<%s id:%d state:%r>" % ( | |
| type(self).__name__, | |
| self.stream_id, | |
| self.state_machine.state | |
| ) | |
| def inbound_flow_control_window(self): | |
| """ | |
| The size of the inbound flow control window for the stream. This is | |
| rarely publicly useful: instead, use :meth:`remote_flow_control_window | |
| <h2.stream.H2Stream.remote_flow_control_window>`. This shortcut is | |
| largely present to provide a shortcut to this data. | |
| """ | |
| return self._inbound_window_manager.current_window_size | |
| def open(self): | |
| """ | |
| Whether the stream is 'open' in any sense: that is, whether it counts | |
| against the number of concurrent streams. | |
| """ | |
| # RFC 7540 Section 5.1.2 defines 'open' for this purpose to mean either | |
| # the OPEN state or either of the HALF_CLOSED states. Perplexingly, | |
| # this excludes the reserved states. | |
| # For more detail on why we're doing this in this slightly weird way, | |
| # see the comment on ``STREAM_OPEN`` at the top of the file. | |
| return STREAM_OPEN[self.state_machine.state] | |
| def closed(self): | |
| """ | |
| Whether the stream is closed. | |
| """ | |
| return self.state_machine.state == StreamState.CLOSED | |
| def closed_by(self): | |
| """ | |
| Returns how the stream was closed, as one of StreamClosedBy. | |
| """ | |
| return self.state_machine.stream_closed_by | |
| def upgrade(self, client_side): | |
| """ | |
| Called by the connection to indicate that this stream is the initial | |
| request/response of an upgraded connection. Places the stream into an | |
| appropriate state. | |
| """ | |
| self.config.logger.debug("Upgrading %r", self) | |
| assert self.stream_id == 1 | |
| input_ = ( | |
| StreamInputs.UPGRADE_CLIENT if client_side | |
| else StreamInputs.UPGRADE_SERVER | |
| ) | |
| # This may return events, we deliberately don't want them. | |
| self.state_machine.process_input(input_) | |
| return | |
| def send_headers(self, headers, encoder, end_stream=False): | |
| """ | |
| Returns a list of HEADERS/CONTINUATION frames to emit as either headers | |
| or trailers. | |
| """ | |
| self.config.logger.debug("Send headers %s on %r", headers, self) | |
| # Because encoding headers makes an irreversible change to the header | |
| # compression context, we make the state transition before we encode | |
| # them. | |
| # First, check if we're a client. If we are, no problem: if we aren't, | |
| # we need to scan the header block to see if this is an informational | |
| # response. | |
| input_ = StreamInputs.SEND_HEADERS | |
| if ((not self.state_machine.client) and | |
| is_informational_response(headers)): | |
| if end_stream: | |
| raise ProtocolError( | |
| "Cannot set END_STREAM on informational responses." | |
| ) | |
| input_ = StreamInputs.SEND_INFORMATIONAL_HEADERS | |
| events = self.state_machine.process_input(input_) | |
| hf = HeadersFrame(self.stream_id) | |
| hdr_validation_flags = self._build_hdr_validation_flags(events) | |
| frames = self._build_headers_frames( | |
| headers, encoder, hf, hdr_validation_flags | |
| ) | |
| if end_stream: | |
| # Not a bug: the END_STREAM flag is valid on the initial HEADERS | |
| # frame, not the CONTINUATION frames that follow. | |
| self.state_machine.process_input(StreamInputs.SEND_END_STREAM) | |
| frames[0].flags.add('END_STREAM') | |
| if self.state_machine.trailers_sent and not end_stream: | |
| raise ProtocolError("Trailers must have END_STREAM set.") | |
| if self.state_machine.client and self._authority is None: | |
| self._authority = authority_from_headers(headers) | |
| # store request method for _initialize_content_length | |
| self.request_method = extract_method_header(headers) | |
| return frames | |
| def push_stream_in_band(self, related_stream_id, headers, encoder): | |
| """ | |
| Returns a list of PUSH_PROMISE/CONTINUATION frames to emit as a pushed | |
| stream header. Called on the stream that has the PUSH_PROMISE frame | |
| sent on it. | |
| """ | |
| self.config.logger.debug("Push stream %r", self) | |
| # Because encoding headers makes an irreversible change to the header | |
| # compression context, we make the state transition *first*. | |
| events = self.state_machine.process_input( | |
| StreamInputs.SEND_PUSH_PROMISE | |
| ) | |
| ppf = PushPromiseFrame(self.stream_id) | |
| ppf.promised_stream_id = related_stream_id | |
| hdr_validation_flags = self._build_hdr_validation_flags(events) | |
| frames = self._build_headers_frames( | |
| headers, encoder, ppf, hdr_validation_flags | |
| ) | |
| return frames | |
| def locally_pushed(self): | |
| """ | |
| Mark this stream as one that was pushed by this peer. Must be called | |
| immediately after initialization. Sends no frames, simply updates the | |
| state machine. | |
| """ | |
| # This does not trigger any events. | |
| events = self.state_machine.process_input( | |
| StreamInputs.SEND_PUSH_PROMISE | |
| ) | |
| assert not events | |
| return [] | |
| def send_data(self, data, end_stream=False, pad_length=None): | |
| """ | |
| Prepare some data frames. Optionally end the stream. | |
| .. warning:: Does not perform flow control checks. | |
| """ | |
| self.config.logger.debug( | |
| "Send data on %r with end stream set to %s", self, end_stream | |
| ) | |
| self.state_machine.process_input(StreamInputs.SEND_DATA) | |
| df = DataFrame(self.stream_id) | |
| df.data = data | |
| if end_stream: | |
| self.state_machine.process_input(StreamInputs.SEND_END_STREAM) | |
| df.flags.add('END_STREAM') | |
| if pad_length is not None: | |
| df.flags.add('PADDED') | |
| df.pad_length = pad_length | |
| # Subtract flow_controlled_length to account for possible padding | |
| self.outbound_flow_control_window -= df.flow_controlled_length | |
| assert self.outbound_flow_control_window >= 0 | |
| return [df] | |
| def end_stream(self): | |
| """ | |
| End a stream without sending data. | |
| """ | |
| self.config.logger.debug("End stream %r", self) | |
| self.state_machine.process_input(StreamInputs.SEND_END_STREAM) | |
| df = DataFrame(self.stream_id) | |
| df.flags.add('END_STREAM') | |
| return [df] | |
| def advertise_alternative_service(self, field_value): | |
| """ | |
| Advertise an RFC 7838 alternative service. The semantics of this are | |
| better documented in the ``H2Connection`` class. | |
| """ | |
| self.config.logger.debug( | |
| "Advertise alternative service of %r for %r", field_value, self | |
| ) | |
| self.state_machine.process_input(StreamInputs.SEND_ALTERNATIVE_SERVICE) | |
| asf = AltSvcFrame(self.stream_id) | |
| asf.field = field_value | |
| return [asf] | |
| def increase_flow_control_window(self, increment): | |
| """ | |
| Increase the size of the flow control window for the remote side. | |
| """ | |
| self.config.logger.debug( | |
| "Increase flow control window for %r by %d", | |
| self, increment | |
| ) | |
| self.state_machine.process_input(StreamInputs.SEND_WINDOW_UPDATE) | |
| self._inbound_window_manager.window_opened(increment) | |
| wuf = WindowUpdateFrame(self.stream_id) | |
| wuf.window_increment = increment | |
| return [wuf] | |
| def receive_push_promise_in_band(self, | |
| promised_stream_id, | |
| headers, | |
| header_encoding): | |
| """ | |
| Receives a push promise frame sent on this stream, pushing a remote | |
| stream. This is called on the stream that has the PUSH_PROMISE sent | |
| on it. | |
| """ | |
| self.config.logger.debug( | |
| "Receive Push Promise on %r for remote stream %d", | |
| self, promised_stream_id | |
| ) | |
| events = self.state_machine.process_input( | |
| StreamInputs.RECV_PUSH_PROMISE | |
| ) | |
| events[0].pushed_stream_id = promised_stream_id | |
| hdr_validation_flags = self._build_hdr_validation_flags(events) | |
| events[0].headers = self._process_received_headers( | |
| headers, hdr_validation_flags, header_encoding | |
| ) | |
| return [], events | |
| def remotely_pushed(self, pushed_headers): | |
| """ | |
| Mark this stream as one that was pushed by the remote peer. Must be | |
| called immediately after initialization. Sends no frames, simply | |
| updates the state machine. | |
| """ | |
| self.config.logger.debug("%r pushed by remote peer", self) | |
| events = self.state_machine.process_input( | |
| StreamInputs.RECV_PUSH_PROMISE | |
| ) | |
| self._authority = authority_from_headers(pushed_headers) | |
| return [], events | |
| def receive_headers(self, headers, end_stream, header_encoding): | |
| """ | |
| Receive a set of headers (or trailers). | |
| """ | |
| if is_informational_response(headers): | |
| if end_stream: | |
| raise ProtocolError( | |
| "Cannot set END_STREAM on informational responses" | |
| ) | |
| input_ = StreamInputs.RECV_INFORMATIONAL_HEADERS | |
| else: | |
| input_ = StreamInputs.RECV_HEADERS | |
| events = self.state_machine.process_input(input_) | |
| if end_stream: | |
| es_events = self.state_machine.process_input( | |
| StreamInputs.RECV_END_STREAM | |
| ) | |
| events[0].stream_ended = es_events[0] | |
| events += es_events | |
| self._initialize_content_length(headers) | |
| if isinstance(events[0], TrailersReceived): | |
| if not end_stream: | |
| raise ProtocolError("Trailers must have END_STREAM set") | |
| hdr_validation_flags = self._build_hdr_validation_flags(events) | |
| events[0].headers = self._process_received_headers( | |
| headers, hdr_validation_flags, header_encoding | |
| ) | |
| return [], events | |
| def receive_data(self, data, end_stream, flow_control_len): | |
| """ | |
| Receive some data. | |
| """ | |
| self.config.logger.debug( | |
| "Receive data on %r with end stream %s and flow control length " | |
| "set to %d", self, end_stream, flow_control_len | |
| ) | |
| events = self.state_machine.process_input(StreamInputs.RECV_DATA) | |
| self._inbound_window_manager.window_consumed(flow_control_len) | |
| self._track_content_length(len(data), end_stream) | |
| if end_stream: | |
| es_events = self.state_machine.process_input( | |
| StreamInputs.RECV_END_STREAM | |
| ) | |
| events[0].stream_ended = es_events[0] | |
| events.extend(es_events) | |
| events[0].data = data | |
| events[0].flow_controlled_length = flow_control_len | |
| return [], events | |
| def receive_window_update(self, increment): | |
| """ | |
| Handle a WINDOW_UPDATE increment. | |
| """ | |
| self.config.logger.debug( | |
| "Receive Window Update on %r for increment of %d", | |
| self, increment | |
| ) | |
| events = self.state_machine.process_input( | |
| StreamInputs.RECV_WINDOW_UPDATE | |
| ) | |
| frames = [] | |
| # If we encounter a problem with incrementing the flow control window, | |
| # this should be treated as a *stream* error, not a *connection* error. | |
| # That means we need to catch the error and forcibly close the stream. | |
| if events: | |
| events[0].delta = increment | |
| try: | |
| self.outbound_flow_control_window = guard_increment_window( | |
| self.outbound_flow_control_window, | |
| increment | |
| ) | |
| except FlowControlError: | |
| # Ok, this is bad. We're going to need to perform a local | |
| # reset. | |
| event = StreamReset() | |
| event.stream_id = self.stream_id | |
| event.error_code = ErrorCodes.FLOW_CONTROL_ERROR | |
| event.remote_reset = False | |
| events = [event] | |
| frames = self.reset_stream(event.error_code) | |
| return frames, events | |
| def receive_continuation(self): | |
| """ | |
| A naked CONTINUATION frame has been received. This is always an error, | |
| but the type of error it is depends on the state of the stream and must | |
| transition the state of the stream, so we need to handle it. | |
| """ | |
| self.config.logger.debug("Receive Continuation frame on %r", self) | |
| self.state_machine.process_input( | |
| StreamInputs.RECV_CONTINUATION | |
| ) | |
| assert False, "Should not be reachable" | |
| def receive_alt_svc(self, frame): | |
| """ | |
| An Alternative Service frame was received on the stream. This frame | |
| inherits the origin associated with this stream. | |
| """ | |
| self.config.logger.debug( | |
| "Receive Alternative Service frame on stream %r", self | |
| ) | |
| # If the origin is present, RFC 7838 says we have to ignore it. | |
| if frame.origin: | |
| return [], [] | |
| events = self.state_machine.process_input( | |
| StreamInputs.RECV_ALTERNATIVE_SERVICE | |
| ) | |
| # There are lots of situations where we want to ignore the ALTSVC | |
| # frame. If we need to pay attention, we'll have an event and should | |
| # fill it out. | |
| if events: | |
| assert isinstance(events[0], AlternativeServiceAvailable) | |
| events[0].origin = self._authority | |
| events[0].field_value = frame.field | |
| return [], events | |
| def reset_stream(self, error_code=0): | |
| """ | |
| Close the stream locally. Reset the stream with an error code. | |
| """ | |
| self.config.logger.debug( | |
| "Local reset %r with error code: %d", self, error_code | |
| ) | |
| self.state_machine.process_input(StreamInputs.SEND_RST_STREAM) | |
| rsf = RstStreamFrame(self.stream_id) | |
| rsf.error_code = error_code | |
| return [rsf] | |
| def stream_reset(self, frame): | |
| """ | |
| Handle a stream being reset remotely. | |
| """ | |
| self.config.logger.debug( | |
| "Remote reset %r with error code: %d", self, frame.error_code | |
| ) | |
| events = self.state_machine.process_input(StreamInputs.RECV_RST_STREAM) | |
| if events: | |
| # We don't fire an event if this stream is already closed. | |
| events[0].error_code = _error_code_from_int(frame.error_code) | |
| return [], events | |
| def acknowledge_received_data(self, acknowledged_size): | |
| """ | |
| The user has informed us that they've processed some amount of data | |
| that was received on this stream. Pass that to the window manager and | |
| potentially return some WindowUpdate frames. | |
| """ | |
| self.config.logger.debug( | |
| "Acknowledge received data with size %d on %r", | |
| acknowledged_size, self | |
| ) | |
| increment = self._inbound_window_manager.process_bytes( | |
| acknowledged_size | |
| ) | |
| if increment: | |
| f = WindowUpdateFrame(self.stream_id) | |
| f.window_increment = increment | |
| return [f] | |
| return [] | |
| def _build_hdr_validation_flags(self, events): | |
| """ | |
| Constructs a set of header validation flags for use when normalizing | |
| and validating header blocks. | |
| """ | |
| is_trailer = isinstance( | |
| events[0], (_TrailersSent, TrailersReceived) | |
| ) | |
| is_response_header = isinstance( | |
| events[0], | |
| ( | |
| _ResponseSent, | |
| ResponseReceived, | |
| InformationalResponseReceived | |
| ) | |
| ) | |
| is_push_promise = isinstance( | |
| events[0], (PushedStreamReceived, _PushedRequestSent) | |
| ) | |
| return HeaderValidationFlags( | |
| is_client=self.state_machine.client, | |
| is_trailer=is_trailer, | |
| is_response_header=is_response_header, | |
| is_push_promise=is_push_promise, | |
| ) | |
| def _build_headers_frames(self, | |
| headers, | |
| encoder, | |
| first_frame, | |
| hdr_validation_flags): | |
| """ | |
| Helper method to build headers or push promise frames. | |
| """ | |
| # We need to lowercase the header names, and to ensure that secure | |
| # header fields are kept out of compression contexts. | |
| if self.config.normalize_outbound_headers: | |
| headers = normalize_outbound_headers( | |
| headers, hdr_validation_flags | |
| ) | |
| if self.config.validate_outbound_headers: | |
| headers = validate_outbound_headers( | |
| headers, hdr_validation_flags | |
| ) | |
| encoded_headers = encoder.encode(headers) | |
| # Slice into blocks of max_outbound_frame_size. Be careful with this: | |
| # it only works right because we never send padded frames or priority | |
| # information on the frames. Revisit this if we do. | |
| header_blocks = [ | |
| encoded_headers[i:i+self.max_outbound_frame_size] | |
| for i in range( | |
| 0, len(encoded_headers), self.max_outbound_frame_size | |
| ) | |
| ] | |
| frames = [] | |
| first_frame.data = header_blocks[0] | |
| frames.append(first_frame) | |
| for block in header_blocks[1:]: | |
| cf = ContinuationFrame(self.stream_id) | |
| cf.data = block | |
| frames.append(cf) | |
| frames[-1].flags.add('END_HEADERS') | |
| return frames | |
| def _process_received_headers(self, | |
| headers, | |
| header_validation_flags, | |
| header_encoding): | |
| """ | |
| When headers have been received from the remote peer, run a processing | |
| pipeline on them to transform them into the appropriate form for | |
| attaching to an event. | |
| """ | |
| if self.config.normalize_inbound_headers: | |
| headers = normalize_inbound_headers( | |
| headers, header_validation_flags | |
| ) | |
| if self.config.validate_inbound_headers: | |
| headers = validate_headers(headers, header_validation_flags) | |
| if header_encoding: | |
| headers = _decode_headers(headers, header_encoding) | |
| # The above steps are all generators, so we need to concretize the | |
| # headers now. | |
| return list(headers) | |
| def _initialize_content_length(self, headers): | |
| """ | |
| Checks the headers for a content-length header and initializes the | |
| _expected_content_length field from it. It's not an error for no | |
| Content-Length header to be present. | |
| """ | |
| if self.request_method == b'HEAD': | |
| self._expected_content_length = 0 | |
| return | |
| for n, v in headers: | |
| if n == b'content-length': | |
| try: | |
| self._expected_content_length = int(v, 10) | |
| except ValueError: | |
| raise ProtocolError( | |
| "Invalid content-length header: %s" % v | |
| ) | |
| return | |
| def _track_content_length(self, length, end_stream): | |
| """ | |
| Update the expected content length in response to data being received. | |
| Validates that the appropriate amount of data is sent. Always updates | |
| the received data, but only validates the length against the | |
| content-length header if one was sent. | |
| :param length: The length of the body chunk received. | |
| :param end_stream: If this is the last body chunk received. | |
| """ | |
| self._actual_content_length += length | |
| actual = self._actual_content_length | |
| expected = self._expected_content_length | |
| if expected is not None: | |
| if expected < actual: | |
| raise InvalidBodyLengthError(expected, actual) | |
| if end_stream and expected != actual: | |
| raise InvalidBodyLengthError(expected, actual) | |
| def _inbound_flow_control_change_from_settings(self, delta): | |
| """ | |
| We changed SETTINGS_INITIAL_WINDOW_SIZE, which means we need to | |
| update the target window size for flow control. For our flow control | |
| strategy, this means we need to do two things: we need to adjust the | |
| current window size, but we also need to set the target maximum window | |
| size to the new value. | |
| """ | |
| new_max_size = self._inbound_window_manager.max_window_size + delta | |
| self._inbound_window_manager.window_opened(delta) | |
| self._inbound_window_manager.max_window_size = new_max_size | |
| def _decode_headers(headers, encoding): | |
| """ | |
| Given an iterable of header two-tuples and an encoding, decodes those | |
| headers using that encoding while preserving the type of the header tuple. | |
| This ensures that the use of ``HeaderTuple`` is preserved. | |
| """ | |
| for header in headers: | |
| # This function expects to work on decoded headers, which are always | |
| # HeaderTuple objects. | |
| assert isinstance(header, HeaderTuple) | |
| name, value = header | |
| name = name.decode(encoding) | |
| value = value.decode(encoding) | |
| yield header.__class__(name, value) | |