|
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
|
@@ -731,8 +732,8 @@ 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 | + def request(self, enable_push=True): |
| 736 | + self.conn = HTTP20Connection('www.google.com', enable_push=enable_push) |
736 | 737 | self.conn._sock = DummySocket()
|
737 | 738 | self.conn._sock.buffer = BytesIO(
|
738 | 739 | b''.join([frame.serialize() for frame in self.frames])
|
@@ -934,8 +935,7 @@ def test_reset_pushed_streams_when_push_disabled(self):
|
934 | 935 | 1, [(':status', '200'), ('content-type', 'text/html')]
|
935 | 936 | )
|
936 | 937 |
|
937 |
| - self.request() |
938 |
| - self.conn._enable_push = False |
| 938 | + self.request(False) |
939 | 939 | self.conn.get_response()
|
940 | 940 |
|
941 | 941 | f = RstStreamFrame(2)
|
@@ -1303,6 +1303,188 @@ def test_resetting_streams_after_close(self):
|
1303 | 1303 | c._single_read()
|
1304 | 1304 |
|
1305 | 1305 |
|
| 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 | + |
1306 | 1488 | # Some utility classes for the tests.
|
1307 | 1489 | class NullEncoder(object):
|
1308 | 1490 | @staticmethod
|
|
0 commit comments