From 4d292abc1984af220ddf05bdd4e86df0bf2cc699 Mon Sep 17 00:00:00 2001 From: Allan Lei Date: Sat, 11 Feb 2017 14:31:24 +0800 Subject: [PATCH 1/4] Implement HTTP20Adapter.close to close connections --- hyper/contrib.py | 4 ++++ test/test_hyper.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/hyper/contrib.py b/hyper/contrib.py index 5a580f29..fa96aedb 100644 --- a/hyper/contrib.py +++ b/hyper/contrib.py @@ -196,3 +196,7 @@ def getheaders(self, name): orig.msg = FakeOriginalResponse(resp.headers.iter_raw()) return response + + def close(self): + for connection in self.connections.values(): + connection._conn.close() diff --git a/test/test_hyper.py b/test/test_hyper.py index f4a5994d..3c254b84 100644 --- a/test/test_hyper.py +++ b/test/test_hyper.py @@ -27,6 +27,7 @@ import socket import zlib from io import BytesIO +import requests TEST_DIR = os.path.abspath(os.path.dirname(__file__)) TEST_CERTS_DIR = os.path.join(TEST_DIR, 'certs') @@ -1209,6 +1210,22 @@ def test_adapter_respects_custom_ca_verification(self): assert conn._conn.ssl_context.check_hostname assert conn._conn.ssl_context.verify_mode == ssl.CERT_REQUIRED + def test_adapter_close(self): + """ + Tests HTTP20Adapter properly closes connections + """ + s = requests.Session() + s.mount('https://', HTTP20Adapter()) + s.close() + + def test_adapter_close_context_manager(self): + """ + Tests HTTP20Adapter properly closes connections via context manager + """ + with requests.Session() as s: + a = HTTP20Adapter() + s.mount('https://', a) + class TestUtilities(object): def test_combining_repeated_headers(self): From 92bac67426f28bc251c2f0a7370e9eacd423bba6 Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Mon, 31 Jul 2017 15:28:58 +0300 Subject: [PATCH 2/4] Make requests adapter close() tests integrational --- hyper/contrib.py | 3 +- test/test_hyper.py | 17 --------- test/test_integration.py | 77 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/hyper/contrib.py b/hyper/contrib.py index fa96aedb..ff4f8ff8 100644 --- a/hyper/contrib.py +++ b/hyper/contrib.py @@ -199,4 +199,5 @@ def getheaders(self, name): def close(self): for connection in self.connections.values(): - connection._conn.close() + connection.close() + self.connections.clear() diff --git a/test/test_hyper.py b/test/test_hyper.py index 3c254b84..f4a5994d 100644 --- a/test/test_hyper.py +++ b/test/test_hyper.py @@ -27,7 +27,6 @@ import socket import zlib from io import BytesIO -import requests TEST_DIR = os.path.abspath(os.path.dirname(__file__)) TEST_CERTS_DIR = os.path.join(TEST_DIR, 'certs') @@ -1210,22 +1209,6 @@ def test_adapter_respects_custom_ca_verification(self): assert conn._conn.ssl_context.check_hostname assert conn._conn.ssl_context.verify_mode == ssl.CERT_REQUIRED - def test_adapter_close(self): - """ - Tests HTTP20Adapter properly closes connections - """ - s = requests.Session() - s.mount('https://', HTTP20Adapter()) - s.close() - - def test_adapter_close_context_manager(self): - """ - Tests HTTP20Adapter properly closes connections via context manager - """ - with requests.Session() as s: - a = HTTP20Adapter() - s.mount('https://', a) - class TestUtilities(object): def test_combining_repeated_headers(self): diff --git a/test/test_integration.py b/test/test_integration.py index bde7d393..e2976782 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1717,3 +1717,80 @@ def socket_handler(listener): timeout=(10, 0.5)) self.tear_down() + + def test_adapter_close(self): + self.set_up(secure=False) + + def socket_handler(listener): + sock = listener.accept()[0] + + # We should get the initial request. + data = b'' + while not data.endswith(b'\r\n\r\n'): + data += sock.recv(65535) + + # We need to send back a response. + resp = ( + b'HTTP/1.1 201 No Content\r\n' + b'Server: socket-level-server\r\n' + b'Content-Length: 0\r\n' + b'Connection: close\r\n' + b'\r\n' + ) + sock.send(resp) + sock.close() + + self._start_server(socket_handler) + + s = requests.Session() + s.mount('http://', HTTP20Adapter()) + r = s.get('http://%s:%s' % (self.host, self.port)) + s.close() + + assert r.status_code == 201 + assert len(r.headers) == 3 + assert r.headers['server'] == 'socket-level-server' + assert r.headers['content-length'] == '0' + assert r.headers['connection'] == 'close' + + assert r.content == b'' + + self.tear_down() + + def test_adapter_close_context_manager(self): + self.set_up(secure=False) + + def socket_handler(listener): + sock = listener.accept()[0] + + # We should get the initial request. + data = b'' + while not data.endswith(b'\r\n\r\n'): + data += sock.recv(65535) + + # We need to send back a response. + resp = ( + b'HTTP/1.1 201 No Content\r\n' + b'Server: socket-level-server\r\n' + b'Content-Length: 0\r\n' + b'Connection: close\r\n' + b'\r\n' + ) + sock.send(resp) + sock.close() + + self._start_server(socket_handler) + + with requests.Session() as s: + s.mount('http://', HTTP20Adapter()) + r = s.get('http://%s:%s' % (self.host, self.port)) + + assert r.status_code == 201 + assert len(r.headers) == 3 + assert r.headers['server'] == 'socket-level-server' + assert r.headers['content-length'] == '0' + assert r.headers['connection'] == 'close' + + assert r.content == b'' + + self.tear_down() From 8587eb363de5ae330aa4172fa2e9b49fbb8f63c1 Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Mon, 31 Jul 2017 18:35:27 +0300 Subject: [PATCH 3/4] Ensure that connections are actually closed on closing requests adapter --- test/test_integration.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/test_integration.py b/test/test_integration.py index e2976782..968c6fa3 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1742,11 +1742,20 @@ def socket_handler(listener): self._start_server(socket_handler) + a = HTTP20Adapter() s = requests.Session() - s.mount('http://', HTTP20Adapter()) + s.mount('http://', a) r = s.get('http://%s:%s' % (self.host, self.port)) + connections_before_close = list(a.connections.values()) s.close() + # check that connections cache is empty + assert not a.connections + + # check that all connections are actually closed + assert (connections_before_close and + all(conn._sock is None for conn in connections_before_close)) + assert r.status_code == 201 assert len(r.headers) == 3 assert r.headers['server'] == 'socket-level-server' @@ -1782,8 +1791,17 @@ def socket_handler(listener): self._start_server(socket_handler) with requests.Session() as s: - s.mount('http://', HTTP20Adapter()) + a = HTTP20Adapter() + s.mount('http://', a) r = s.get('http://%s:%s' % (self.host, self.port)) + connections_before_close = list(a.connections.values()) + + # check that connections cache is empty + assert not a.connections + + # check that all connections are actually closed + assert (connections_before_close and + all(conn._sock is None for conn in connections_before_close)) assert r.status_code == 201 assert len(r.headers) == 3 From 9a4910637fa2cd46326564c8d69e278a9ac35264 Mon Sep 17 00:00:00 2001 From: Kostya Esmukov Date: Mon, 31 Jul 2017 18:46:26 +0300 Subject: [PATCH 4/4] Split an assertion in requests adapter close() tests --- test/test_integration.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/test_integration.py b/test/test_integration.py index 968c6fa3..6a9ece42 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1747,14 +1747,17 @@ def socket_handler(listener): s.mount('http://', a) r = s.get('http://%s:%s' % (self.host, self.port)) connections_before_close = list(a.connections.values()) + + # ensure that we have at least 1 connection + assert connections_before_close + s.close() # check that connections cache is empty assert not a.connections # check that all connections are actually closed - assert (connections_before_close and - all(conn._sock is None for conn in connections_before_close)) + assert all(conn._sock is None for conn in connections_before_close) assert r.status_code == 201 assert len(r.headers) == 3 @@ -1796,12 +1799,14 @@ def socket_handler(listener): r = s.get('http://%s:%s' % (self.host, self.port)) connections_before_close = list(a.connections.values()) + # ensure that we have at least 1 connection + assert connections_before_close + # check that connections cache is empty assert not a.connections # check that all connections are actually closed - assert (connections_before_close and - all(conn._sock is None for conn in connections_before_close)) + assert all(conn._sock is None for conn in connections_before_close) assert r.status_code == 201 assert len(r.headers) == 3