|
8 | 8 | from hpack.hpack_compat import Encoder
|
9 | 9 | from hyper.http20.connection import HTTP20Connection
|
10 | 10 | from hyper.http20.response import HTTP20Response, HTTP20Push
|
11 |
| -from hyper.http20.exceptions import ConnectionError |
| 11 | +from hyper.http20.exceptions import ConnectionError, StreamResetError |
12 | 12 | from hyper.http20.util import (
|
13 | 13 | combine_repeated_headers, split_repeated_headers, h2_safe_headers
|
14 | 14 | )
|
|
23 | 23 | import zlib
|
24 | 24 | from io import BytesIO
|
25 | 25 |
|
26 |
| - |
27 | 26 | TEST_DIR = os.path.abspath(os.path.dirname(__file__))
|
28 | 27 | TEST_CERTS_DIR = os.path.join(TEST_DIR, 'certs')
|
29 | 28 | CLIENT_PEM_FILE = os.path.join(TEST_CERTS_DIR, 'nopassword.pem')
|
@@ -293,6 +292,100 @@ def test_streams_are_cleared_from_connections_on_close(self):
|
293 | 292 | assert not c.streams
|
294 | 293 | assert c.next_stream_id == 3
|
295 | 294 |
|
| 295 | + def test_streams_raise_error_on_read_after_close(self): |
| 296 | + # Prepare a socket so we can open a stream. |
| 297 | + sock = DummySocket() |
| 298 | + c = HTTP20Connection('www.google.com') |
| 299 | + c._sock = sock |
| 300 | + |
| 301 | + # Open a request (which creates a stream) |
| 302 | + stream_id = c.request('GET', '/') |
| 303 | + |
| 304 | + # close connection |
| 305 | + c.close() |
| 306 | + |
| 307 | + # try to read the stream |
| 308 | + with pytest.raises(StreamResetError): |
| 309 | + c.get_response(stream_id) |
| 310 | + |
| 311 | + def test_reads_on_remote_close(self): |
| 312 | + # Prepare a socket so we can open a stream. |
| 313 | + sock = DummySocket() |
| 314 | + c = HTTP20Connection('www.google.com') |
| 315 | + c._sock = sock |
| 316 | + |
| 317 | + # Open a few requests (which creates a stream) |
| 318 | + s1 = c.request('GET', '/') |
| 319 | + s2 = c.request('GET', '/') |
| 320 | + |
| 321 | + # simulate state of blocking on read while sock |
| 322 | + f = GoAwayFrame(0) |
| 323 | + # Set error code to PROTOCOL_ERROR |
| 324 | + f.error_code = 1 |
| 325 | + c._sock.buffer = BytesIO(f.serialize()) |
| 326 | + |
| 327 | + # 'Receive' the GOAWAY frame. |
| 328 | + # Validate that the spec error name and description are used to throw |
| 329 | + # the connection exception. |
| 330 | + with pytest.raises(ConnectionError): |
| 331 | + c.get_response(s1) |
| 332 | + |
| 333 | + # try to read the stream |
| 334 | + with pytest.raises(StreamResetError): |
| 335 | + c.get_response(s2) |
| 336 | + |
| 337 | + def test_race_condition_on_socket_close(self): |
| 338 | + # Prepare a socket so we can open a stream. |
| 339 | + sock = DummySocket() |
| 340 | + c = HTTP20Connection('www.google.com') |
| 341 | + c._sock = sock |
| 342 | + |
| 343 | + # Open a few requests (which creates a stream) |
| 344 | + s1 = c.request('GET', '/') |
| 345 | + c.request('GET', '/') |
| 346 | + |
| 347 | + # simulate state of blocking on read while sock |
| 348 | + f = GoAwayFrame(0) |
| 349 | + # Set error code to PROTOCOL_ERROR |
| 350 | + f.error_code = 1 |
| 351 | + c._sock.buffer = BytesIO(f.serialize()) |
| 352 | + |
| 353 | + # 'Receive' the GOAWAY frame. |
| 354 | + # Validate that the spec error name and description are used to throw |
| 355 | + # the connection exception. |
| 356 | + with pytest.raises(ConnectionError): |
| 357 | + c.get_response(s1) |
| 358 | + |
| 359 | + # try to read again after close |
| 360 | + with pytest.raises(ConnectionError): |
| 361 | + c._single_read() |
| 362 | + |
| 363 | + def test_stream_close_behavior(self): |
| 364 | + # Prepare a socket so we can open a stream. |
| 365 | + sock = DummySocket() |
| 366 | + c = HTTP20Connection('www.google.com') |
| 367 | + c._sock = sock |
| 368 | + |
| 369 | + # Open a few requests (which creates a stream) |
| 370 | + s1 = c.request('GET', '/') |
| 371 | + c.request('GET', '/') |
| 372 | + |
| 373 | + # simulate state of blocking on read while sock |
| 374 | + f = GoAwayFrame(0) |
| 375 | + # Set error code to PROTOCOL_ERROR |
| 376 | + f.error_code = 1 |
| 377 | + c._sock.buffer = BytesIO(f.serialize()) |
| 378 | + |
| 379 | + # 'Receive' the GOAWAY frame. |
| 380 | + # Validate that the spec error name and description are used to throw |
| 381 | + # the connection exception. |
| 382 | + with pytest.raises(ConnectionError): |
| 383 | + c.get_response(s1) |
| 384 | + |
| 385 | + # try to read again after close |
| 386 | + with pytest.raises(ConnectionError): |
| 387 | + c._single_read() |
| 388 | + |
296 | 389 | def test_read_headers_out_of_order(self):
|
297 | 390 | # If header blocks aren't decoded in the same order they're received,
|
298 | 391 | # regardless of the stream they belong to, the decoder state will
|
@@ -326,10 +419,10 @@ def test_headers_with_continuation(self):
|
326 | 419 | ('content-length', '0')
|
327 | 420 | ])
|
328 | 421 | h = HeadersFrame(1)
|
329 |
| - h.data = header_data[0:int(len(header_data)/2)] |
| 422 | + h.data = header_data[0:int(len(header_data) / 2)] |
330 | 423 | h.flags.add('END_STREAM')
|
331 | 424 | c = ContinuationFrame(1)
|
332 |
| - c.data = header_data[int(len(header_data)/2):] |
| 425 | + c.data = header_data[int(len(header_data) / 2):] |
333 | 426 | c.flags.add('END_HEADERS')
|
334 | 427 | sock = DummySocket()
|
335 | 428 | sock.buffer = BytesIO(h.serialize() + c.serialize())
|
@@ -883,7 +976,7 @@ def test_read_compressed_frames(self):
|
883 | 976 | body += c.flush()
|
884 | 977 |
|
885 | 978 | stream = DummyStream(None)
|
886 |
| - chunks = [body[x:x+2] for x in range(0, len(body), 2)] |
| 979 | + chunks = [body[x:x + 2] for x in range(0, len(body), 2)] |
887 | 980 | stream.data_frames = chunks
|
888 | 981 | resp = HTTP20Response(headers, stream)
|
889 | 982 |
|
|
0 commit comments