def build_rst_stream_frame(self, stream_id, error_code=0): """ Builds a single RST_STREAM frame. """ f = RstStreamFrame(stream_id) f.error_code = error_code return f
def _receive_frame(self, frame): """ Handle a frame received on the connection. .. versionchanged:: 2.0.0 Removed from the public API. """ try: # I don't love using __class__ here, maybe reconsider it. frames, events = self._frame_dispatch_table[frame.__class__](frame) except StreamClosedError as e: # We need to send a RST_STREAM frame on behalf of the stream. # The frame the stream wants to emit is already present in the # exception. # This does not require re-raising: it's an expected behaviour. f = RstStreamFrame(e.stream_id) f.error_code = e.error_code self._prepare_for_sending([f]) events = e._events except KeyError as e: # We don't have a function for handling this frame. Let's call this # a PROTOCOL_ERROR and exit. raise UnsupportedFrameError("Unexpected frame: %s" % frame) else: self._prepare_for_sending(frames) return events
def reset_stream(self, error_code=0): """ Close the stream locally. Reset the stream with an error code. """ self.state_machine.process_input(StreamInputs.SEND_RST_STREAM) rsf = RstStreamFrame(self.stream_id) rsf.error_code = error_code return [rsf]
def _receive_push_promise_frame(self, frame): """ Receive a push-promise frame on the connection. """ if not self.local_settings.enable_push: raise ProtocolError("Received pushed stream") pushed_headers = self.decoder.decode(frame.data) events = self.state_machine.process_input( ConnectionInputs.RECV_PUSH_PROMISE ) try: stream = self._get_stream_by_id(frame.stream_id) except NoSuchStreamError: # We need to check if the parent stream was reset by us. If it was # then we presume that the PUSH_PROMISE was in flight when we reset # the parent stream. Rather than accept the new stream, just reset # it. # # If this was closed naturally, however, 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. if frame.stream_id in self._reset_streams: f = RstStreamFrame(frame.promised_stream_id) f.error_code = REFUSED_STREAM return [f], events raise ProtocolError("Attempted to push on closed stream.") # We need to prevent peers pushing streams in response to streams that # they themselves have already pushed: see #163 and RFC 7540 § 6.6. The # easiest way to do that is to assert that the stream_id is not even: # this shortcut works because only servers can push and the state # machine will enforce this. if (frame.stream_id % 2) == 0: raise ProtocolError("Cannot recursively push streams.") frames, stream_events = stream.receive_push_promise_in_band( frame.promised_stream_id, pushed_headers, ) new_stream = self._begin_new_stream( frame.promised_stream_id, AllowedStreamIDs.EVEN ) self.streams[frame.promised_stream_id] = new_stream new_stream.remotely_pushed() return frames, events + stream_events
def test_cancel_push(self): self.add_push_frame(1, 2, [(':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip')]) self.add_headers_frame(1, [(':status', '200'), ('content-type', 'text/html')]) self.request() self.conn.get_response() list(self.conn.get_pushes())[0].cancel() f = RstStreamFrame(2) f.error_code = 8 assert self.conn._sock.queue[-1] == f.serialize()
def test_reset_pushed_streams_when_push_disabled(self): self.add_push_frame(1, 2, [(':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip')]) self.add_headers_frame(1, [(':status', '200'), ('content-type', 'text/html')]) self.request() self.conn._enable_push = False self.conn.get_response() f = RstStreamFrame(2) f.error_code = 7 assert self.conn._sock.queue[-1] == f.serialize()
def socket_handler(listener): sock = listener.accept()[0] # We get two messages for the connection open and then a HEADERS # frame. receive_preamble(sock) sock.recv(65535) # Now, send two RST_STREAM frames. for _ in range(0, 2): f = RstStreamFrame(1) sock.send(f.serialize()) # Wait for the message from the main thread. recv_event.wait(5) sock.close()
def _receive_frame(self, frame): """ Handle a frame received on the connection. .. versionchanged:: 2.0.0 Removed from the public API. """ try: # I don't love using __class__ here, maybe reconsider it. frames, events = self._frame_dispatch_table[frame.__class__](frame) except StreamClosedError as e: # We need to send a RST_STREAM frame on behalf of the stream. # The frame the stream wants to emit is already present in the # exception. # This does not require re-raising: it's an expected behaviour. The # only time we don't do that is if this is a stream the user # manually reset. if frame.stream_id not in self._reset_streams: f = RstStreamFrame(e.stream_id) f.error_code = e.error_code self._prepare_for_sending([f]) events = e._events else: events = [] except StreamIDTooLowError as e: # The stream ID seems invalid. This is unlikely, so it's probably # the case that this frame is actually for a stream that we've # already reset and removed the state for. If it is, just swallow # the error. If we didn't do that, re-raise. if frame.stream_id not in self._reset_streams: raise events = [] except KeyError as e: # We don't have a function for handling this frame. Let's call this # a PROTOCOL_ERROR and exit. raise UnsupportedFrameError("Unexpected frame: %s" % frame) else: self._prepare_for_sending(frames) return events
def test_cancel_push(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn.get_response() list(self.conn.get_pushes())[0].cancel() f = RstStreamFrame(2) f.error_code = 8 assert self.conn._sock.queue[-1] == f.serialize()
def test_reset_pushed_streams_when_push_disabled(self): self.add_push_frame( 1, 2, [ (':method', 'GET'), (':path', '/'), (':authority', 'www.google.com'), (':scheme', 'https'), ('accept-encoding', 'gzip') ] ) self.add_headers_frame( 1, [(':status', '200'), ('content-type', 'text/html')] ) self.request() self.conn._enable_push = False self.conn.get_response() f = RstStreamFrame(2) f.error_code = 7 assert self.conn._sock.queue[-1] == f.serialize()
def receive_frame(self, frame): """ Handle a frame received on the connection. """ try: if frame.body_len > self.max_inbound_frame_size: raise ProtocolError( "Received overlong frame: length %d, max %d" % (frame.body_len, self.max_inbound_frame_size)) # I don't love using __class__ here, maybe reconsider it. frames, events = self._frame_dispatch_table[frame.__class__](frame) except ProtocolError as e: # For whatever reason, receiving the frame caused a protocol error. # We should prepare to emit a GoAway frame before throwing the # exception up further. No need for an event: the exception will # do fine. f = GoAwayFrame(0) f.last_stream_id = sorted(self.streams.keys())[-1] f.error_code = e.error_code self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) self._prepare_for_sending([f]) raise except StreamClosedError as e: # We need to send a RST_STREAM frame on behalf of the stream. # The frame the stream wants to emit is already present in the # exception. # This does not require re-raising: it's an expected behaviour. f = RstStreamFrame(e.stream_id) f.error_code = e.error_code self._prepare_for_sending([f]) events = [] else: self._prepare_for_sending(frames) return events
def test_rst_stream_frame_has_no_flags(self): f = RstStreamFrame(1) flags = f.parse_flags(0xFF) assert not flags assert isinstance(flags, Flags)
def test_rst_stream_frame_must_have_body_length_four(self): f = RstStreamFrame(1) with pytest.raises(ValueError): f.parse_body(b'\x01')
def test_rst_stream_frame_serializes_properly(self): f = RstStreamFrame(1) f.error_code = 420 s = f.serialize() assert s == b'\x00\x00\x04\x03\x00\x00\x00\x00\x01\x00\x00\x01\xa4'
def test_rst_stream_frame_has_no_flags(self): f = RstStreamFrame(1) flags = f.parse_flags(0xFF) assert not flags assert isinstance(flags, set)
def test_rst_stream_frame_comes_on_a_stream(self): with pytest.raises(ValueError): RstStreamFrame(0)
def test_rst_stream_frame_comes_on_a_stream(self): with pytest.raises(InvalidDataError): RstStreamFrame(0)
def test_repr(self): f = RstStreamFrame(1) assert repr(f).endswith("error_code=0") f.error_code = 420 assert repr(f).endswith("error_code=420")