Skip to content
This repository has been archived by the owner on Jan 13, 2021. It is now read-only.

Add ENABLE_PUSH flag in the Upgrade HTTP2-Settings header #311

Merged
merged 5 commits into from
Feb 24, 2017
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
3 changes: 2 additions & 1 deletion hyper/common/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def __init__(self,
self._port = port
self._h1_kwargs = {
'secure': secure, 'ssl_context': ssl_context,
'proxy_host': proxy_host, 'proxy_port': proxy_port
'proxy_host': proxy_host, 'proxy_port': proxy_port,
'enable_push': enable_push
}
self._h2_kwargs = {
'window_manager': window_manager, 'enable_push': enable_push,
Expand Down
5 changes: 5 additions & 0 deletions hyper/http11/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def __init__(self, host, port=None, secure=None, ssl_context=None,

# only send http upgrade headers for non-secure connection
self._send_http_upgrade = not self.secure
self._enable_push = kwargs.get('enable_push')

self.ssl_context = ssl_context
self._sock = None
Expand Down Expand Up @@ -276,6 +277,10 @@ def _add_upgrade_headers(self, headers):
# Settings header.
http2_settings = SettingsFrame(0)
http2_settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535
if self._enable_push is not None:
http2_settings.settings[SettingsFrame.ENABLE_PUSH] = (
int(self._enable_push)
)
encoded_settings = base64.urlsafe_b64encode(
http2_settings.serialize_body()
)
Expand Down
1 change: 1 addition & 0 deletions test/test_abstraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_h1_kwargs(self):
'proxy_host': False,
'proxy_port': False,
'other_kwarg': True,
'enable_push': True,
}

def test_h2_kwargs(self):
Expand Down
164 changes: 159 additions & 5 deletions test/test_hyper.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
PingFrame, FRAME_MAX_ALLOWED_LEN
)
from hpack.hpack_compat import Encoder
from hyper.common.connection import HTTPConnection
from hyper.http20.connection import HTTP20Connection
from hyper.http20.response import HTTP20Response, HTTP20Push
from hyper.http20.exceptions import ConnectionError, StreamResetError
Expand Down Expand Up @@ -699,7 +700,7 @@ def test_incrementing_window_after_close(self):
assert len(originally_sent_data) + 1 == len(c._sock.queue)


class TestServerPush(object):
class FrameEncoderMixin(object):
def setup_method(self, method):
self.frames = []
self.encoder = Encoder()
Expand Down Expand Up @@ -731,8 +732,10 @@ def add_data_frame(self, stream_id, data, end_stream=False):
frame.flags.add('END_STREAM')
self.frames.append(frame)

def request(self):
self.conn = HTTP20Connection('www.google.com', enable_push=True)

class TestServerPush(FrameEncoderMixin):
def request(self, enable_push=True):
self.conn = HTTP20Connection('www.google.com', enable_push=enable_push)
self.conn._sock = DummySocket()
self.conn._sock.buffer = BytesIO(
b''.join([frame.serialize() for frame in self.frames])
Expand Down Expand Up @@ -934,8 +937,7 @@ def test_reset_pushed_streams_when_push_disabled(self):
1, [(':status', '200'), ('content-type', 'text/html')]
)

self.request()
self.conn._enable_push = False
self.request(False)
self.conn.get_response()

f = RstStreamFrame(2)
Expand Down Expand Up @@ -1303,6 +1305,158 @@ def test_resetting_streams_after_close(self):
c._single_read()


class TestUpgradingPush(FrameEncoderMixin):
http101 = (b"HTTP/1.1 101 Switching Protocols\r\n"
b"Connection: upgrade\r\n"
b"Upgrade: h2c\r\n"
b"\r\n")

def request(self, enable_push=True):
self.frames = [SettingsFrame(0)] + self.frames # Server side preface
self.conn = HTTPConnection('www.google.com', enable_push=enable_push)
self.conn._conn._sock = DummySocket()
self.conn._conn._sock.buffer = BytesIO(
self.http101 + b''.join([frame.serialize()
for frame in self.frames])
)
self.conn.request('GET', '/')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should do any asserting that the Upgrade header is present?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upgrading process itself is tested at test_http11.py, and IMHO, we would focus on client side view here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fair enough.


def assert_response(self):
self.response = self.conn.get_response()
assert self.response.status == 200
assert dict(self.response.headers) == {b'content-type': [b'text/html']}

def assert_pushes(self):
self.pushes = list(self.conn.get_pushes())
assert len(self.pushes) == 1
assert self.pushes[0].method == b'GET'
assert self.pushes[0].scheme == b'http'
assert self.pushes[0].authority == b'www.google.com'
assert self.pushes[0].path == b'/'
expected_headers = {b'accept-encoding': [b'gzip']}
assert dict(self.pushes[0].request_headers) == expected_headers

def assert_push_response(self):
push_response = self.pushes[0].get_response()
assert push_response.status == 200
assert dict(push_response.headers) == {
b'content-type': [b'application/javascript']
}
assert push_response.read() == b'bar'

def test_promise_before_headers(self):
# Current implementation only support get_pushes call
# after get_response
pass

def test_promise_after_headers(self):
self.add_headers_frame(
1, [(':status', '200'), ('content-type', 'text/html')]
)
self.add_push_frame(
1,
2,
[
(':method', 'GET'),
(':path', '/'),
(':authority', 'www.google.com'),
(':scheme', 'http'),
('accept-encoding', 'gzip')
]
)
self.add_data_frame(1, b'foo', end_stream=True)
self.add_headers_frame(
2, [(':status', '200'), ('content-type', 'application/javascript')]
)
self.add_data_frame(2, b'bar', end_stream=True)

self.request()
self.assert_response()
assert self.response.read() == b'foo'
self.assert_pushes()
self.assert_push_response()

def test_promise_after_data(self):
self.add_headers_frame(
1, [(':status', '200'), ('content-type', 'text/html')]
)
self.add_data_frame(1, b'fo')
self.add_push_frame(
1,
2,
[
(':method', 'GET'),
(':path', '/'),
(':authority', 'www.google.com'),
(':scheme', 'http'),
('accept-encoding', 'gzip')
]
)
self.add_data_frame(1, b'o', end_stream=True)
self.add_headers_frame(
2, [(':status', '200'), ('content-type', 'application/javascript')]
)
self.add_data_frame(2, b'bar', end_stream=True)

self.request()
self.assert_response()
assert self.response.read() == b'foo'
self.assert_pushes()
self.assert_push_response()

def test_capture_all_promises(self):
# Current implementation does not support capture_all
# for h2c upgrading connection.
pass

def test_cancel_push(self):
self.add_push_frame(
1,
2,
[
(':method', 'GET'),
(':path', '/'),
(':authority', 'www.google.com'),
(':scheme', 'http'),
('accept-encoding', 'gzip')
]
)
self.add_headers_frame(
1, [(':status', '200'), ('content-type', 'text/html')]
)

self.request()
self.conn.get_response()
list(self.conn.get_pushes())[0].cancel()

f = RstStreamFrame(2)
f.error_code = 8
assert self.conn._sock.queue[-1] == f.serialize()

def test_reset_pushed_streams_when_push_disabled(self):
self.add_push_frame(
1,
2,
[
(':method', 'GET'),
(':path', '/'),
(':authority', 'www.google.com'),
(':scheme', 'http'),
('accept-encoding', 'gzip')
]
)
self.add_headers_frame(
1, [(':status', '200'), ('content-type', 'text/html')]
)

self.request(False)
self.conn.get_response()

f = RstStreamFrame(2)
f.error_code = 7
assert self.conn._sock.queue[-1].endswith(f.serialize())


# Some utility classes for the tests.
class NullEncoder(object):
@staticmethod
Expand Down
Loading