Repository: certbot/certbot

Issue:
Connectionpool not pooling
When I run with `-v`, I see entries like this repeatedly:

2016-04-07 05:23:55,283:INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
2016-04-07 05:23:58,509:INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org

This probably means that the connectionpool is not actually reusing connections properly, which makes the process slower than it should be.



Discussion:

Resolving patch:
diff --git a/acme/acme/client.py b/acme/acme/client.py
--- a/acme/acme/client.py
+++ b/acme/acme/client.py
@@ -512,6 +512,10 @@ def __init__(self, key, alg=jose.RS256, verify_ssl=True,
         self.verify_ssl = verify_ssl
         self._nonces = set()
         self.user_agent = user_agent
+        self.session = requests.Session()
+
+    def __del__(self):
+        self.session.close()
 
     def _wrap_in_jws(self, obj, nonce):
         """Wrap `JSONDeSerializable` object in JWS.
@@ -606,7 +610,7 @@ def _send_request(self, method, url, *args, **kwargs):
         kwargs['verify'] = self.verify_ssl
         kwargs.setdefault('headers', {})
         kwargs['headers'].setdefault('User-Agent', self.user_agent)
-        response = requests.request(method, url, *args, **kwargs)
+        response = self.session.request(method, url, *args, **kwargs)
         logging.debug('Received %s. Headers: %s. Content: %r',
                       response, response.headers, response.content)
         return response


Test patch:
diff --git a/acme/acme/client_test.py b/acme/acme/client_test.py
--- a/acme/acme/client_test.py
+++ b/acme/acme/client_test.py
@@ -528,40 +528,49 @@ def test_check_response_jobj(self):
             self.assertEqual(
                 self.response, self.net._check_response(self.response))
 
-    @mock.patch('acme.client.requests')
-    def test_send_request(self, mock_requests):
-        mock_requests.request.return_value = self.response
+    def test_send_request(self):
+        self.net.session = mock.MagicMock()
+        self.net.session.request.return_value = self.response
         # pylint: disable=protected-access
         self.assertEqual(self.response, self.net._send_request(
-            'HEAD', 'url', 'foo', bar='baz'))
-        mock_requests.request.assert_called_once_with(
-            'HEAD', 'url', 'foo', verify=mock.ANY, bar='baz', headers=mock.ANY)
+            'HEAD', 'http://example.com/', 'foo', bar='baz'))
+        self.net.session.request.assert_called_once_with(
+            'HEAD', 'http://example.com/', 'foo',
+            headers=mock.ANY, verify=mock.ANY, bar='baz')
 
-    @mock.patch('acme.client.requests')
-    def test_send_request_verify_ssl(self, mock_requests):
+    def test_send_request_verify_ssl(self):
         # pylint: disable=protected-access
         for verify in True, False:
-            mock_requests.request.reset_mock()
-            mock_requests.request.return_value = self.response
+            self.net.session = mock.MagicMock()
+            self.net.session.request.return_value = self.response
             self.net.verify_ssl = verify
             # pylint: disable=protected-access
             self.assertEqual(
-                self.response, self.net._send_request('GET', 'url'))
-            mock_requests.request.assert_called_once_with(
-                'GET', 'url', verify=verify, headers=mock.ANY)
+                self.response,
+                self.net._send_request('GET', 'http://example.com/'))
+            self.net.session.request.assert_called_once_with(
+                'GET', 'http://example.com/', verify=verify, headers=mock.ANY)
 
-    @mock.patch('acme.client.requests')
-    def test_send_request_user_agent(self, mock_requests):
-        mock_requests.request.return_value = self.response
+    def test_send_request_user_agent(self):
+        self.net.session = mock.MagicMock()
         # pylint: disable=protected-access
-        self.net._send_request('GET', 'url', headers={'bar': 'baz'})
-        mock_requests.request.assert_called_once_with(
-            'GET', 'url', verify=mock.ANY,
+        self.net._send_request('GET', 'http://example.com/',
+                               headers={'bar': 'baz'})
+        self.net.session.request.assert_called_once_with(
+            'GET', 'http://example.com/', verify=mock.ANY,
             headers={'User-Agent': 'acme-python-test', 'bar': 'baz'})
 
-        self.net._send_request('GET', 'url', headers={'User-Agent': 'foo2'})
-        mock_requests.request.assert_called_with(
-            'GET', 'url', verify=mock.ANY, headers={'User-Agent': 'foo2'})
+        self.net._send_request('GET', 'http://example.com/',
+                               headers={'User-Agent': 'foo2'})
+        self.net.session.request.assert_called_with(
+            'GET', 'http://example.com/',
+            verify=mock.ANY, headers={'User-Agent': 'foo2'})
+
+    def test_del(self):
+        sess = mock.MagicMock()
+        self.net.session = sess
+        del self.net
+        sess.close.assert_called_once()
 
     @mock.patch('acme.client.requests')
     def test_requests_error_passthrough(self, mock_requests):
@@ -614,14 +623,16 @@ def check_response(self, response, content_type):
         return self.checked_response
 
     def test_head(self):
-        self.assertEqual(self.response, self.net.head('url', 'foo', bar='baz'))
+        self.assertEqual(self.response, self.net.head(
+            'http://example.com/', 'foo', bar='baz'))
         self.send_request.assert_called_once_with(
-            'HEAD', 'url', 'foo', bar='baz')
+            'HEAD', 'http://example.com/', 'foo', bar='baz')
 
     def test_get(self):
         self.assertEqual(self.checked_response, self.net.get(
-            'url', content_type=self.content_type, bar='baz'))
-        self.send_request.assert_called_once_with('GET', 'url', bar='baz')
+            'http://example.com/', content_type=self.content_type, bar='baz'))
+        self.send_request.assert_called_once_with(
+            'GET', 'http://example.com/', bar='baz')
 
     def test_post(self):
         # pylint: disable=protected-access