def test_stream_close_behavior(self): # Prepare a socket so we can open a stream. sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock # Open a few requests (which creates a stream) s1 = c.request('GET', '/') c.request('GET', '/') # simulate state of blocking on read while sock f = GoAwayFrame(0) # Set error code to PROTOCOL_ERROR f.error_code = 1 c._sock.buffer = BytesIO(f.serialize()) # 'Receive' the GOAWAY frame. # Validate that the spec error name and description are used to throw # the connection exception. with pytest.raises(ConnectionError): c.get_response(s1) # try to read again after close with pytest.raises(ConnectionError): c._single_read()
def build_goaway_frame(self, last_stream_id, error_code=0): """ Builds a single GOAWAY frame. """ f = GoAwayFrame(0) f.error_code = error_code f.last_stream_id = last_stream_id return f
def test_repr(self): f = GoAwayFrame() assert repr(f).endswith( "last_stream_id=0, error_code=0, additional_data=b''") f.last_stream_id = 64 f.error_code = 32 f.additional_data = b'hello' assert repr(f).endswith( "last_stream_id=64, error_code=32, additional_data=b'hello'")
def _terminate_connection(self, error_code): """ Terminate the connection early. Used in error handling blocks to send GOAWAY frames. """ f = GoAwayFrame(0) f.last_stream_id = self.highest_inbound_stream_id f.error_code = error_code self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) self._prepare_for_sending([f])
def build_goaway_frame(self, last_stream_id, error_code=0, additional_data=b''): """ Builds a single GOAWAY frame. """ f = GoAwayFrame(0) f.error_code = error_code f.last_stream_id = last_stream_id f.additional_data = additional_data return f
def close_connection(self, error_code=0): """ Close a connection, emitting a GOAWAY frame. """ self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) f = GoAwayFrame(0) f.error_code = error_code f.last_stream_id = self.highest_stream_id self._prepare_for_sending([f]) return []
def test_goaway_serializes_properly(self): f = GoAwayFrame() f.last_stream_id = 64 f.error_code = 32 f.additional_data = b'hello' s = f.serialize() assert s == ( b'\x00\x00\x0D\x07\x00\x00\x00\x00\x00' + # Frame header b'\x00\x00\x00\x40' + # Last Stream ID b'\x00\x00\x00\x20' + # Error Code b'hello' # Additional data )
def test_goaway_frame_NO_ERROR(self): f = GoAwayFrame(0) # Set error code to NO_ERROR f.error_code = 0 c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # 'Receive' the GOAWAY frame. # Test makes sure no exception is raised; error code 0 means we are # dealing with a standard and graceful shutdown. c._single_read()
def test_goaway_serializes_properly(self): f = GoAwayFrame(0) f.last_stream_id = 64 f.error_code = 32 f.additional_data = b'hello' s = f.serialize() assert s == ( b'\x00\x00\x0D\x07\x00\x00\x00\x00\x00' + # Frame header b'\x00\x00\x00\x40' + # Last Stream ID b'\x00\x00\x00\x20' + # Error Code b'hello' # Additional data )
def close_connection(self, error_code=0): """ Close a connection, emitting a GOAWAY frame. :param error_code: (optional) The error code to send in the GOAWAY frame. :returns: Nothing """ self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) f = GoAwayFrame(0) f.error_code = error_code f.last_stream_id = self.highest_inbound_stream_id self._prepare_for_sending([f])
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 socket_handler(listener): sock = listener.accept()[0] # We should get one packet. Rather than respond to it, send a # GOAWAY frame with error code 0 indicating clean shutdown. sock.recv(65535) # Now, send the shut down. f = GoAwayFrame(0) f.error_code = 1 sock.send(f.serialize()) # Wait for the message from the main thread. sock.close() recv_event.set()
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_goaway_frame_invalid_error_code(self): f = GoAwayFrame(0) # Set error code to non existing error f.error_code = 100 c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # 'Receive' the GOAWAY frame. # If the error code does not exist in the spec then the additional # data is used instead. with pytest.raises(ConnectionError) as conn_err: c._single_read() err_msg = str(conn_err) with pytest.raises(ValueError): name, number, description = errors.get_data(100) assert str(f.error_code) in err_msg
def test_goaway_frame_PROTOCOL_ERROR(self): f = GoAwayFrame(0) # Set error code to PROTOCOL_ERROR f.error_code = 1 c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # 'Receive' the GOAWAY frame. # Validate that the spec error name and description are used to throw # the connection exception. with pytest.raises(ConnectionError) as conn_err: c._single_read() err_msg = str(conn_err) name, number, description = errors.get_data(1) assert name in err_msg assert number in err_msg assert description in err_msg
def test_goaway_frame_HTTP_1_1_REQUIRED(self): f = GoAwayFrame(0) # Set error code to HTTP_1_1_REQUIRED f.error_code = 13 c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # 'Receive' the GOAWAY frame. # Validate that the spec error name and description are used to throw # the connection exception. with pytest.raises(ConnectionError) as conn_err: c._single_read() err_msg = str(conn_err) name, number, description = errors.get_data(13) assert name in err_msg assert number in err_msg assert description in err_msg