|
9 | 9 | PingFrame, FRAME_MAX_ALLOWED_LEN
|
10 | 10 | )
|
11 | 11 | from hpack.hpack_compat import Encoder
|
| 12 | +from hyper.common.connection import HTTPConnection |
12 | 13 | from hyper.http20.connection import HTTP20Connection
|
13 | 14 | from hyper.http20.response import HTTP20Response, HTTP20Push
|
14 | 15 | from hyper.http20.exceptions import ConnectionError, StreamResetError
|
@@ -699,7 +700,7 @@ def test_incrementing_window_after_close(self):
|
699 | 700 | assert len(originally_sent_data) + 1 == len(c._sock.queue)
|
700 | 701 |
|
701 | 702 |
|
702 |
| -class TestServerPush(object): |
| 703 | +class FrameEncoderMixin(object): |
703 | 704 | def setup_method(self, method):
|
704 | 705 | self.frames = []
|
705 | 706 | self.encoder = Encoder()
|
@@ -731,8 +732,10 @@ def add_data_frame(self, stream_id, data, end_stream=False):
|
731 | 732 | frame.flags.add('END_STREAM')
|
732 | 733 | self.frames.append(frame)
|
733 | 734 |
|
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) |
736 | 739 | self.conn._sock = DummySocket()
|
737 | 740 | self.conn._sock.buffer = BytesIO(
|
738 | 741 | b''.join([frame.serialize() for frame in self.frames])
|
@@ -934,8 +937,7 @@ def test_reset_pushed_streams_when_push_disabled(self):
|
934 | 937 | 1, [(':status', '200'), ('content-type', 'text/html')]
|
935 | 938 | )
|
936 | 939 |
|
937 |
| - self.request() |
938 |
| - self.conn._enable_push = False |
| 940 | + self.request(False) |
939 | 941 | self.conn.get_response()
|
940 | 942 |
|
941 | 943 | f = RstStreamFrame(2)
|
@@ -1303,6 +1305,158 @@ def test_resetting_streams_after_close(self):
|
1303 | 1305 | c._single_read()
|
1304 | 1306 |
|
1305 | 1307 |
|
| 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 | + |
1306 | 1460 | # Some utility classes for the tests.
|
1307 | 1461 | class NullEncoder(object):
|
1308 | 1462 | @staticmethod
|
|
0 commit comments