|
9 | 9 | from socket import error as SocketError |
10 | 10 | from socket import timeout as SocketTimeout |
11 | 11 |
|
| 12 | +from ._collections import HTTPHeaderDict |
12 | 13 | from .connection import ( |
13 | 14 | BaseSSLError, |
14 | 15 | BrokenPipeError, |
|
50 | 51 | from .util.url import _normalize_host as normalize_host |
51 | 52 | from .util.url import get_host, parse_url |
52 | 53 |
|
| 54 | +try: # Platform-specific: Python 3 |
| 55 | + import weakref |
| 56 | + |
| 57 | + weakref_finalize = weakref.finalize |
| 58 | +except AttributeError: # Platform-specific: Python 2 |
| 59 | + from .packages.backports.weakref_finalize import weakref_finalize |
| 60 | + |
53 | 61 | xrange = six.moves.xrange |
54 | 62 |
|
55 | 63 | log = logging.getLogger(__name__) |
@@ -220,6 +228,16 @@ def __init__( |
220 | 228 | self.conn_kw["proxy"] = self.proxy |
221 | 229 | self.conn_kw["proxy_config"] = self.proxy_config |
222 | 230 |
|
| 231 | + # Do not pass 'self' as callback to 'finalize'. |
| 232 | + # Then the 'finalize' would keep an endless living (leak) to self. |
| 233 | + # By just passing a reference to the pool allows the garbage collector |
| 234 | + # to free self if nobody else has a reference to it. |
| 235 | + pool = self.pool |
| 236 | + |
| 237 | + # Close all the HTTPConnections in the pool before the |
| 238 | + # HTTPConnectionPool object is garbage collected. |
| 239 | + weakref_finalize(self, _close_pool_connections, pool) |
| 240 | + |
223 | 241 | def _new_conn(self): |
224 | 242 | """ |
225 | 243 | Return a fresh :class:`HTTPConnection`. |
@@ -489,14 +507,8 @@ def close(self): |
489 | 507 | # Disable access to the pool |
490 | 508 | old_pool, self.pool = self.pool, None |
491 | 509 |
|
492 | | - try: |
493 | | - while True: |
494 | | - conn = old_pool.get(block=False) |
495 | | - if conn: |
496 | | - conn.close() |
497 | | - |
498 | | - except queue.Empty: |
499 | | - pass # Done. |
| 510 | + # Close all the HTTPConnections in the pool. |
| 511 | + _close_pool_connections(old_pool) |
500 | 512 |
|
501 | 513 | def is_same_host(self, url): |
502 | 514 | """ |
@@ -832,7 +844,11 @@ def _is_ssl_error_message_from_http_proxy(ssl_error): |
832 | 844 | redirect_location = redirect and response.get_redirect_location() |
833 | 845 | if redirect_location: |
834 | 846 | if response.status == 303: |
| 847 | + # Change the method according to RFC 9110, Section 15.4.4. |
835 | 848 | method = "GET" |
| 849 | + # And lose the body not to transfer anything sensitive. |
| 850 | + body = None |
| 851 | + headers = HTTPHeaderDict(headers)._prepare_for_method_change() |
836 | 852 |
|
837 | 853 | try: |
838 | 854 | retries = retries.increment(method, url, response=response, _pool=self) |
@@ -1108,3 +1124,14 @@ def _normalize_host(host, scheme): |
1108 | 1124 | if host.startswith("[") and host.endswith("]"): |
1109 | 1125 | host = host[1:-1] |
1110 | 1126 | return host |
| 1127 | + |
| 1128 | + |
| 1129 | +def _close_pool_connections(pool): |
| 1130 | + """Drains a queue of connections and closes each one.""" |
| 1131 | + try: |
| 1132 | + while True: |
| 1133 | + conn = pool.get(block=False) |
| 1134 | + if conn: |
| 1135 | + conn.close() |
| 1136 | + except queue.Empty: |
| 1137 | + pass # Done. |
0 commit comments