diff --git a/awscli/botocore/awsrequest.py b/awscli/botocore/awsrequest.py index 4ce0449..2b14009 100644 --- a/awscli/botocore/awsrequest.py +++ b/awscli/botocore/awsrequest.py @@ -14,6 +14,7 @@ import functools import io import logging +from collections.abc import Mapping import socket import sys @@ -24,6 +25,7 @@ from botocore.compat import ( HTTPResponse, MutableMapping, urlencode, + urlparse, urlsplit, urlunsplit, ) @@ -66,32 +68,34 @@ class AWSConnection: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._original_response_cls = self.response_class - # We'd ideally hook into httplib's states, but they're all - # __mangled_vars so we use our own state var. This variable is set - # when we receive an early response from the server. If this value is - # set to True, any calls to send() are noops. This value is reset to - # false every time _send_request is called. This is to workaround the - # fact that py2.6 (and only py2.6) has a separate send() call for the - # body in _send_request, as opposed to endheaders(), which is where the - # body is sent in all versions > 2.6. + # This variable is set when we receive an early response from the + # server. If this value is set to True, any calls to send() are noops. + # This value is reset to false every time _send_request is called. + # This is to workaround changes in urllib3 2.0 which uses separate + # send() calls in request() instead of delegating to endheaders(), + # which is where the body is sent in CPython's HTTPConnection. self._response_received = False self._expect_header_set = False + self._send_called = False def close(self): super().close() # Reset all of our instance state we were tracking. self._response_received = False self._expect_header_set = False + self._send_called = False self.response_class = self._original_response_cls - def _send_request(self, method, url, body, headers, *args, **kwargs): + def request(self, method, url, body=None, headers=None, *args, **kwargs): + if headers is None: + headers = {} self._response_received = False if headers.get('Expect', b'') == b'100-continue': self._expect_header_set = True else: self._expect_header_set = False self.response_class = self._original_response_cls - rval = super()._send_request( + rval = super().request( method, url, body, headers, *args, **kwargs ) self._expect_header_set = False @@ -210,10 +214,13 @@ class AWSConnection: def send(self, str): if self._response_received: - logger.debug( - "send() called, but reseponse already received. " - "Not sending data." - ) + if not self._send_called: + # urllib3 2.0 chunks and calls send potentially + # thousands of times inside `request` unlike the + # standard library. Only log this once for sanity. + logger.debug("send() called, but reseponse already received. " + "Not sending data.") + self._send_called = True return return super().send(str) @@ -370,8 +377,14 @@ class AWSRequestPreparer: def _prepare_url(self, original): url = original.url if original.params: - params = urlencode(list(original.params.items()), doseq=True) - url = f'{url}?{params}' + url_parts = urlparse(url) + delim = '&' if url_parts.query else '?' + if isinstance(original.params, Mapping): + params_to_encode = list(original.params.items()) + else: + params_to_encode = original.params + params = urlencode(params_to_encode, doseq=True) + url = delim.join((url, params)) return url def _prepare_headers(self, original, prepared_body=None): diff --git a/awscli/botocore/httpsession.py b/awscli/botocore/httpsession.py index 516bb3f..25f982d 100644 --- a/awscli/botocore/httpsession.py +++ b/awscli/botocore/httpsession.py @@ -328,7 +328,6 @@ class URLLib3Session: def _get_pool_manager_kwargs(self, **extra_kwargs): pool_manager_kwargs = { - 'strict': True, 'timeout': self._timeout, 'maxsize': self._max_pool_connections, 'ssl_context': self._get_ssl_context(), diff --git a/tests/unit/botocore/test_http_session.py b/tests/unit/botocore/test_http_session.py index 90ed7d4..0a10c15 100644 --- a/tests/unit/botocore/test_http_session.py +++ b/tests/unit/botocore/test_http_session.py @@ -148,7 +148,6 @@ class TestURLLib3Session(unittest.TestCase): def _assert_manager_call(self, manager, *assert_args, **assert_kwargs): call_kwargs = { - 'strict': True, 'maxsize': mock.ANY, 'timeout': mock.ANY, 'ssl_context': mock.ANY,