|
|
|
""" |
|
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 |
|
SEND_INFORMATIONAL_HEADERS = 13 |
|
RECV_INFORMATIONAL_HEADERS = 14 |
|
SEND_ALTERNATIVE_SERVICE = 15 |
|
RECV_ALTERNATIVE_SERVICE = 16 |
|
UPGRADE_CLIENT = 17 |
|
UPGRADE_SERVER = 18 |
|
|
|
|
|
class StreamClosedBy(Enum): |
|
SEND_END_STREAM = 0 |
|
RECV_END_STREAM = 1 |
|
SEND_RST_STREAM = 2 |
|
RECV_RST_STREAM = 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
self.client = None |
|
|
|
|
|
self.headers_sent = None |
|
self.trailers_sent = None |
|
self.headers_received = None |
|
self.trailers_received = None |
|
|
|
|
|
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: |
|
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: |
|
msg = "Idle streams cannot receive pushes" |
|
else: |
|
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). |
|
""" |
|
|
|
|
|
if self.client is False: |
|
return [] |
|
|
|
|
|
|
|
|
|
if self.headers_received: |
|
return [] |
|
|
|
|
|
|
|
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. |
|
""" |
|
|
|
|
|
if self.headers_sent: |
|
raise ProtocolError( |
|
"Cannot send ALTSVC after sending response headers." |
|
) |
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_transitions = { |
|
|
|
(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), |
|
|
|
|
|
(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), |
|
|
|
|
|
(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), |
|
|
|
|
|
(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), |
|
|
|
|
|
(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), |
|
|
|
|
|
(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), |
|
|
|
|
|
(StreamState.CLOSED, StreamInputs.RECV_END_STREAM): |
|
(None, StreamState.CLOSED), |
|
(StreamState.CLOSED, StreamInputs.RECV_ALTERNATIVE_SERVICE): |
|
(None, StreamState.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), |
|
|
|
|
|
|
|
|
|
|
|
(StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE): |
|
(None, StreamState.CLOSED), |
|
(StreamState.CLOSED, StreamInputs.RECV_RST_STREAM): |
|
(None, StreamState.CLOSED), |
|
|
|
|
|
|
|
|
|
(StreamState.CLOSED, StreamInputs.RECV_PUSH_PROMISE): |
|
(H2StreamStateMachine.recv_push_on_closed_stream, StreamState.CLOSED), |
|
|
|
|
|
(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 |
|
|
|
|
|
self.outbound_flow_control_window = outbound_window_size |
|
|
|
|
|
self._inbound_window_manager = WindowManager(inbound_window_size) |
|
|
|
|
|
self._expected_content_length = None |
|
|
|
|
|
self._actual_content_length = 0 |
|
|
|
|
|
self._authority = None |
|
|
|
|
|
self.config = config |
|
|
|
def __repr__(self): |
|
return "<%s id:%d state:%r>" % ( |
|
type(self).__name__, |
|
self.stream_id, |
|
self.state_machine.state |
|
) |
|
|
|
@property |
|
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 |
|
|
|
@property |
|
def open(self): |
|
""" |
|
Whether the stream is 'open' in any sense: that is, whether it counts |
|
against the number of concurrent streams. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
return STREAM_OPEN[self.state_machine.state] |
|
|
|
@property |
|
def closed(self): |
|
""" |
|
Whether the stream is closed. |
|
""" |
|
return self.state_machine.state == StreamState.CLOSED |
|
|
|
@property |
|
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 |
|
) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
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. |
|
""" |
|
|
|
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 |
|
|
|
|
|
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 events: |
|
events[0].delta = increment |
|
try: |
|
self.outbound_flow_control_window = guard_increment_window( |
|
self.outbound_flow_control_window, |
|
increment |
|
) |
|
except FlowControlError: |
|
|
|
|
|
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 frame.origin: |
|
return [], [] |
|
|
|
events = self.state_machine.process_input( |
|
StreamInputs.RECV_ALTERNATIVE_SERVICE |
|
) |
|
|
|
|
|
|
|
|
|
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: |
|
|
|
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. |
|
""" |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
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: |
|
|
|
|
|
assert isinstance(header, HeaderTuple) |
|
|
|
name, value = header |
|
name = name.decode(encoding) |
|
value = value.decode(encoding) |
|
yield header.__class__(name, value) |
|
|