def test_response_ignores_unknown_headers(self): headers = HTTPHeaderMap([(':status', '200'), (':reserved', 'yes'), ('no', 'no')]) stream = DummyStream(b'') resp = HTTP20Response(headers, stream) assert resp.headers == HTTPHeaderMap([('no', 'no')])
def test_can_receive_trailers(self): headers = [('a', 'b'), ('c', 'd'), (':status', '200')] trailers = [('e', 'f'), ('g', 'h')] s = Stream(1, None, None, None, None, FixedDecoder(headers), None) s.state = STATE_HALF_CLOSED_LOCAL # Provide the first HEADERS frame. f = HeadersFrame(1) f.data = b'hi there!' f.flags.add('END_HEADERS') s.receive_frame(f) assert s.response_headers == HTTPHeaderMap(headers) # Now, replace the dummy decoder to ensure we get a new header block. s._decoder = FixedDecoder(trailers) # Provide the trailers. f = HeadersFrame(1) f.data = b'hi there again!' f.flags.add('END_STREAM') f.flags.add('END_HEADERS') s.receive_frame(f) # Now, check the trailers. assert s.response_trailers == HTTPHeaderMap(trailers) # Confirm we closed the stream. assert s.state == STATE_CLOSED
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 test_trailers_are_read(self): headers = HTTPHeaderMap([(':status', '200')]) trailers = HTTPHeaderMap([('a', 'b'), ('c', 'd')]) stream = DummyStream(b'', trailers=trailers) resp = HTTP20Response(headers, stream) assert resp.trailers == trailers assert resp.trailers['a'] == [b'b'] assert resp.trailers['c'] == [b'd']
def test_headers_must_be_strings(self): with pytest.raises(ValueError): HTTPHeaderMap(key=1) h = HTTPHeaderMap() with pytest.raises(ValueError): h['k'] = 1 with pytest.raises(ValueError): h[1] = 'v'
def test_pushed_requests_ignore_unexpected_headers(self): headers = HTTPHeaderMap([ (':scheme', 'http'), (':method', 'get'), (':authority', 'google.com'), (':path', '/'), (':reserved', 'no'), ('no', 'no'), ]) p = HTTP20Push(headers, DummyStream(b'')) assert p.request_headers == HTTPHeaderMap([('no', 'no')])
def test_equality(self): h1 = HTTPHeaderMap() h1['k1'] = 'v1, v2' h1['k2'] = 'v3' h1['k1'] = 'v4' h2 = HTTPHeaderMap() h2['k1'] = 'v1, v2' h2['k2'] = 'v3' h2['k1'] = 'v4' assert h1 == h2
def test_inequality_of_raw_ordering(self): h1 = HTTPHeaderMap() h1['k1'] = 'v1, v2' h1['k2'] = 'v3' h1['k1'] = 'v4' h2 = HTTPHeaderMap() h2['k1'] = 'v1' h2['k1'] = 'v2' h2['k2'] = 'v3' h2['k1'] = 'v4' assert h1 != h2
def test_merge_headermaps_preserves_raw(self): h1 = HTTPHeaderMap([ (b'hi', b'there') ]) h2 = HTTPHeaderMap([ (b'Hi', b'there, sir, maam') ]) h1.merge(h2) assert list(h1.iter_raw()) == [ (b'hi', b'there'), (b'Hi', b'there, sir, maam'), ]
def test_response_calls_stream_close(self): headers = HTTPHeaderMap([(':status', '200')]) stream = DummyStream('') resp = HTTP20Response(headers, stream) resp.close() assert stream.closed
def test_getheader(self): headers = HTTPHeaderMap([(':status', '200'), ('content-type', 'application/json')]) stream = DummyStream(b'') resp = HTTP20Response(headers, stream) assert resp.headers[b'content-type'] == [b'application/json']
def _handle_header_block(self, headers): """ Handles the logic for receiving a completed headers block. A headers block is an uninterrupted sequence of one HEADERS frame followed by zero or more CONTINUATION frames, and is terminated by a frame bearing the END_HEADERS flag. HTTP/2 allows receipt of up to three such blocks on a stream. The first is optional, and contains a 1XX response. The second is mandatory, and must contain a final response (200 or higher). The third is optional, and may contain 'trailers', headers that are sent after a chunk-encoded body is sent. Here we only process the simple state: no push, one header frame. """ if self.response_headers is None: self.response_headers = HTTPHeaderMap(headers) else: # Received too many headers blocks. raise ProtocolError("Too many header blocks.") status = int(self.response_headers[b':status'][0]) if status in [400, 403]: # xlog.warn("status:%d host:%s", status, self.host) self.connection.close("get 40x") return
def test_length_counts_lines_separately(self): h = HTTPHeaderMap() h['k1'] = 'v1, v2' h['k2'] = 'v3' h['k1'] = 'v4' assert len(h) == 4
def test_header_map_can_delete_value(self): h = HTTPHeaderMap() h['key'] = b'v1' del h[b'key'] with pytest.raises(KeyError): h[b'key']
def test_actual_get(self): h = HTTPHeaderMap() h['k1'] = 'v1, v2' h['k2'] = 'v3' h['k1'] = 'v4' assert h.get('k1') == [b'v1', b'v2', b'v4']
def test_containment(self): h = HTTPHeaderMap() h['key'] = 'val' assert 'key' in h assert b'key' in h assert 'nonkey' not in h
def test_header_map_deletes_all_values(self): h = HTTPHeaderMap() h['key'] = 'v1' h['key'] = 'v2' del h['key'] with pytest.raises(KeyError): h['key']
def test_keys(self): h = HTTPHeaderMap() h['k1'] = 'v1, v2' h['k2'] = 'v3' h['k1'] = 'v4' assert len(list(h.keys())) == 4 assert list(h.keys()) == [b'k1', b'k1', b'k2', b'k1']
def test_responses_are_context_managers(self): headers = HTTPHeaderMap([(':status', '200')]) stream = DummyStream('') with HTTP20Response(headers, stream): pass assert stream.closed
def test_values(self): h = HTTPHeaderMap() h['k1'] = 'v1, v2' h['k2'] = 'v3' h['k1'] = 'v4' assert len(list(h.values())) == 4 assert list(h.values()) == [b'v1', b'v2', b'v3', b'v4']
def test_response_transparently_decrypts_wrong_deflate(self): headers = HTTPHeaderMap([(':status', '200'), ('content-encoding', 'deflate')]) c = zlib_compressobj(wbits=-zlib.MAX_WBITS) body = c.compress(b'this is test data') body += c.flush() resp = HTTP20Response(headers, DummyStream(body)) assert resp.read() == b'this is test data'
def test_raw_iteration(self): items = [ (b'k1', b'v2'), (b'k2', b'v2, v3, v4'), (b'k2', b'v3'), ] h = HTTPHeaderMap(items) assert list(h.iter_raw()) == items
def test_can_create_from_multiple_iterables(self): items = [ (b'k1', b'v2'), (b'k2', b'v2'), (b'k2', b'v3'), ] h = HTTPHeaderMap(items, items, items) assert list(h) == items + items + items
def test_request_with_unicodestring_body(self): c = HTTP11Connection('httpbin.org') c._sock = DummySocket() with pytest.raises(ValueError): c.request('POST', '/post', headers=HTTPHeaderMap([('User-Agent', 'hyper')]), body=u'hi')
def test_read_frames(self): headers = HTTPHeaderMap([(':status', '200')]) stream = DummyStream(None) chunks = [b'12', b'3456', b'78', b'9'] stream.data_frames = chunks resp = HTTP20Response(headers, stream) for recv, expected in zip(resp.read_chunked(), chunks[:]): assert recv == expected
def __init__(self, attrs): self.body = {} self.headers = HTTPHeaderMap() self.items = [] self.method = None self._url = '' self.url = DummyUrlInfo() for key, value in attrs.items(): setattr(self, key, value)
def test_merge_header_map_dict(self): h = HTTPHeaderMap([(b'hi', b'there')]) d = {'cat': 'dog'} h.merge(d) assert list(h.items()) == [ (b'hi', b'there'), (b'cat', b'dog'), ]
def test_reading_trailers_early_reads_all_data(self): in_frames = [] headers = [('a', 'b'), ('c', 'd'), (':status', '200')] trailers = [('e', 'f'), ('g', 'h')] def recv_cb(s): def inner(): s.receive_frame(in_frames.pop(0)) return inner s = Stream(1, None, None, None, None, FixedDecoder(headers), FlowControlManager(65535)) s._recv_cb = recv_cb(s) s.state = STATE_HALF_CLOSED_LOCAL # Provide the first HEADERS frame. f = HeadersFrame(1) f.data = b'hi there!' f.flags.add('END_HEADERS') in_frames.append(f) # Provide some data. f = DataFrame(1) f.data = b'testdata' in_frames.append(f) # Provide the trailers. f = HeadersFrame(1) f.data = b'hi there again!' f.flags.add('END_STREAM') f.flags.add('END_HEADERS') in_frames.append(f) # Begin by reading the first headers. assert s.getheaders() == HTTPHeaderMap(headers) # Now, replace the dummy decoder to ensure we get a new header block. s._decoder = FixedDecoder(trailers) # Ask for the trailers. This should also read the data frames. assert s.gettrailers() == HTTPHeaderMap(trailers) assert s.data == [b'testdata']
def test_read_small_chunks(self): headers = HTTPHeaderMap([(':status', '200')]) stream = DummyStream(b'1234567890') chunks = [b'12', b'34', b'56', b'78', b'90'] resp = HTTP20Response(headers, stream) for chunk in chunks: assert resp.read(2) == chunk assert resp.read() == b''
def __init__(self, connection, ip, stream_id, host, task, send_cb, close_cb, header_encoder, header_decoder, receive_window_manager, remote_window_size, max_frame_size): self.connection = connection self.ip = ip self.stream_id = stream_id self.host = host self.task = task self.state = STATE_IDLE self.get_head_time = None # There are two flow control windows: one for data we're sending, # one for data being sent to us. self.receive_window_manager = receive_window_manager self.remote_window_size = remote_window_size self.max_frame_size = max_frame_size # This is the callback handed to the stream by its parent connection. # It is called when the stream wants to send data. It expects to # receive a list of frames that will be automatically serialized. self._send_cb = send_cb # This is the callback to be called when the stream is closed. self._close_cb = close_cb # A reference to the header encoder and decoder objects belonging to # the parent connection. self._encoder = header_encoder self._decoder = header_decoder self.request_headers = HTTPHeaderMap() # Convert the body to bytes if needed. self.request_body = to_bytestring(self.task.body) # request body not send blocked by send window # the left body will send when send window opened. self.request_body_left = len(self.request_body) self.request_body_sended = False # data list before decode self.response_header_datas = [] # Set to a key-value set of the response headers once their # HEADERS..CONTINUATION frame sequence finishes. self.response_headers = None # Unconsumed response data chunks self.response_body = [] self.response_body_len = 0 threading.Thread(target=self.timeout_response).start() self.start_request()