| # -*- coding: utf-8 -*- |
| """ |
| test_informational_responses |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| Tests that validate that hyper-h2 correctly handles informational (1XX) |
| responses in its state machine. |
| """ |
| import pytest |
| |
| import h2.config |
| import h2.connection |
| import h2.events |
| import h2.exceptions |
| |
| |
| class TestReceivingInformationalResponses(object): |
| """ |
| Tests for receiving informational responses. |
| """ |
| example_request_headers = [ |
| (b':authority', b'example.com'), |
| (b':path', b'/'), |
| (b':scheme', b'https'), |
| (b':method', b'GET'), |
| (b'expect', b'100-continue'), |
| ] |
| example_informational_headers = [ |
| (b':status', b'100'), |
| (b'server', b'fake-serv/0.1.0') |
| ] |
| example_response_headers = [ |
| (b':status', b'200'), |
| (b'server', b'fake-serv/0.1.0') |
| ] |
| example_trailers = [ |
| (b'trailer', b'you-bet'), |
| ] |
| |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_single_informational_response(self, frame_factory, end_stream): |
| """ |
| When receiving a informational response, the appropriate event is |
| signaled. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_request_headers, |
| end_stream=end_stream |
| ) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_informational_headers, |
| stream_id=1, |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 1 |
| event = events[0] |
| |
| assert isinstance(event, h2.events.InformationalResponseReceived) |
| assert event.headers == self.example_informational_headers |
| assert event.stream_id == 1 |
| |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): |
| """ |
| At least three header blocks can be received: informational, headers, |
| trailers. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_request_headers, |
| end_stream=end_stream |
| ) |
| |
| f1 = frame_factory.build_headers_frame( |
| headers=self.example_informational_headers, |
| stream_id=1, |
| ) |
| f2 = frame_factory.build_headers_frame( |
| headers=self.example_response_headers, |
| stream_id=1, |
| ) |
| f3 = frame_factory.build_headers_frame( |
| headers=self.example_trailers, |
| stream_id=1, |
| flags=['END_STREAM'], |
| ) |
| events = c.receive_data( |
| f1.serialize() + f2.serialize() + f3.serialize() |
| ) |
| |
| assert len(events) == 4 |
| |
| assert isinstance(events[0], h2.events.InformationalResponseReceived) |
| assert events[0].headers == self.example_informational_headers |
| assert events[0].stream_id == 1 |
| |
| assert isinstance(events[1], h2.events.ResponseReceived) |
| assert events[1].headers == self.example_response_headers |
| assert events[1].stream_id == 1 |
| |
| assert isinstance(events[2], h2.events.TrailersReceived) |
| assert events[2].headers == self.example_trailers |
| assert events[2].stream_id == 1 |
| |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_receiving_multiple_informational_responses(self, |
| frame_factory, |
| end_stream): |
| """ |
| More than one informational response is allowed. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_request_headers, |
| end_stream=end_stream |
| ) |
| |
| f1 = frame_factory.build_headers_frame( |
| headers=self.example_informational_headers, |
| stream_id=1, |
| ) |
| f2 = frame_factory.build_headers_frame( |
| headers=[(':status', '101')], |
| stream_id=1, |
| ) |
| events = c.receive_data(f1.serialize() + f2.serialize()) |
| |
| assert len(events) == 2 |
| |
| assert isinstance(events[0], h2.events.InformationalResponseReceived) |
| assert events[0].headers == self.example_informational_headers |
| assert events[0].stream_id == 1 |
| |
| assert isinstance(events[1], h2.events.InformationalResponseReceived) |
| assert events[1].headers == [(b':status', b'101')] |
| assert events[1].stream_id == 1 |
| |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_receive_provisional_response_with_end_stream(self, |
| frame_factory, |
| end_stream): |
| """ |
| Receiving provisional responses with END_STREAM set causes |
| ProtocolErrors. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_request_headers, |
| end_stream=end_stream |
| ) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_informational_headers, |
| stream_id=1, |
| flags=['END_STREAM'] |
| ) |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.receive_data(f.serialize()) |
| |
| expected = frame_factory.build_goaway_frame( |
| last_stream_id=0, |
| error_code=1, |
| ) |
| assert c.data_to_send() == expected.serialize() |
| |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_receiving_out_of_order_headers(self, frame_factory, end_stream): |
| """ |
| When receiving a informational response after the actual response |
| headers we consider it a ProtocolError and raise it. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_request_headers, |
| end_stream=end_stream |
| ) |
| |
| f1 = frame_factory.build_headers_frame( |
| headers=self.example_response_headers, |
| stream_id=1, |
| ) |
| f2 = frame_factory.build_headers_frame( |
| headers=self.example_informational_headers, |
| stream_id=1, |
| ) |
| c.receive_data(f1.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.receive_data(f2.serialize()) |
| |
| expected = frame_factory.build_goaway_frame( |
| last_stream_id=0, |
| error_code=1, |
| ) |
| assert c.data_to_send() == expected.serialize() |
| |
| |
| class TestSendingInformationalResponses(object): |
| """ |
| Tests for sending informational responses. |
| """ |
| example_request_headers = [ |
| (b':authority', b'example.com'), |
| (b':path', b'/'), |
| (b':scheme', b'https'), |
| (b':method', b'GET'), |
| (b'expect', b'100-continue'), |
| ] |
| unicode_informational_headers = [ |
| (u':status', u'100'), |
| (u'server', u'fake-serv/0.1.0') |
| ] |
| bytes_informational_headers = [ |
| (b':status', b'100'), |
| (b'server', b'fake-serv/0.1.0') |
| ] |
| example_response_headers = [ |
| (b':status', b'200'), |
| (b'server', b'fake-serv/0.1.0') |
| ] |
| example_trailers = [ |
| (b'trailer', b'you-bet'), |
| ] |
| server_config = h2.config.H2Configuration(client_side=False) |
| |
| @pytest.mark.parametrize( |
| 'hdrs', (unicode_informational_headers, bytes_informational_headers), |
| ) |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_single_informational_response(self, |
| frame_factory, |
| hdrs, |
| end_stream): |
| """ |
| When sending a informational response, the appropriate frames are |
| emitted. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| flags = ['END_STREAM'] if end_stream else [] |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers, |
| stream_id=1, |
| flags=flags, |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| frame_factory.refresh_encoder() |
| |
| c.send_headers( |
| stream_id=1, |
| headers=hdrs |
| ) |
| |
| f = frame_factory.build_headers_frame( |
| headers=hdrs, |
| stream_id=1, |
| ) |
| assert c.data_to_send() == f.serialize() |
| |
| @pytest.mark.parametrize( |
| 'hdrs', (unicode_informational_headers, bytes_informational_headers), |
| ) |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_sending_multiple_header_blocks(self, |
| frame_factory, |
| hdrs, |
| end_stream): |
| """ |
| At least three header blocks can be sent: informational, headers, |
| trailers. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| flags = ['END_STREAM'] if end_stream else [] |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers, |
| stream_id=1, |
| flags=flags, |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| frame_factory.refresh_encoder() |
| |
| # Send the three header blocks. |
| c.send_headers( |
| stream_id=1, |
| headers=hdrs |
| ) |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_response_headers |
| ) |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_trailers, |
| end_stream=True |
| ) |
| |
| # Check that we sent them properly. |
| f1 = frame_factory.build_headers_frame( |
| headers=hdrs, |
| stream_id=1, |
| ) |
| f2 = frame_factory.build_headers_frame( |
| headers=self.example_response_headers, |
| stream_id=1, |
| ) |
| f3 = frame_factory.build_headers_frame( |
| headers=self.example_trailers, |
| stream_id=1, |
| flags=['END_STREAM'] |
| ) |
| assert ( |
| c.data_to_send() == |
| f1.serialize() + f2.serialize() + f3.serialize() |
| ) |
| |
| @pytest.mark.parametrize( |
| 'hdrs', (unicode_informational_headers, bytes_informational_headers), |
| ) |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_sending_multiple_informational_responses(self, |
| frame_factory, |
| hdrs, |
| end_stream): |
| """ |
| More than one informational response is allowed. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| flags = ['END_STREAM'] if end_stream else [] |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers, |
| stream_id=1, |
| flags=flags, |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| frame_factory.refresh_encoder() |
| |
| # Send two informational responses. |
| c.send_headers( |
| stream_id=1, |
| headers=hdrs, |
| ) |
| c.send_headers( |
| stream_id=1, |
| headers=[(':status', '101')] |
| ) |
| |
| # Check we sent them both. |
| f1 = frame_factory.build_headers_frame( |
| headers=hdrs, |
| stream_id=1, |
| ) |
| f2 = frame_factory.build_headers_frame( |
| headers=[(':status', '101')], |
| stream_id=1, |
| ) |
| assert c.data_to_send() == f1.serialize() + f2.serialize() |
| |
| @pytest.mark.parametrize( |
| 'hdrs', (unicode_informational_headers, bytes_informational_headers), |
| ) |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_send_provisional_response_with_end_stream(self, |
| frame_factory, |
| hdrs, |
| end_stream): |
| """ |
| Sending provisional responses with END_STREAM set causes |
| ProtocolErrors. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| flags = ['END_STREAM'] if end_stream else [] |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers, |
| stream_id=1, |
| flags=flags, |
| ) |
| c.receive_data(f.serialize()) |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.send_headers( |
| stream_id=1, |
| headers=hdrs, |
| end_stream=True, |
| ) |
| |
| @pytest.mark.parametrize( |
| 'hdrs', (unicode_informational_headers, bytes_informational_headers), |
| ) |
| @pytest.mark.parametrize('end_stream', (True, False)) |
| def test_reject_sending_out_of_order_headers(self, |
| frame_factory, |
| hdrs, |
| end_stream): |
| """ |
| When sending an informational response after the actual response |
| headers we consider it a ProtocolError and raise it. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| flags = ['END_STREAM'] if end_stream else [] |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers, |
| stream_id=1, |
| flags=flags, |
| ) |
| c.receive_data(f.serialize()) |
| |
| c.send_headers( |
| stream_id=1, |
| headers=self.example_response_headers |
| ) |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.send_headers( |
| stream_id=1, |
| headers=hdrs |
| ) |