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

Commit 04f3266

Browse files
authored
Merge pull request #311 from hkwi/push_setting_in_upgrade2
Add ENABLE_PUSH flag in the Upgrade HTTP2-Settings header
2 parents 28bfaba + 3a02fe1 commit 04f3266

File tree

5 files changed

+306
-76
lines changed

5 files changed

+306
-76
lines changed

hyper/common/connection.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def __init__(self,
6262
self._port = port
6363
self._h1_kwargs = {
6464
'secure': secure, 'ssl_context': ssl_context,
65-
'proxy_host': proxy_host, 'proxy_port': proxy_port
65+
'proxy_host': proxy_host, 'proxy_port': proxy_port,
66+
'enable_push': enable_push
6667
}
6768
self._h2_kwargs = {
6869
'window_manager': window_manager, 'enable_push': enable_push,

hyper/http11/connection.py

+5
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(self, host, port=None, secure=None, ssl_context=None,
7878

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

8283
self.ssl_context = ssl_context
8384
self._sock = None
@@ -276,6 +277,10 @@ def _add_upgrade_headers(self, headers):
276277
# Settings header.
277278
http2_settings = SettingsFrame(0)
278279
http2_settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535
280+
if self._enable_push is not None:
281+
http2_settings.settings[SettingsFrame.ENABLE_PUSH] = (
282+
int(self._enable_push)
283+
)
279284
encoded_settings = base64.urlsafe_b64encode(
280285
http2_settings.serialize_body()
281286
)

test/test_abstraction.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def test_h1_kwargs(self):
1919
'proxy_host': False,
2020
'proxy_port': False,
2121
'other_kwarg': True,
22+
'enable_push': True,
2223
}
2324

2425
def test_h2_kwargs(self):

test/test_hyper.py

+159-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
PingFrame, FRAME_MAX_ALLOWED_LEN
1010
)
1111
from hpack.hpack_compat import Encoder
12+
from hyper.common.connection import HTTPConnection
1213
from hyper.http20.connection import HTTP20Connection
1314
from hyper.http20.response import HTTP20Response, HTTP20Push
1415
from hyper.http20.exceptions import ConnectionError, StreamResetError
@@ -699,7 +700,7 @@ def test_incrementing_window_after_close(self):
699700
assert len(originally_sent_data) + 1 == len(c._sock.queue)
700701

701702

702-
class TestServerPush(object):
703+
class FrameEncoderMixin(object):
703704
def setup_method(self, method):
704705
self.frames = []
705706
self.encoder = Encoder()
@@ -731,8 +732,10 @@ def add_data_frame(self, stream_id, data, end_stream=False):
731732
frame.flags.add('END_STREAM')
732733
self.frames.append(frame)
733734

734-
def request(self):
735-
self.conn = HTTP20Connection('www.google.com', enable_push=True)
735+
736+
class TestServerPush(FrameEncoderMixin):
737+
def request(self, enable_push=True):
738+
self.conn = HTTP20Connection('www.google.com', enable_push=enable_push)
736739
self.conn._sock = DummySocket()
737740
self.conn._sock.buffer = BytesIO(
738741
b''.join([frame.serialize() for frame in self.frames])
@@ -934,8 +937,7 @@ def test_reset_pushed_streams_when_push_disabled(self):
934937
1, [(':status', '200'), ('content-type', 'text/html')]
935938
)
936939

937-
self.request()
938-
self.conn._enable_push = False
940+
self.request(False)
939941
self.conn.get_response()
940942

941943
f = RstStreamFrame(2)
@@ -1303,6 +1305,158 @@ def test_resetting_streams_after_close(self):
13031305
c._single_read()
13041306

13051307

1308+
class TestUpgradingPush(FrameEncoderMixin):
1309+
http101 = (b"HTTP/1.1 101 Switching Protocols\r\n"
1310+
b"Connection: upgrade\r\n"
1311+
b"Upgrade: h2c\r\n"
1312+
b"\r\n")
1313+
1314+
def request(self, enable_push=True):
1315+
self.frames = [SettingsFrame(0)] + self.frames # Server side preface
1316+
self.conn = HTTPConnection('www.google.com', enable_push=enable_push)
1317+
self.conn._conn._sock = DummySocket()
1318+
self.conn._conn._sock.buffer = BytesIO(
1319+
self.http101 + b''.join([frame.serialize()
1320+
for frame in self.frames])
1321+
)
1322+
self.conn.request('GET', '/')
1323+
1324+
def assert_response(self):
1325+
self.response = self.conn.get_response()
1326+
assert self.response.status == 200
1327+
assert dict(self.response.headers) == {b'content-type': [b'text/html']}
1328+
1329+
def assert_pushes(self):
1330+
self.pushes = list(self.conn.get_pushes())
1331+
assert len(self.pushes) == 1
1332+
assert self.pushes[0].method == b'GET'
1333+
assert self.pushes[0].scheme == b'http'
1334+
assert self.pushes[0].authority == b'www.google.com'
1335+
assert self.pushes[0].path == b'/'
1336+
expected_headers = {b'accept-encoding': [b'gzip']}
1337+
assert dict(self.pushes[0].request_headers) == expected_headers
1338+
1339+
def assert_push_response(self):
1340+
push_response = self.pushes[0].get_response()
1341+
assert push_response.status == 200
1342+
assert dict(push_response.headers) == {
1343+
b'content-type': [b'application/javascript']
1344+
}
1345+
assert push_response.read() == b'bar'
1346+
1347+
def test_promise_before_headers(self):
1348+
# Current implementation only support get_pushes call
1349+
# after get_response
1350+
pass
1351+
1352+
def test_promise_after_headers(self):
1353+
self.add_headers_frame(
1354+
1, [(':status', '200'), ('content-type', 'text/html')]
1355+
)
1356+
self.add_push_frame(
1357+
1,
1358+
2,
1359+
[
1360+
(':method', 'GET'),
1361+
(':path', '/'),
1362+
(':authority', 'www.google.com'),
1363+
(':scheme', 'http'),
1364+
('accept-encoding', 'gzip')
1365+
]
1366+
)
1367+
self.add_data_frame(1, b'foo', end_stream=True)
1368+
self.add_headers_frame(
1369+
2, [(':status', '200'), ('content-type', 'application/javascript')]
1370+
)
1371+
self.add_data_frame(2, b'bar', end_stream=True)
1372+
1373+
self.request()
1374+
self.assert_response()
1375+
assert self.response.read() == b'foo'
1376+
self.assert_pushes()
1377+
self.assert_push_response()
1378+
1379+
def test_promise_after_data(self):
1380+
self.add_headers_frame(
1381+
1, [(':status', '200'), ('content-type', 'text/html')]
1382+
)
1383+
self.add_data_frame(1, b'fo')
1384+
self.add_push_frame(
1385+
1,
1386+
2,
1387+
[
1388+
(':method', 'GET'),
1389+
(':path', '/'),
1390+
(':authority', 'www.google.com'),
1391+
(':scheme', 'http'),
1392+
('accept-encoding', 'gzip')
1393+
]
1394+
)
1395+
self.add_data_frame(1, b'o', end_stream=True)
1396+
self.add_headers_frame(
1397+
2, [(':status', '200'), ('content-type', 'application/javascript')]
1398+
)
1399+
self.add_data_frame(2, b'bar', end_stream=True)
1400+
1401+
self.request()
1402+
self.assert_response()
1403+
assert self.response.read() == b'foo'
1404+
self.assert_pushes()
1405+
self.assert_push_response()
1406+
1407+
def test_capture_all_promises(self):
1408+
# Current implementation does not support capture_all
1409+
# for h2c upgrading connection.
1410+
pass
1411+
1412+
def test_cancel_push(self):
1413+
self.add_push_frame(
1414+
1,
1415+
2,
1416+
[
1417+
(':method', 'GET'),
1418+
(':path', '/'),
1419+
(':authority', 'www.google.com'),
1420+
(':scheme', 'http'),
1421+
('accept-encoding', 'gzip')
1422+
]
1423+
)
1424+
self.add_headers_frame(
1425+
1, [(':status', '200'), ('content-type', 'text/html')]
1426+
)
1427+
1428+
self.request()
1429+
self.conn.get_response()
1430+
list(self.conn.get_pushes())[0].cancel()
1431+
1432+
f = RstStreamFrame(2)
1433+
f.error_code = 8
1434+
assert self.conn._sock.queue[-1] == f.serialize()
1435+
1436+
def test_reset_pushed_streams_when_push_disabled(self):
1437+
self.add_push_frame(
1438+
1,
1439+
2,
1440+
[
1441+
(':method', 'GET'),
1442+
(':path', '/'),
1443+
(':authority', 'www.google.com'),
1444+
(':scheme', 'http'),
1445+
('accept-encoding', 'gzip')
1446+
]
1447+
)
1448+
self.add_headers_frame(
1449+
1, [(':status', '200'), ('content-type', 'text/html')]
1450+
)
1451+
1452+
self.request(False)
1453+
self.conn.get_response()
1454+
1455+
f = RstStreamFrame(2)
1456+
f.error_code = 7
1457+
assert self.conn._sock.queue[-1].endswith(f.serialize())
1458+
1459+
13061460
# Some utility classes for the tests.
13071461
class NullEncoder(object):
13081462
@staticmethod

0 commit comments

Comments
 (0)