Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ API Changes (Backward-Incompatible)
- Removed deprecated ``client_side`` and ``header_encoding`` properties from
``H2Connection``.
- ``dict`` objects are no longer allowed for user-supplied headers.
- The default header encoding is now ``None``, not ``utf-8``: this means that
all events that carry headers now return those headers as byte strings by
default. The header encoding can be set back to ``utf-8`` to restore the old
behaviour.

API Changes (Backward-Compatible)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
22 changes: 13 additions & 9 deletions h2/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ class H2Configuration(object):

:param header_encoding: Controls whether the headers emitted by this object
in events are transparently decoded to ``unicode`` strings, and what
encoding is used to do that decoding. For historical reasons, this
defaults to ``'utf-8'``. To prevent the decoding of headers (that is,
to force them to be returned as bytestrings), this can be set to
``False`` or the empty string.
encoding is used to do that decoding. This defaults to ``None``,
meaning that headers will be returned as bytes. To automatically
decode headers (that is, to return them as unicode strings), this can
be set to the string name of any encoding, e.g. ``'utf-8'``.

.. versionchanged:: 3.0.0
Changed default value from ``'utf-8'`` to ``None``

:type header_encoding: ``str``, ``False``, or ``None``

:param validate_outbound_headers: Controls whether the headers emitted
Expand Down Expand Up @@ -122,7 +126,7 @@ class H2Configuration(object):

def __init__(self,
client_side=True,
header_encoding='utf-8',
header_encoding=None,
validate_outbound_headers=True,
normalize_outbound_headers=True,
validate_inbound_headers=True,
Expand All @@ -141,10 +145,10 @@ def header_encoding(self):
"""
Controls whether the headers emitted by this object in events are
transparently decoded to ``unicode`` strings, and what encoding is used
to do that decoding. For historical reasons, this defaults to
``'utf-8'``. To prevent the decoding of headers (that is, to force them
to be returned as bytestrings), this can be set to ``False`` or the
empty string.
to do that decoding. This defaults to ``None``, meaning that headers
will be returned as bytes. To automatically decode headers (that is, to
return them as unicode strings), this can be set to the string name of
any encoding, e.g. ``'utf-8'``.
"""
return self._header_encoding

Expand Down
1 change: 0 additions & 1 deletion h2/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ def __init__(self, config=None):
if self.config is None:
self.config = H2Configuration(
client_side=True,
header_encoding='utf-8',
)

# Objects that store settings, including defaults.
Expand Down
24 changes: 17 additions & 7 deletions test/test_basic_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ def test_receiving_a_response(self, frame_factory):
"""
When receiving a response, the ResponseReceived event fires.
"""
c = h2.connection.H2Connection()
config = h2.config.H2Configuration(header_encoding='utf-8')
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
c.send_headers(1, self.example_request_headers, end_stream=True)

Expand Down Expand Up @@ -346,7 +347,8 @@ def test_receiving_pushed_stream(self, frame_factory):
Pushed streams fire a PushedStreamReceived event, followed by
ResponseReceived when the response headers are received.
"""
c = h2.connection.H2Connection()
config = h2.config.H2Configuration(header_encoding='utf-8')
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
c.send_headers(1, self.example_request_headers, end_stream=False)

Expand Down Expand Up @@ -660,7 +662,8 @@ def test_can_receive_trailers(self, frame_factory):
When two HEADERS blocks are received in the same stream from a
server, the second set are trailers.
"""
c = h2.connection.H2Connection()
config = h2.config.H2Configuration(header_encoding='utf-8')
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
c.send_headers(1, self.example_request_headers)
f = frame_factory.build_headers_frame(self.example_response_headers)
Expand Down Expand Up @@ -842,7 +845,8 @@ def test_cookies_are_joined_on_push(self, frame_factory):
'path=1; test1=val1; test2=val2'
)

c = h2.connection.H2Connection()
config = h2.config.H2Configuration(header_encoding='utf-8')
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
c.send_headers(1, self.example_request_headers, end_stream=True)

Expand Down Expand Up @@ -877,7 +881,9 @@ def test_cookies_arent_joined_without_normalization(self, frame_factory):
]

config = h2.config.H2Configuration(
client_side=True, normalize_inbound_headers=False
client_side=True,
normalize_inbound_headers=False,
header_encoding='utf-8'
)
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
Expand Down Expand Up @@ -918,7 +924,9 @@ class TestBasicServer(object):
(':status', '200'),
('server', 'hyper-h2/0.1.0')
]
server_config = h2.config.H2Configuration(client_side=False)
server_config = h2.config.H2Configuration(
client_side=False, header_encoding='utf-8'
)

def test_ignores_preamble(self):
"""
Expand Down Expand Up @@ -1787,7 +1795,9 @@ def test_cookies_arent_joined_without_normalization(self, frame_factory):
]

config = h2.config.H2Configuration(
client_side=False, normalize_inbound_headers=False
client_side=False,
normalize_inbound_headers=False,
header_encoding='utf-8'
)
c = h2.connection.H2Connection(config=config)
c.initiate_connection()
Expand Down
32 changes: 16 additions & 16 deletions test/test_complex_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ class TestComplexServer(object):
Complex tests for server-side stacks.
"""
example_request_headers = [
(':authority', 'example.com'),
(':path', '/'),
(':scheme', 'https'),
(':method', 'GET'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
]
example_response_headers = [
(':status', '200'),
('server', 'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]
server_config = h2.config.H2Configuration(client_side=False)

Expand Down Expand Up @@ -194,10 +194,10 @@ class TestContinuationFrames(object):
Tests for the relatively complex CONTINUATION frame logic.
"""
example_request_headers = [
(':authority', 'example.com'),
(':path', '/'),
(':scheme', 'https'),
(':method', 'GET'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
]
server_config = h2.config.H2Configuration(client_side=False)

Expand Down Expand Up @@ -371,14 +371,14 @@ class TestContinuationFramesPushPromise(object):
PUSH_PROMISE frames.
"""
example_request_headers = [
(':authority', 'example.com'),
(':path', '/'),
(':scheme', 'https'),
(':method', 'GET'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
]
example_response_headers = [
(':status', '200'),
('server', 'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]

def _build_continuation_sequence(self, headers, block_size, frame_factory):
Expand Down
2 changes: 1 addition & 1 deletion test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_defaults(self):
"""
config = h2.config.H2Configuration()
assert config.client_side
assert config.header_encoding == 'utf-8'
assert config.header_encoding is None
assert isinstance(config.logger, h2.config.DummyLogger)

boolean_config_options = [
Expand Down
24 changes: 12 additions & 12 deletions test/test_h2_upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ class TestClientUpgrade(object):
Tests of the client-side of the HTTP/2 upgrade dance.
"""
example_request_headers = [
(u':authority', u'example.com'),
(u':path', u'/'),
(u':scheme', u'https'),
(u':method', u'GET'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
]
example_response_headers = [
(u':status', u'200'),
(u'server', u'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]

def test_returns_http2_settings(self, frame_factory):
Expand Down Expand Up @@ -146,14 +146,14 @@ class TestServerUpgrade(object):
Tests of the server-side of the HTTP/2 upgrade dance.
"""
example_request_headers = [
(u':authority', u'example.com'),
(u':path', u'/'),
(u':scheme', u'https'),
(u':method', u'GET'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
]
example_response_headers = [
(u':status', u'200'),
(u'server', u'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]
server_config = h2.config.H2Configuration(client_side=False)

Expand Down
14 changes: 7 additions & 7 deletions test/test_head_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
class TestHeadRequest(object):

example_request_headers = [
(u':authority', u'example.com'),
(u':path', u'/'),
(u':scheme', u'https'),
(u':method', u'HEAD'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'HEAD'),
]

example_response_headers = [
(u':status', u'200'),
(u'server', u'fake-serv/0.1.0'),
(u'content_length', u'1'),
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0'),
(b'content_length', b'1'),
]

def test_non_zero_content_and_no_body(self, frame_factory):
Expand Down
38 changes: 19 additions & 19 deletions test/test_informational_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@ class TestReceivingInformationalResponses(object):
Tests for receiving informational responses.
"""
example_request_headers = [
(':authority', 'example.com'),
(':path', '/'),
(':scheme', 'https'),
(':method', 'GET'),
('expect', '100-continue'),
(b':authority', b'example.com'),
(b':path', b'/'),
(b':scheme', b'https'),
(b':method', b'GET'),
(b'expect', b'100-continue'),
]
example_informational_headers = [
(':status', '100'),
('server', 'fake-serv/0.1.0')
(b':status', b'100'),
(b'server', b'fake-serv/0.1.0')
]
example_response_headers = [
(':status', '200'),
('server', 'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]
example_trailers = [
('trailer', 'you-bet'),
(b'trailer', b'you-bet'),
]

@pytest.mark.parametrize('end_stream', (True, False))
Expand Down Expand Up @@ -141,7 +141,7 @@ def test_receiving_multiple_informational_responses(self,
assert events[0].stream_id == 1

assert isinstance(events[1], h2.events.InformationalResponseReceived)
assert events[1].headers == [(':status', '101')]
assert events[1].headers == [(b':status', b'101')]
assert events[1].stream_id == 1

@pytest.mark.parametrize('end_stream', (True, False))
Expand Down Expand Up @@ -216,11 +216,11 @@ class TestSendingInformationalResponses(object):
Tests for sending informational responses.
"""
example_request_headers = [
(':authority', 'example.com'),
(':path', '/'),
(':scheme', 'https'),
(':method', 'GET'),
('expect', '100-continue'),
(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'),
Expand All @@ -231,11 +231,11 @@ class TestSendingInformationalResponses(object):
(b'server', b'fake-serv/0.1.0')
]
example_response_headers = [
(':status', '200'),
('server', 'fake-serv/0.1.0')
(b':status', b'200'),
(b'server', b'fake-serv/0.1.0')
]
example_trailers = [
('trailer', 'you-bet'),
(b'trailer', b'you-bet'),
]
server_config = h2.config.H2Configuration(client_side=False)

Expand Down
16 changes: 8 additions & 8 deletions test/test_interacting_stacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ def test_basic_request_response(self):
A request issued by hyper-h2 can be responded to by hyper-h2.
"""
request_headers = [
(':method', 'GET'),
(':path', '/'),
(':authority', 'example.com'),
(':scheme', 'https'),
('user-agent', 'test-client/0.1.0'),
(b':method', b'GET'),
(b':path', b'/'),
(b':authority', b'example.com'),
(b':scheme', b'https'),
(b'user-agent', b'test-client/0.1.0'),
]
response_headers = [
(':status', '204'),
('server', 'test-server/0.1.0'),
('content-length', '0'),
(b':status', b'204'),
(b'server', b'test-server/0.1.0'),
(b'content-length', b'0'),
]

def client():
Expand Down
11 changes: 8 additions & 3 deletions test/test_invalid_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ class TestInvalidFrameSequences(object):
[header for header in base_request_headers
if header[0] != ':authority'],
]
server_config = h2.config.H2Configuration(client_side=False)
server_config = h2.config.H2Configuration(
client_side=False, header_encoding='utf-8'
)

@pytest.mark.parametrize('headers', invalid_header_blocks)
def test_headers_event(self, frame_factory, headers):
Expand Down Expand Up @@ -113,7 +115,8 @@ def test_push_promise_skipping_validation(self, frame_factory, headers):
"""
config = h2.config.H2Configuration(
client_side=True,
validate_inbound_headers=False
validate_inbound_headers=False,
header_encoding='utf-8'
)

c = h2.connection.H2Connection(config=config)
Expand Down Expand Up @@ -143,7 +146,9 @@ def test_headers_event_skipping_validation(self, frame_factory, headers):
"""
config = h2.config.H2Configuration(
client_side=False,
validate_inbound_headers=False)
validate_inbound_headers=False,
header_encoding='utf-8'
)

c = h2.connection.H2Connection(config=config)
c.receive_data(frame_factory.preamble())
Expand Down
Loading