def socket_handler(listener): sock = listener.accept()[0] e = Encoder() e.huffman_coder = HuffmanEncoder(REQUEST_CODES, REQUEST_CODES_LENGTH) # We get two messages for the connection open and then a HEADERS # frame. receive_preamble(sock) # Now, send the headers for the response. This response has no body. f = build_headers_frame([(':status', '200'), ('content-length', '0')], e) f.stream_id = 1 sock.send(f.serialize()) # Also send a data frame. f = DataFrame(1) f.data = b'have some data' sock.send(f.serialize()) # Now, send a headers frame again, containing trailing headers. f = build_headers_frame([('trailing', 'sure'), (':res', 'no')], e) f.flags.add('END_STREAM') f.stream_id = 1 sock.send(f.serialize()) # Wait for the message from the main thread. recv_event.wait() sock.close()
def get_encoder(self): """ Returns a HPACK encoder set up for responses. """ e = Encoder() e.huffman_coder = HuffmanEncoder(REQUEST_CODES, REQUEST_CODES_LENGTH) return e
def build_headers_frame(headers): f = HeadersFrame(1) e = Encoder() e.huffman_coder = HuffmanEncoder(RESPONSE_CODES, RESPONSE_CODES_LENGTH) f.data = e.encode(headers) f.flags.add('END_HEADERS') return f
def test_indexed_header_field_from_static_table(self): e = Encoder() e.header_table_size = 0 header_set = {':method': 'GET'} result = b'\x82' assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == []
def test_request_examples_with_huffman(self): """ This section shows the same examples as the previous section, but using Huffman encoding for the literal values. """ e = Encoder() first_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com'), ] # The first_header_table doesn't contain 'authority' first_header_table = first_header_set[::-1][1:] first_result = ( b'\x82\x87\x86\x44\x8b\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f' ) assert e.encode(first_header_set, huffman=True) == first_result assert list(e.header_table) == [ (n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table ] # This request takes advantage of the differential encoding of header # sets. second_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com',), ('cache-control', 'no-cache'), ] second_result = b'\x44\x8b\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f\x5a\x86\x63\x65\x4a\x13\x98\xff' assert e.encode(second_header_set, huffman=True) == second_result assert list(e.header_table) == [ (n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table ] # This request has not enough headers in common with the previous # request to take advantage of the differential encoding. Therefore, # the reference set is emptied before encoding the header fields. third_header_set = [ (':method', 'GET',), (':scheme', 'https',), (':path', '/index.html',), (':authority', 'www.example.com',), ('custom-key', 'custom-value'), ] third_result = ( b'\x80\x83\x8a\x89F\x8b\xdbm\x88>h\xd1\xcb\x12%\xba\x7f\x00\x88N' b'\xb0\x8bt\x97\x90\xfa\x7f\x89N\xb0\x8bt\x97\x9a\x17\xa8\xff' ) assert e.encode(third_header_set, huffman=True) == third_result # Don't check the header table here, it's just too complex to be # reliable. Check its length though. assert len(e.header_table) == 6
def build_headers_frame(headers, encoder=None): f = HeadersFrame(1) e = encoder if e is None: e = Encoder() e.huffman_coder = HuffmanEncoder(REQUEST_CODES, REQUEST_CODES_LENGTH) f.data = e.encode(headers) f.flags.add('END_HEADERS') return f
def test_request_examples_without_huffman(self): """ This section shows several consecutive header sets, corresponding to HTTP requests, on the same connection. """ e = Encoder() first_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com'), ] # The first_header_table doesn't contain 'authority' first_header_table = first_header_set[::-1][1:] first_result = b'\x82\x87\x86\x44\x0fwww.example.com' assert e.encode(first_header_set, huffman=False) == first_result assert list(e.header_table) == [ (n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table ] # This request takes advantage of the differential encoding of header # sets. second_header_set = [ (':method', 'GET',), (':scheme', 'http',), (':path', '/',), (':authority', 'www.example.com',), ('cache-control', 'no-cache'), ] second_result = b'\x44\x0fwww.example.com\x5a\x08no-cache' assert e.encode(second_header_set, huffman=False) == second_result assert list(e.header_table) == [ (n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table ] # This request has not enough headers in common with the previous # request to take advantage of the differential encoding. Therefore, # the reference set is emptied before encoding the header fields. third_header_set = [ (':method', 'GET',), (':scheme', 'https',), (':path', '/index.html',), (':authority', 'www.example.com',), ('custom-key', 'custom-value'), ] third_result = ( b'\x80\x83\x8a\x89\x46\x0fwww.example.com' + b'\x00\x0acustom-key\x0ccustom-value' ) assert e.encode(third_header_set, huffman=False) == third_result # Don't check the header table here, it's just too complex to be # reliable. Check its length though. assert len(e.header_table) == 6
def test_literal_header_field_without_indexing(self): """ The header field representation uses an indexed name and a literal value. """ e = Encoder() header_set = {':path': '/sample/path'} result = b'\x44\x0c/sample/path' assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == []
def test_literal_header_field_with_indexing(self): """ The header field representation uses a literal name and a literal value. """ e = Encoder() header_set = {"custom-key": "custom-header"} result = b"\x00\x0acustom-key\x0dcustom-header" assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == [(n.encode("utf-8"), v.encode("utf-8")) for n, v in header_set.items()]
def hpack(): """ This task generates HPACK test data suitable for use with https://github.com/http2jp/hpack-test-case The current format defines a JSON object with three keys: 'draft', 'description' and 'cases'. The cases key has as its value a list of objects, with each object representing a set of headers and the output from the encoder. The object has the following keys: - 'header_table_size': the size of the header table used. - 'headers': A list of the headers as JSON objects. - 'wire': The output from the encoder in hexadecimal. """ # A generator that contains the paths to all the raw data files and their # names. raw_story_files = ((os.path.join('test_fixtures/raw-data', name), name) for name in os.listdir('test_fixtures/raw-data')) # For each file, build our output. for source, outname in raw_story_files: with open(source, 'rb') as f: indata = json.loads(f.read()) # Prepare the output and the encoder. output = { 'draft': DRAFT, 'description': 'Encoded by hyper. See github.com/Lukasa/hyper for more information.', 'cases': [] } e = Encoder() for case in indata['cases']: outcase = {'header_table_size': e.header_table_size} outcase['headers'] = case['headers'] headers = [] for header in case['headers']: key = header.keys()[0] header = (key, header[key]) headers.append(header) outcase['wire'] = hexlify(e.encode(headers)) output['cases'].append(outcase) with open(outname, 'wb') as f: f.write( json.dumps(output, sort_keys=True, indent=2, separators=(',', ': ')))
def hpack(): """ This task generates HPACK test data suitable for use with https://github.com/http2jp/hpack-test-case The current format defines a JSON object with three keys: 'draft', 'description' and 'cases'. The cases key has as its value a list of objects, with each object representing a set of headers and the output from the encoder. The object has the following keys: - 'header_table_size': the size of the header table used. - 'headers': A list of the headers as JSON objects. - 'wire': The output from the encoder in hexadecimal. """ # A generator that contains the paths to all the raw data files and their # names. raw_story_files = ( (os.path.join('test/test_fixtures/raw-data', name), name) for name in os.listdir('test/test_fixtures/raw-data') ) # For each file, build our output. for source, outname in raw_story_files: with open(source, 'rb') as f: indata = json.load(f) # Prepare the output and the encoder. output = { 'description': 'Encoded by hyper. See github.com/Lukasa/hyper for more information.', 'cases': [] } e = Encoder() for case in indata['cases']: outcase = { 'header_table_size': e.header_table_size, 'headers': case['headers'], } headers = [] for header in case['headers']: key = header.keys()[0] header = (key, header[key]) headers.append(header) outcase['wire'] = hexlify(e.encode(headers)) output['cases'].append(outcase) with open(outname, 'wb') as f: f.write(json.dumps(output, sort_keys=True, indent=2, separators=(',', ': ')))
def test_can_encode_a_story_with_huffman(self, raw_story): d = Decoder() e = Encoder() for case in raw_story['cases']: # The input headers are a list of dicts, which is annoying. input_headers = [(item[0], item[1]) for header in case['headers'] for item in header.items()] encoded = e.encode(input_headers, huffman=True) decoded_headers = d.decode(encoded) assert input_headers == decoded_headers
def test_indexed_header_field(self): """ The header field representation uses an indexed header field, from the static table. Upon using it, the static table entry is copied into the header table. """ e = Encoder() header_set = {":method": "GET"} result = b"\x82" assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == [(n.encode("utf-8"), v.encode("utf-8")) for n, v in header_set.items()]
def test_literal_header_field_with_indexing(self): """ The header field representation uses a literal name and a literal value. """ e = Encoder() header_set = {'custom-key': 'custom-header'} result = b'\x00\x0acustom-key\x0dcustom-header' assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in header_set.items()]
def test_indexed_header_field(self): """ The header field representation uses an indexed header field, from the static table. Upon using it, the static table entry is copied into the header table. """ e = Encoder() header_set = {':method': 'GET'} result = b'\x82' assert e.encode(header_set, huffman=False) == result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in header_set.items()]
def test_can_encode_a_story_with_huffman(self, raw_story): d = Decoder() e = Encoder() if raw_story['context'] == 'request': d.huffman_coder = HuffmanDecoder(REQUEST_CODES, REQUEST_CODES_LENGTH) else: e.huffman_coder = HuffmanEncoder(RESPONSE_CODES, RESPONSE_CODES_LENGTH) for case in raw_story['cases']: # The input headers are a list of dicts, which is annoying. input_headers = {(item[0], item[1]) for header in case['headers'] for item in header.items()} encoded = e.encode(input_headers, huffman=True) decoded_headers = d.decode(encoded) assert input_headers == decoded_headers
def test_request_examples_with_huffman(self): """ This section shows the same examples as the previous section, but using Huffman encoding for the literal values. """ e = Encoder() first_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'http', ), ( ':path', '/', ), (':authority', 'www.example.com'), ] # The first_header_table doesn't contain 'authority' first_header_table = first_header_set[::-1][1:] first_result = ( b'\x82\x87\x86\x44\x8b\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f' ) assert e.encode(first_header_set, huffman=True) == first_result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table] # This request takes advantage of the differential encoding of header # sets. second_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'http', ), ( ':path', '/', ), ( ':authority', 'www.example.com', ), ('cache-control', 'no-cache'), ] second_result = b'\x44\x8b\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f\x5a\x86\x63\x65\x4a\x13\x98\xff' assert e.encode(second_header_set, huffman=True) == second_result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table] # This request has not enough headers in common with the previous # request to take advantage of the differential encoding. Therefore, # the reference set is emptied before encoding the header fields. third_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'https', ), ( ':path', '/index.html', ), ( ':authority', 'www.example.com', ), ('custom-key', 'custom-value'), ] third_result = ( b'\x80\x83\x8a\x89F\x8b\xdbm\x88>h\xd1\xcb\x12%\xba\x7f\x00\x88N' b'\xb0\x8bt\x97\x90\xfa\x7f\x89N\xb0\x8bt\x97\x9a\x17\xa8\xff') assert e.encode(third_header_set, huffman=True) == third_result # Don't check the header table here, it's just too complex to be # reliable. Check its length though. assert len(e.header_table) == 6
def test_request_examples_without_huffman(self): """ This section shows several consecutive header sets, corresponding to HTTP requests, on the same connection. """ e = Encoder() first_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'http', ), ( ':path', '/', ), (':authority', 'www.example.com'), ] # The first_header_table doesn't contain 'authority' first_header_table = first_header_set[::-1][1:] first_result = b'\x82\x87\x86\x44\x0fwww.example.com' assert e.encode(first_header_set, huffman=False) == first_result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table] # This request takes advantage of the differential encoding of header # sets. second_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'http', ), ( ':path', '/', ), ( ':authority', 'www.example.com', ), ('cache-control', 'no-cache'), ] second_result = b'\x44\x0fwww.example.com\x5a\x08no-cache' assert e.encode(second_header_set, huffman=False) == second_result assert list(e.header_table) == [(n.encode('utf-8'), v.encode('utf-8')) for n, v in first_header_table] # This request has not enough headers in common with the previous # request to take advantage of the differential encoding. Therefore, # the reference set is emptied before encoding the header fields. third_header_set = [ ( ':method', 'GET', ), ( ':scheme', 'https', ), ( ':path', '/index.html', ), ( ':authority', 'www.example.com', ), ('custom-key', 'custom-value'), ] third_result = (b'\x80\x83\x8a\x89\x46\x0fwww.example.com' + b'\x00\x0acustom-key\x0ccustom-value') assert e.encode(third_header_set, huffman=False) == third_result # Don't check the header table here, it's just too complex to be # reliable. Check its length though. assert len(e.header_table) == 6