diff --git a/hyper/http20/response.py b/hyper/http20/response.py index f137acfd..9a4bae56 100644 --- a/hyper/http20/response.py +++ b/hyper/http20/response.py @@ -70,6 +70,11 @@ def getheaders(self): def items(self): return self._headers.items() + def merge(self, headers): + for n, v in headers: + self._headers[n] = v + self._strip_headers() + def _strip_headers(self): """ Strips the headers attached to the instance of any header beginning @@ -162,6 +167,10 @@ def read(self, amt=None, decode_content=True): if decode_content and self._decompressobj: data += self._decompressobj.flush() + if self._stream.response_headers: + self._headers.merge(self._stream.response_headers) + + # We're at the end. Close the connection. if not data: self.close() diff --git a/hyper/http20/stream.py b/hyper/http20/stream.py index 23464769..0e34e081 100644 --- a/hyper/http20/stream.py +++ b/hyper/http20/stream.py @@ -229,6 +229,8 @@ def receive_frame(self, frame): else: self.promised_headers[self.promised_stream_id] = headers + self.header_data = None + def open(self, end): """ Open the stream. Does this by encoding and sending the headers: no more diff --git a/test/test_hyper.py b/test/test_hyper.py index bd4fa252..0fd29d5b 100644 --- a/test/test_hyper.py +++ b/test/test_hyper.py @@ -1945,6 +1945,8 @@ class DummyStream(object): def __init__(self, data): self.data = data self.closed = False + self.response_headers = {} + self._remote_closed = False def _read(self, *args, **kwargs): try: @@ -1954,6 +1956,10 @@ def _read(self, *args, **kwargs): d = self.data[:read_len] self.data = self.data[read_len:] + + if not self.data: + self._remote_closed = True + return d def close(self): diff --git a/test/test_integration.py b/test/test_integration.py index d9623983..98e9e38a 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -37,10 +37,12 @@ def decode_frame(frame_data): return f -def build_headers_frame(headers): +def build_headers_frame(headers, encoder=None): f = HeadersFrame(1) - e = Encoder() - e.huffman_coder = HuffmanEncoder(REQUEST_CODES, REQUEST_CODES_LENGTH) + 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 @@ -310,6 +312,64 @@ def socket_handler(listener): self.tear_down() + def test_receiving_trailers(self): + self.set_up() + + recv_event = threading.Event() + + 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() + + self._start_server(socket_handler) + conn = self.get_connection() + conn.request('GET', '/') + resp = conn.getresponse() + + # Confirm the status code. + assert resp.status == 200 + + # Confirm that we can read this, but it has no body. + assert resp.read() == b'have some data' + assert resp._stream._in_window_manager.document_size == 0 + + # Confirm that we got the trailing headers, and that they don't contain + # reserved headers. + assert resp.getheader('trailing') == 'sure' + assert resp.getheader(':res') is None + assert len(resp.getheaders()) == 2 + + # Awesome, we're done now. + recv_event.set() + + self.tear_down() + def test_clean_shut_down(self): self.set_up()