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

Commit 7aa17fd

Browse files
committed
Add ENABLE_PUSH flag in the Upgrade HTTP2-Settings header
Push flag required for the case the initial upgrade request triggered server push.
1 parent 28bfaba commit 7aa17fd

File tree

4 files changed

+194
-5
lines changed

4 files changed

+194
-5
lines changed

hyper/common/connection.py

Lines changed: 2 additions & 1 deletion
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 186 additions & 4 deletions
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
@@ -731,8 +732,8 @@ 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+
def request(self, enable_push=True):
736+
self.conn = HTTP20Connection('www.google.com', enable_push=enable_push)
736737
self.conn._sock = DummySocket()
737738
self.conn._sock.buffer = BytesIO(
738739
b''.join([frame.serialize() for frame in self.frames])
@@ -934,8 +935,7 @@ def test_reset_pushed_streams_when_push_disabled(self):
934935
1, [(':status', '200'), ('content-type', 'text/html')]
935936
)
936937

937-
self.request()
938-
self.conn._enable_push = False
938+
self.request(False)
939939
self.conn.get_response()
940940

941941
f = RstStreamFrame(2)
@@ -1303,6 +1303,188 @@ def test_resetting_streams_after_close(self):
13031303
c._single_read()
13041304

13051305

1306+
class TestUpgradingPush(object):
1307+
http101 = (b"HTTP/1.1 101 Switching Protocols\r\n"
1308+
b"Connection: upgrade\r\n"
1309+
b"Upgrade: h2c\r\n"
1310+
b"\r\n")
1311+
1312+
def setup_method(self, method):
1313+
self.frames = [SettingsFrame(0)] # Server side preface
1314+
self.encoder = Encoder()
1315+
self.conn = None
1316+
1317+
def add_push_frame(self, stream_id, promised_stream_id, headers,
1318+
end_block=True):
1319+
frame = PushPromiseFrame(stream_id)
1320+
frame.promised_stream_id = promised_stream_id
1321+
frame.data = self.encoder.encode(headers)
1322+
if end_block:
1323+
frame.flags.add('END_HEADERS')
1324+
self.frames.append(frame)
1325+
1326+
def add_headers_frame(self, stream_id, headers, end_block=True,
1327+
end_stream=False):
1328+
frame = HeadersFrame(stream_id)
1329+
frame.data = self.encoder.encode(headers)
1330+
if end_block:
1331+
frame.flags.add('END_HEADERS')
1332+
if end_stream:
1333+
frame.flags.add('END_STREAM')
1334+
self.frames.append(frame)
1335+
1336+
def add_data_frame(self, stream_id, data, end_stream=False):
1337+
frame = DataFrame(stream_id)
1338+
frame.data = data
1339+
if end_stream:
1340+
frame.flags.add('END_STREAM')
1341+
self.frames.append(frame)
1342+
1343+
def request(self, enable_push=True):
1344+
self.conn = HTTPConnection('www.google.com', enable_push=enable_push)
1345+
self.conn._conn._sock = DummySocket()
1346+
self.conn._conn._sock.buffer = BytesIO(
1347+
self.http101 + b''.join([frame.serialize()
1348+
for frame in self.frames])
1349+
)
1350+
self.conn.request('GET', '/')
1351+
1352+
def assert_response(self):
1353+
self.response = self.conn.get_response()
1354+
assert self.response.status == 200
1355+
assert dict(self.response.headers) == {b'content-type': [b'text/html']}
1356+
1357+
def assert_pushes(self):
1358+
self.pushes = list(self.conn.get_pushes())
1359+
assert len(self.pushes) == 1
1360+
assert self.pushes[0].method == b'GET'
1361+
assert self.pushes[0].scheme == b'http'
1362+
assert self.pushes[0].authority == b'www.google.com'
1363+
assert self.pushes[0].path == b'/'
1364+
expected_headers = {b'accept-encoding': [b'gzip']}
1365+
assert dict(self.pushes[0].request_headers) == expected_headers
1366+
1367+
def assert_push_response(self):
1368+
push_response = self.pushes[0].get_response()
1369+
assert push_response.status == 200
1370+
assert dict(push_response.headers) == {
1371+
b'content-type': [b'application/javascript']
1372+
}
1373+
assert push_response.read() == b'bar'
1374+
1375+
def test_promise_before_headers(self):
1376+
# Current implementation only support get_pushes call
1377+
# after get_response
1378+
pass
1379+
1380+
def test_promise_after_headers(self):
1381+
self.add_headers_frame(
1382+
1, [(':status', '200'), ('content-type', 'text/html')]
1383+
)
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'foo', 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_promise_after_data(self):
1408+
self.add_headers_frame(
1409+
1, [(':status', '200'), ('content-type', 'text/html')]
1410+
)
1411+
self.add_data_frame(1, b'fo')
1412+
self.add_push_frame(
1413+
1,
1414+
2,
1415+
[
1416+
(':method', 'GET'),
1417+
(':path', '/'),
1418+
(':authority', 'www.google.com'),
1419+
(':scheme', 'http'),
1420+
('accept-encoding', 'gzip')
1421+
]
1422+
)
1423+
self.add_data_frame(1, b'o', end_stream=True)
1424+
self.add_headers_frame(
1425+
2, [(':status', '200'), ('content-type', 'application/javascript')]
1426+
)
1427+
self.add_data_frame(2, b'bar', end_stream=True)
1428+
1429+
self.request()
1430+
self.assert_response()
1431+
assert self.response.read() == b'foo'
1432+
self.assert_pushes()
1433+
self.assert_push_response()
1434+
1435+
def test_capture_all_promises(self):
1436+
# Current implementation does not support capture_all
1437+
# for h2c upgrading connection.
1438+
pass
1439+
1440+
def test_cancel_push(self):
1441+
self.add_push_frame(
1442+
1,
1443+
2,
1444+
[
1445+
(':method', 'GET'),
1446+
(':path', '/'),
1447+
(':authority', 'www.google.com'),
1448+
(':scheme', 'http'),
1449+
('accept-encoding', 'gzip')
1450+
]
1451+
)
1452+
self.add_headers_frame(
1453+
1, [(':status', '200'), ('content-type', 'text/html')]
1454+
)
1455+
1456+
self.request()
1457+
self.conn.get_response()
1458+
list(self.conn.get_pushes())[0].cancel()
1459+
1460+
f = RstStreamFrame(2)
1461+
f.error_code = 8
1462+
assert self.conn._sock.queue[-1] == f.serialize()
1463+
1464+
def test_reset_pushed_streams_when_push_disabled(self):
1465+
self.add_push_frame(
1466+
1,
1467+
2,
1468+
[
1469+
(':method', 'GET'),
1470+
(':path', '/'),
1471+
(':authority', 'www.google.com'),
1472+
(':scheme', 'http'),
1473+
('accept-encoding', 'gzip')
1474+
]
1475+
)
1476+
self.add_headers_frame(
1477+
1, [(':status', '200'), ('content-type', 'text/html')]
1478+
)
1479+
1480+
self.request(False)
1481+
self.conn.get_response()
1482+
1483+
f = RstStreamFrame(2)
1484+
f.error_code = 7
1485+
assert self.conn._sock.queue[-1].endswith(f.serialize())
1486+
1487+
13061488
# Some utility classes for the tests.
13071489
class NullEncoder(object):
13081490
@staticmethod

0 commit comments

Comments
 (0)