def test_settings_frame_serializes_properly(self): f = SettingsFrame(0) f.parse_flags(0xFF) f.settings = self.settings s = f.serialize() assert s == self.serialized
def build_settings_frame(self, settings, ack=False): """ Builds a single settings frame. """ f = SettingsFrame(0) if ack: f.flags.add('ACK') f.settings = settings return f
def socket_handler(listener): sock = listener.accept()[0] # Dispose of the first packet. sock.recv(65535) # Send a Settings frame that reduces the flow-control window to # 64 bytes. f = SettingsFrame(0) f.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 64 sock.send(f.serialize()) # Grab three frames, the settings ACK, the initial headers frame, # and the first data frame. for x in range(0, 3): data.append(sock.recv(65535)) # Send a WindowUpdate giving more window room to the stream. f = WindowUpdateFrame(1) f.window_increment = 64 sock.send(f.serialize()) # Send one that gives more room to the connection. f = WindowUpdateFrame(0) f.window_increment = 64 sock.send(f.serialize()) # Reeive the remaining frame. data.append(sock.recv(65535)) send_event.set() # We're done. sock.close()
def get_http2_upgrade_header(self): # try h2c protocol hc = {} hc["Upgrade"] = "h2c" hc['Connection'] = 'Upgrade, HTTP2-Settings' http2_settings = SettingsFrame(0) http2_settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535 settings = base64.urlsafe_b64encode( http2_settings.serialize_body()).rstrip(b'=').decode("utf8") hc['HTTP2-Settings'] = settings return hc
def update_settings(self, new_settings): """ Update the local settings. This will prepare and emit the appropriate SETTINGS frame. :param new_settings: A dictionary of {setting: new value} """ self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) self.local_settings.update(new_settings) s = SettingsFrame(0) s.settings = new_settings self._prepare_for_sending([s])
def _add_upgrade_headers(self, headers): # Add HTTP Upgrade headers. headers[b'connection'] = b'Upgrade, HTTP2-Settings' headers[b'upgrade'] = H2C_PROTOCOL # Encode SETTINGS frame payload in Base64 and put into the HTTP-2 # Settings header. http2_settings = SettingsFrame(0) http2_settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535 encoded_settings = base64.urlsafe_b64encode( http2_settings.serialize_body()) headers[b'HTTP2-Settings'] = encoded_settings.rstrip(b'=')
def test_incrementing_window_after_close(self): """ Hyper does not attempt to increment the flow control window once the stream is closed. """ # For this test, we want to send a response that has three frames at # the default max frame size (16,384 bytes). That will, on the third # frame, trigger the processing to increment the flow control window, # which should then not happen. f = SettingsFrame(0, settings={h2.settings.INITIAL_WINDOW_SIZE: 100}) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # Open stream 1. c.request('GET', '/') # Check what data we've sent right now. originally_sent_data = c._sock.queue[:] # Swap out the buffer to get a GoAway frame. length = 16384 total_length = (3 * 16384) + len(b'some more data') e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode( [(':status', 200), ('content-length', '%d' % total_length)] ) h1.flags |= set(['END_HEADERS']) d1 = DataFrame(1) d1.data = b'\x00' * length d2 = d1 d3 = d1 d4 = DataFrame(1) d4.data = b'some more data' d4.flags |= set(['END_STREAM']) buffer = BytesIO( b''.join(f.serialize() for f in [h1, d1, d2, d3, d4]) ) c._sock.buffer = buffer # Read the response resp = c.get_response(stream_id=1) assert resp.status == 200 assert resp.read() == b''.join( [b'\x00' * (3 * length), b'some more data'] ) # We should have sent only one extra frame assert len(originally_sent_data) + 1 == len(c._sock.queue)
def _add_upgrade_headers(self, headers): # Add HTTP Upgrade headers. headers[b'connection'] = b'Upgrade, HTTP2-Settings' headers[b'upgrade'] = H2C_PROTOCOL # Encode SETTINGS frame payload in Base64 and put into the HTTP-2 # Settings header. http2_settings = SettingsFrame(0) http2_settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535 encoded_settings = base64.urlsafe_b64encode( http2_settings.serialize_body() ) headers[b'HTTP2-Settings'] = encoded_settings.rstrip(b'=')
def socket_handler(listener): sock = listener.accept()[0] # We should get one big chunk. first = sock.recv(65535) data.append(first) # We need to send back a SettingsFrame. f = SettingsFrame(0) sock.send(f.serialize()) send_event.set() sock.close()
def _send_preamble(self): self.send_queue.put(RawFrame(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n')) f = SettingsFrame(0) f.settings[SettingsFrame.ENABLE_PUSH] = 0 f.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = self.local_settings[SettingsFrame.INITIAL_WINDOW_SIZE] f.settings[SettingsFrame.MAX_FRAME_SIZE] = self.local_settings[SettingsFrame.MAX_FRAME_SIZE] self._send_cb(f) # update local connection windows size f = WindowUpdateFrame(0) f.window_increment = self.local_connection_initial_windows - DEFAULT_WINDOW_SIZE self._send_cb(f)
def initiate_connection(self): """ Provides any data that needs to be sent at the start of the connection. Must be called for both clients and servers. """ self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) if self.client_side: preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' else: preamble = b'' f = SettingsFrame(0) for setting, value in self.local_settings.items(): f.settings[setting] = value self._data_to_send += preamble + f.serialize()
def receive_frame(self, frame): #self.logger.debug("h2conn recv:%s", frame) if frame.type == WindowUpdateFrame.type: # self.logger.debug("WindowUpdateFrame %d", frame.window_increment) self.increase_remote_window_size(frame.window_increment) elif frame.type == PingFrame.type: if 'ACK' in frame.flags: ping_time = struct.unpack("!d", frame.opaque_data)[0] time_now = time.time() rtt = (time_now - ping_time) * 1000 if rtt < 0: self.logger.error("rtt:%f ping_time:%f now:%f", rtt, ping_time, time_now) self.rtt = rtt self.ping_on_way -= 1 #self.logger.debug("RTT:%d, on_way:%d", self.rtt, self.ping_on_way) if self.keep_running and self.ping_on_way == 0: self.accept_task = True else: # The spec requires us to reply with PING+ACK and identical data. p = PingFrame(0) p.flags.add('ACK') p.opaque_data = frame.opaque_data self._send_cb(p) elif frame.type == SettingsFrame.type: if 'ACK' not in frame.flags: # send ACK as soon as possible f = SettingsFrame(0) f.flags.add('ACK') self._send_cb(f) # this may trigger send DataFrame blocked by remote window self._update_settings(frame) else: self.accept_task = True self.idle_cb() elif frame.type == GoAwayFrame.type: # If we get GoAway with error code zero, we are doing a graceful # shutdown and all is well. Otherwise, throw an exception. # If an error occured, try to read the error description from # code registry otherwise use the frame's additional data. time_cost = time.time() - self.last_recv_time self.close("GoAway:%s inactive time:%s" % ("conn close", time_cost)) elif frame.type in FRAMES: # This frame isn't valid at this point. #raise ValueError("Unexpected frame %s." % frame) self.logger.error("%s Unexpected frame %s.", self.ip_str, frame) else: # pragma: no cover # Unexpected frames belong to extensions. Just drop it on the # floor, but log so that users know that something happened. self.logger.error("%s Received unknown frame, type %d", self.ip_str, frame.type)
def receive_preamble(sock): # Receive the HTTP/2 'preamble'. first = sock.recv(65535) # Work around some bugs: if the first message received was only the PRI # string, aim to receive a settings frame as well. if len(first) <= len(b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'): sock.recv(65535) sock.send(SettingsFrame(0).serialize()) sock.recv(65535) return
def test_send_tolerate_peer_gone(self): class ErrorSocket(DummySocket): def sendall(self, data): raise socket.error(errno.EPIPE) c = HTTP20Connection('www.google.com') c._sock = ErrorSocket() f = SettingsFrame(0) with pytest.raises(socket.error): c._send_cb(f, False) c._sock = DummySocket() c._send_cb(f, True) # shouldn't raise an error
def test_settings_frame_serializes_properly(self): f = SettingsFrame() f.parse_flags(0xFF) f.settings = self.settings s = f.serialize() assert s == self.serialized
def test_resetting_streams_after_close(self): """ Attempts to reset streams when the connection is torn down are tolerated. """ f = SettingsFrame(0) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._sock.buffer = BytesIO(f.serialize()) # Open stream 1. c.request('GET', '/') # Swap out the buffer to get a GoAway frame. f = GoAwayFrame(0) f.error_code = 1 c._sock.buffer = BytesIO(f.serialize()) # "Read" the GoAway with pytest.raises(ConnectionError): c._single_read()
def acknowledge_settings(self, event): """ Acknowledge settings that have been received. :param event: The RemoteSettingsChanged event that is being acknowledged. :returns: A list of events. """ assert isinstance(event, RemoteSettingsChanged) self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) changes = self.remote_settings.acknowledge() if SettingsFrame.INITIAL_WINDOW_SIZE in changes: setting = changes[SettingsFrame.INITIAL_WINDOW_SIZE] self._flow_control_change_from_settings( setting.original_value, setting.new_value, ) # HEADER_TABLE_SIZE changes by the remote part affect our encoder: cf. # RFC 7540 Section 6.5.2. if SettingsFrame.HEADER_TABLE_SIZE in changes: setting = changes[SettingsFrame.HEADER_TABLE_SIZE] self.encoder.header_table_size = setting.new_value if SettingsFrame.SETTINGS_MAX_FRAME_SIZE in changes: setting = changes[SettingsFrame.SETTINGS_MAX_FRAME_SIZE] self.max_outbound_frame_size = setting.new_value for stream in self.streams.values(): stream.max_outbound_frame_size = setting.new_value f = SettingsFrame(0) f.flags.add('ACK') self._prepare_for_sending([f]) return []
def _acknowledge_settings(self): """ Acknowledge settings that have been received. .. versionchanged:: 2.0.0 Removed from public API, removed useless ``event`` parameter, made automatic. :returns: Nothing """ self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) changes = self.remote_settings.acknowledge() if INITIAL_WINDOW_SIZE in changes: setting = changes[INITIAL_WINDOW_SIZE] self._flow_control_change_from_settings( setting.original_value, setting.new_value, ) # HEADER_TABLE_SIZE changes by the remote part affect our encoder: cf. # RFC 7540 Section 6.5.2. if HEADER_TABLE_SIZE in changes: setting = changes[HEADER_TABLE_SIZE] self.encoder.header_table_size = setting.new_value if MAX_FRAME_SIZE in changes: setting = changes[MAX_FRAME_SIZE] self.max_outbound_frame_size = setting.new_value for stream in self.streams.values(): stream.max_outbound_frame_size = setting.new_value f = SettingsFrame(0) f.flags.add('ACK') return [f]
def test_repr(self): f = SettingsFrame() assert repr(f).endswith("settings={}") f.settings[SettingsFrame.MAX_FRAME_SIZE] = 16384 assert repr(f).endswith("settings={5: 16384}")
def test_settings_frame_has_only_one_flag(self): f = SettingsFrame(0) flags = f.parse_flags(0xFF) assert flags == set(['ACK'])
def test_settings_frames_never_have_streams(self): with pytest.raises(ValueError): SettingsFrame(stream_id=1)
def test_settings_frame_ack_and_settings(self): with pytest.raises(ValueError): SettingsFrame(settings=self.settings, flags=('ACK', ))
def test_settings_frame_with_ack(self): f = SettingsFrame(flags=('ACK', )) assert 'ACK' in f.flags
def test_settings_frame_without_settings(self): f = SettingsFrame() assert f.settings == {}
def test_settings_frame_has_only_one_flag(self): f = SettingsFrame() flags = f.parse_flags(0xFF) assert flags == set(['ACK'])
# https://github.com/python-hyper/hpack/pull/60 --- test/test_hyper.py.orig 2019-05-17 10:17:07 UTC +++ test/test_hyper.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- -import h2.settings - from h2.frame_buffer import FrameBuffer from h2.connection import ConnectionState +from h2.settings import SettingCodes from hyperframe.frame import ( Frame, DataFrame, RstStreamFrame, SettingsFrame, PushPromiseFrame, WindowUpdateFrame, HeadersFrame, ContinuationFrame, GoAwayFrame, PingFrame, FRAME_MAX_ALLOWED_LEN ) -from hpack.hpack_compat import Encoder +from hpack import Encoder from hyper.common.connection import HTTPConnection from hyper.http20.connection import HTTP20Connection from hyper.http20.response import HTTP20Response, HTTP20Push @@ -766,7 +765,7 @@ class TestHyperConnection(object): # the default max frame size (16,384 bytes). That will, on the third # frame, trigger the processing to increment the flow control window, # which should then not happen. - f = SettingsFrame(0, settings={h2.settings.INITIAL_WINDOW_SIZE: 100}) + f = SettingsFrame(0, settings={SettingCodes.INITIAL_WINDOW_SIZE: 100}) c = HTTP20Connection('www.google.com') c._sock = DummySocket()
def test_settings_frames_never_have_streams(self): with pytest.raises(InvalidDataError): SettingsFrame(1)
def test_settings_frame_ack_and_settings(self): with pytest.raises(InvalidDataError): SettingsFrame(settings=self.settings, flags=('ACK', )) with pytest.raises(InvalidDataError): decode_frame(self.serialized)
def test_settings_frame_with_settings(self): f = SettingsFrame(settings=self.settings) assert f.settings == self.settings