def test_request_correctly_sent_max_chunk(self, frame_buffer): """ Test that request correctly sent when data length multiple max chunk. We check last chunk has a end flag and correct number of chunks. """ def data_callback(chunk, **kwargs): frame_buffer.add_data(chunk) # one chunk c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders(message_body=b'1'*1024, final=True) frames = list(frame_buffer) assert len(frames) == 2 assert isinstance(frames[1], DataFrame) assert frames[1].flags == set(['END_STREAM']) # two chunks c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders(message_body=b'1' * 2024, final=True) frames = list(frame_buffer) assert len(frames) == 3 assert isinstance(frames[1], DataFrame) assert frames[2].flags == set(['END_STREAM']) # two chunks with last chunk < 1024 c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders(message_body=b'1' * 2000, final=True) frames = list(frame_buffer) assert len(frames) == 3 assert isinstance(frames[1], DataFrame) assert frames[2].flags == set(['END_STREAM']) # no chunks c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders(message_body=b'', final=True) frames = list(frame_buffer) assert len(frames) == 1
def request(self): self.conn = HTTP20Connection('www.google.com', enable_push=True) self.conn._sock = DummySocket() self.conn._sock.buffer = BytesIO( b''.join([frame.serialize() for frame in self.frames]) ) self.conn.request('GET', '/')
def test_closed_connections_are_reset(self): c = HTTP20Connection('www.google.com') c._sock = DummySocket() wm = c.window_manager c.request('GET', '/') c.close() assert c._sock is None assert not c.streams assert c.recent_stream is None assert c.next_stream_id == 1 assert c.window_manager is not wm with c._conn as conn: assert conn.state_machine.state == ConnectionState.IDLE origin_h2_conn = conn c.close() assert c._sock is None assert not c.streams assert c.recent_stream is None assert c.next_stream_id == 1 assert c.window_manager is not wm with c._conn as conn: assert conn.state_machine.state == ConnectionState.IDLE assert conn != origin_h2_conn
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 test_read_headers_out_of_order(self): # If header blocks aren't decoded in the same order they're received, # regardless of the stream they belong to, the decoder state will # become corrupted. e = Encoder() h1 = HeadersFrame(1) h1.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h1.flags |= set(['END_HEADERS', 'END_STREAM']) h3 = HeadersFrame(3) h3.data = e.encode([(':status', 200), ('content-type', 'baz/qux')]) h3.flags |= set(['END_HEADERS', 'END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h1.serialize() + h3.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock r1 = c.request('GET', '/a') r3 = c.request('GET', '/b') assert c.get_response(r3).headers == HTTPHeaderMap( [('content-type', 'baz/qux')] ) assert c.get_response(r1).headers == HTTPHeaderMap( [('content-type', 'foo/bar')] )
def send_request_message(self, request_id, meta, body, message_expiry_in_seconds=None): connection = HTTP20Connection( host=self.http_host, port=self.http_port, secure=self.secure, ssl_context=self.ssl_context, ) message = {'request_id': request_id, 'meta': meta, 'body': body} serializer = self.default_serializer non_default_serializer = False if 'serializer' in meta: # TODO: Breaking change: Assume a MIME type is always specified. This should not be done until all # TODO servers and clients have Step 2 code. This will be a Step 3 breaking change. serializer = meta.pop('serializer') non_default_serializer = True serialized_message = serializer.dict_to_blob(message) if non_default_serializer: # TODO: Breaking change: Make this happen always, not just when a specific MIME type was requested. # TODO This should not be done until all servers and clients have this Step 1 code. This will be a Step # TODO 2 breaking change. serialized_message = ( 'content-type:{};'.format(serializer.mime_type).encode('utf-8') + serialized_message ) request = connection.request('POST', '/', body=serialized_message) self.requests.append((connection, request))
def test_stream_window_increments_appropriately(self, frame_buffer): e = Encoder() h = HeadersFrame(1) h.data = e.encode([(':status', 200), ('content-type', 'foo/bar')]) h.flags = set(['END_HEADERS']) d = DataFrame(1) d.data = b'hi there sir' d2 = DataFrame(1) d2.data = b'hi there sir again' sock = DummySocket() sock.buffer = BytesIO(h.serialize() + d.serialize() + d2.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/') c.streams[1]._in_window_manager.window_size = 1000 c.streams[1]._in_window_manager.initial_window_size = 1000 resp = c.get_response() resp.read(len(b'hi there sir')) resp.read(len(b'hi there sir again')) frame_buffer.add_data(b''.join(sock.queue)) queue = list(frame_buffer) assert len(queue) == 3 # one headers frame, two window update frames. assert isinstance(queue[1], WindowUpdateFrame) assert queue[1].window_increment == len(b'hi there sir') assert isinstance(queue[2], WindowUpdateFrame) assert queue[2].window_increment == len(b'hi there sir again')
def test_window_increments_appropriately(self): e = Encoder() h = HeadersFrame(1) h.data = e.encode({':status': 200, 'content-type': 'foo/bar'}) h.flags = set(['END_HEADERS']) d = DataFrame(1) d.data = b'hi there sir' d2 = DataFrame(1) d2.data = b'hi there sir again' d2.flags = set(['END_STREAM']) sock = DummySocket() sock.buffer = BytesIO(h.serialize() + d.serialize() + d2.serialize()) c = HTTP20Connection('www.google.com') c._sock = sock c.window_manager.window_size = 1000 c.window_manager.initial_window_size = 1000 c.request('GET', '/') resp = c.get_response() resp.read() queue = list(map(decode_frame, map(memoryview, sock.queue))) assert len(queue) == 3 # one headers frame, two window update frames. assert isinstance(queue[1], WindowUpdateFrame) assert queue[1].window_increment == len(b'hi there sir') assert isinstance(queue[2], WindowUpdateFrame) assert queue[2].window_increment == len(b'hi there sir again')
def test_different_request_headers(self): sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/', body='hello', headers={b'name': b'value'}) s = c.recent_stream assert list(s.headers.items()) == [ (b':method', b'GET'), (b':scheme', b'https'), (b':authority', b'www.google.com'), (b':path', b'/'), (b'name', b'value'), ] c.request('GET', '/', body='hello', headers={u'name2': u'value2'}) s = c.recent_stream assert list(s.headers.items()) == [ (b':method', b'GET'), (b':scheme', b'https'), (b':authority', b'www.google.com'), (b':path', b'/'), (b'name2', b'value2'), ]
def test_connections_can_parse_proxy_hosts_and_ports(self): c = HTTP20Connection('www.google.com', proxy_host='localhost', proxy_port=8443) assert c.host == 'www.google.com' assert c.proxy_host == 'localhost' assert c.proxy_port == 8443
def test_putrequest_establishes_new_stream(self): c = HTTP20Connection("www.google.com") stream_id = c.putrequest('GET', '/') stream = c.streams[stream_id] assert len(c.streams) == 1 assert c.recent_stream is stream
def test_connections_can_parse_ipv6_hosts_and_ports(self): c = HTTP20Connection('[abcd:dcba::1234]', proxy_host='[ffff:aaaa::1]:8443') assert c.host == 'abcd:dcba::1234' assert c.port == 443 assert c.proxy_host == 'ffff:aaaa::1' assert c.proxy_port == 8443
def test_receive_unexpected_frame(self): # RST_STREAM frames are never defined on connections, so send one of # those. c = HTTP20Connection('www.google.com') f = RstStreamFrame(1) with pytest.raises(ValueError): c.receive_frame(f)
def test_that_we_correctly_send_over_the_socket(self): sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock c.putrequest('GET', '/') c.endheaders(message_body=b'hello there', final=True) # Don't bother testing that the serialization was ok, that should be # fine. assert len(sock.queue) == 3
def test_connections_increment_send_window_properly(self): f = WindowUpdateFrame(0) f.window_increment = 1000 c = HTTP20Connection('www.google.com') c._sock = DummySocket() # 'Receive' the WINDOWUPDATE frame. c.receive_frame(f) assert c._out_flow_control_window == 65535 + 1000
def test_ping_with_ack_ignored(self): c = HTTP20Connection('www.google.com') f = PingFrame(0) f.flags = set(['ACK']) f.opaque_data = b'12345678' def data_cb(frame, tolerate_peer_gone=False): assert False, 'should not be called' c._send_cb = data_cb c.receive_frame(f)
def test_closed_connections_are_reset(self): c = HTTP20Connection('www.google.com') c._sock = DummySocket() wm = c.window_manager c.request('GET', '/') c.close() assert c._sock is None assert not c.streams assert c.recent_stream is None assert c.next_stream_id == 1 assert c.window_manager is not wm
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 test_we_can_read_from_the_socket(self): sock = DummySocket() sock.buffer = BytesIO(b'\x00\x00\x08\x00\x01\x00\x00\x00\x01testdata') c = HTTP20Connection('www.google.com') c._sock = sock c.putrequest('GET', '/') c.endheaders() c._recv_cb() s = c.recent_stream assert s.data == [b'testdata']
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_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() # '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.receive_frame(f)
def test_putrequest_autosets_headers(self): c = HTTP20Connection("www.google.com") c.putrequest('GET', '/') s = c.recent_stream assert s.headers == [ (':method', 'GET'), (':scheme', 'https'), (':authority', 'www.google.com'), (':path', '/'), ]
def test_that_we_correctly_send_over_the_socket(self): sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock c.putrequest('GET', '/') c.endheaders(message_body=b'hello there', final=True) # Don't bother testing that the serialization was ok, that should be # fine. assert len(sock.queue) == 2 # Confirm the window got shrunk. assert c._out_flow_control_window == 65535 - len(b'hello there')
def test_putrequest_sends_data(self): sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/', body='hello', headers={'Content-Type': 'application/json'}) # The socket should have received one headers frame and one body frame. assert len(sock.queue) == 2 assert c._out_flow_control_window == 65535 - len(b'hello')
def test_putrequest_sends_data(self): sock = DummySocket() c = HTTP20Connection('www.google.com') c._sock = sock c.request('GET', '/', body='hello', headers={'Content-Type': 'application/json'}) # The socket should have received one headers frame and two body # frames. assert len(sock.queue) == 3
def test_we_can_read_from_the_socket(self): sock = DummySocket() sock.buffer = BytesIO(b'\x00\x08\x00\x01\x00\x00\x00\x01testdata') c = HTTP20Connection('www.google.com') c._sock = sock c.putrequest('GET', '/') c.endheaders() c._recv_cb() s = c.recent_stream assert len(s._queued_frames) == 1 assert isinstance(s._queued_frames[0], DataFrame) assert s._queued_frames[0].data == b'testdata'
def test_putheader_puts_headers(self): c = HTTP20Connection("www.google.com") c.putrequest('GET', '/') c.putheader('name', 'value') s = c.recent_stream assert list(s.headers.items()) == [ (b':method', b'GET'), (b':scheme', b'https'), (b':authority', b'www.google.com'), (b':path', b'/'), (b'name', b'value'), ]
def test_endheaders_sends_data(self, frame_buffer): def data_callback(chunk, **kwargs): frame_buffer.add_data(chunk) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders() frames = list(frame_buffer) assert len(frames) == 1 f = frames[0] assert isinstance(f, HeadersFrame)
def test_endheaders_sends_data(self): frames = [] def data_callback(frame): frames.append(frame) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback c.putrequest('GET', '/') c.endheaders() assert len(frames) == 1 f = frames[0] assert isinstance(f, HeadersFrame)
def test_ping(self, frame_buffer): def data_callback(chunk, **kwargs): frame_buffer.add_data(chunk) c = HTTP20Connection('www.google.com') c._sock = DummySocket() c._send_cb = data_callback opaque = '00000000' c.ping(opaque) frames = list(frame_buffer) assert len(frames) == 1 f = frames[0] assert isinstance(f, PingFrame) assert f.opaque_data == to_bytestring(opaque)