| # -*- coding: utf-8 -*- |
| """ |
| test_rfc7838 |
| ~~~~~~~~~~~~ |
| |
| Test the RFC 7838 ALTSVC support. |
| """ |
| import pytest |
| |
| import h2.config |
| import h2.connection |
| import h2.events |
| import h2.exceptions |
| |
| |
| class TestRFC7838Client(object): |
| """ |
| Tests that the client supports receiving the RFC 7838 AltSvc frame. |
| """ |
| example_request_headers = [ |
| (':authority', 'example.com'), |
| (':path', '/'), |
| (':scheme', 'https'), |
| (':method', 'GET'), |
| ] |
| example_response_headers = [ |
| (u':status', u'200'), |
| (u'server', u'fake-serv/0.1.0') |
| ] |
| |
| def test_receiving_altsvc_stream_zero(self, frame_factory): |
| """ |
| An ALTSVC frame received on stream zero correctly transposes all the |
| fields from the frames. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 1 |
| event = events[0] |
| |
| assert isinstance(event, h2.events.AlternativeServiceAvailable) |
| assert event.origin == b"example.com" |
| assert event.field_value == b'h2=":8000"; ma=60' |
| |
| # No data gets sent. |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory): |
| """ |
| An ALTSVC frame received on stream zero without an origin field is |
| ignored. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=0, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert not events |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_on_stream(self, frame_factory): |
| """ |
| An ALTSVC frame received on a stream correctly transposes all the |
| fields from the frame and attaches the expected origin. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 1 |
| event = events[0] |
| |
| assert isinstance(event, h2.events.AlternativeServiceAvailable) |
| assert event.origin == b"example.com" |
| assert event.field_value == b'h2=":8000"; ma=60' |
| |
| # No data gets sent. |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_on_stream_with_origin(self, frame_factory): |
| """ |
| An ALTSVC frame received on a stream with an origin field present gets |
| ignored. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"example.com", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_on_stream_not_yet_opened(self, frame_factory): |
| """ |
| When an ALTSVC frame is received on a stream the client hasn't yet |
| opened, the frame is ignored. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.clear_outbound_data_buffer() |
| |
| # We'll test this twice, once on a client-initiated stream ID and once |
| # on a server initiated one. |
| f1 = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| f2 = frame_factory.build_alt_svc_frame( |
| stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f1.serialize() + f2.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_before_sending_headers(self, frame_factory): |
| """ |
| When an ALTSVC frame is received but the client hasn't sent headers yet |
| it gets ignored. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| |
| # We need to create the idle stream. We have to do it by calling |
| # a private API. While this can't naturally happen in hyper-h2 (we |
| # don't currently have a mechanism by which this could occur), it could |
| # happen in the future and we defend against it. |
| c._begin_new_stream( |
| stream_id=1, allowed_ids=h2.connection.AllowedStreamIDs.ODD |
| ) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_after_receiving_headers(self, frame_factory): |
| """ |
| When an ALTSVC frame is received but the server has already sent |
| headers it gets ignored. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_response_headers |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_on_closed_stream(self, frame_factory): |
| """ |
| When an ALTSVC frame is received on a closed stream, we ignore it. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers( |
| stream_id=1, headers=self.example_request_headers, end_stream=True |
| ) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_response_headers, |
| flags=['END_STREAM'], |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_on_pushed_stream(self, frame_factory): |
| """ |
| When an ALTSVC frame is received on a stream that the server pushed, |
| the frame is accepted. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| |
| f = frame_factory.build_push_promise_frame( |
| stream_id=1, |
| promised_stream_id=2, |
| headers=self.example_request_headers |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 1 |
| event = events[0] |
| |
| assert isinstance(event, h2.events.AlternativeServiceAvailable) |
| assert event.origin == b"example.com" |
| assert event.field_value == b'h2=":8000"; ma=60' |
| |
| # No data gets sent. |
| assert not c.data_to_send() |
| |
| def test_cannot_send_explicit_alternative_service(self, frame_factory): |
| """ |
| A client cannot send an explicit alternative service. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| c.clear_outbound_data_buffer() |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| origin=b"example.com", |
| ) |
| |
| def test_cannot_send_implicit_alternative_service(self, frame_factory): |
| """ |
| A client cannot send an implicit alternative service. |
| """ |
| c = h2.connection.H2Connection() |
| c.initiate_connection() |
| c.send_headers(stream_id=1, headers=self.example_request_headers) |
| c.clear_outbound_data_buffer() |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| stream_id=1, |
| ) |
| |
| |
| class TestRFC7838Server(object): |
| """ |
| Tests that the server supports sending the RFC 7838 AltSvc frame. |
| """ |
| example_request_headers = [ |
| (':authority', 'example.com'), |
| (':path', '/'), |
| (':scheme', 'https'), |
| (':method', 'GET'), |
| ] |
| example_response_headers = [ |
| (u':status', u'200'), |
| (u'server', u'fake-serv/0.1.0') |
| ] |
| |
| server_config = h2.config.H2Configuration(client_side=False) |
| |
| def test_receiving_altsvc_as_server_stream_zero(self, frame_factory): |
| """ |
| When an ALTSVC frame is received on stream zero and we are a server, |
| we ignore it. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_receiving_altsvc_as_server_on_stream(self, frame_factory): |
| """ |
| When an ALTSVC frame is received on a stream and we are a server, we |
| ignore it. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| events = c.receive_data(f.serialize()) |
| |
| assert len(events) == 0 |
| assert not c.data_to_send() |
| |
| def test_sending_explicit_alternative_service(self, frame_factory): |
| """ |
| A server can send an explicit alternative service. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| c.clear_outbound_data_buffer() |
| |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| origin=b"example.com", |
| ) |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' |
| ) |
| assert c.data_to_send() == f.serialize() |
| |
| def test_sending_implicit_alternative_service(self, frame_factory): |
| """ |
| A server can send an implicit alternative service. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers |
| ) |
| c.receive_data(f.serialize()) |
| c.clear_outbound_data_buffer() |
| |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| stream_id=1, |
| ) |
| |
| f = frame_factory.build_alt_svc_frame( |
| stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' |
| ) |
| assert c.data_to_send() == f.serialize() |
| |
| def test_no_implicit_alternative_service_before_headers(self, |
| frame_factory): |
| """ |
| If headers haven't been received yet, the server forbids sending an |
| implicit alternative service. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| c.clear_outbound_data_buffer() |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| stream_id=1, |
| ) |
| |
| def test_no_implicit_alternative_service_after_response(self, |
| frame_factory): |
| """ |
| If the server has sent response headers, hyper-h2 forbids sending an |
| implicit alternative service. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers |
| ) |
| c.receive_data(f.serialize()) |
| c.send_headers(stream_id=1, headers=self.example_response_headers) |
| c.clear_outbound_data_buffer() |
| |
| with pytest.raises(h2.exceptions.ProtocolError): |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| stream_id=1, |
| ) |
| |
| def test_cannot_provide_origin_and_stream_id(self, frame_factory): |
| """ |
| The user cannot provide both the origin and stream_id arguments when |
| advertising alternative services. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| f = frame_factory.build_headers_frame( |
| headers=self.example_request_headers |
| ) |
| c.receive_data(f.serialize()) |
| |
| with pytest.raises(ValueError): |
| c.advertise_alternative_service( |
| field_value=b'h2=":8000"; ma=60', |
| origin=b"example.com", |
| stream_id=1, |
| ) |
| |
| def test_cannot_provide_unicode_altsvc_field(self, frame_factory): |
| """ |
| The user cannot provide the field value for alternative services as a |
| unicode string. |
| """ |
| c = h2.connection.H2Connection(config=self.server_config) |
| c.initiate_connection() |
| c.receive_data(frame_factory.preamble()) |
| |
| with pytest.raises(ValueError): |
| c.advertise_alternative_service( |
| field_value=u'h2=":8000"; ma=60', |
| origin=b"example.com", |
| ) |