Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/urllib3/poolmanager.py: 34%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

245 statements  

1from __future__ import annotations 

2 

3import functools 

4import logging 

5import typing 

6import warnings 

7from types import TracebackType 

8from urllib.parse import urljoin 

9 

10from ._collections import HTTPHeaderDict, RecentlyUsedContainer 

11from ._request_methods import RequestMethods 

12from .connection import ProxyConfig 

13from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme 

14from .exceptions import ( 

15 LocationValueError, 

16 MaxRetryError, 

17 ProxySchemeUnknown, 

18 URLSchemeUnknown, 

19) 

20from .response import BaseHTTPResponse 

21from .util.connection import _TYPE_SOCKET_OPTIONS 

22from .util.proxy import connection_requires_http_tunnel 

23from .util.retry import Retry 

24from .util.timeout import Timeout 

25from .util.url import Url, parse_url 

26 

27if typing.TYPE_CHECKING: 

28 import ssl 

29 

30 from typing_extensions import Self 

31 

32__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] 

33 

34 

35log = logging.getLogger(__name__) 

36 

37SSL_KEYWORDS = ( 

38 "key_file", 

39 "cert_file", 

40 "cert_reqs", 

41 "ca_certs", 

42 "ca_cert_data", 

43 "ssl_version", 

44 "ssl_minimum_version", 

45 "ssl_maximum_version", 

46 "ca_cert_dir", 

47 "ssl_context", 

48 "key_password", 

49 "server_hostname", 

50 "assert_hostname", 

51 "assert_fingerprint", 

52) 

53# Default value for `blocksize` - a new parameter introduced to 

54# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 

55_DEFAULT_BLOCKSIZE = 16384 

56 

57 

58class PoolKey(typing.NamedTuple): 

59 """ 

60 All known keyword arguments that could be provided to the pool manager, its 

61 pools, or the underlying connections. 

62 

63 All custom key schemes should include the fields in this key at a minimum. 

64 """ 

65 

66 key_scheme: str 

67 key_host: str 

68 key_port: int | None 

69 key_timeout: Timeout | float | int | None 

70 key_retries: Retry | bool | int | None 

71 key_block: bool | None 

72 key_source_address: tuple[str, int] | None 

73 key_key_file: str | None 

74 key_key_password: str | None 

75 key_cert_file: str | None 

76 key_cert_reqs: str | None 

77 key_ca_certs: str | None 

78 key_ca_cert_data: str | bytes | None 

79 key_ssl_version: int | str | None 

80 key_ssl_minimum_version: ssl.TLSVersion | None 

81 key_ssl_maximum_version: ssl.TLSVersion | None 

82 key_ca_cert_dir: str | None 

83 key_ssl_context: ssl.SSLContext | None 

84 key_maxsize: int | None 

85 key_headers: frozenset[tuple[str, str]] | None 

86 key__proxy: Url | None 

87 key__proxy_headers: frozenset[tuple[str, str]] | None 

88 key__proxy_config: ProxyConfig | None 

89 key_socket_options: _TYPE_SOCKET_OPTIONS | None 

90 key__socks_options: frozenset[tuple[str, str]] | None 

91 key_assert_hostname: bool | str | None 

92 key_assert_fingerprint: str | None 

93 key_server_hostname: str | None 

94 key_blocksize: int | None 

95 

96 

97def _default_key_normalizer( 

98 key_class: type[PoolKey], request_context: dict[str, typing.Any] 

99) -> PoolKey: 

100 """ 

101 Create a pool key out of a request context dictionary. 

102 

103 According to RFC 3986, both the scheme and host are case-insensitive. 

104 Therefore, this function normalizes both before constructing the pool 

105 key for an HTTPS request. If you wish to change this behaviour, provide 

106 alternate callables to ``key_fn_by_scheme``. 

107 

108 :param key_class: 

109 The class to use when constructing the key. This should be a namedtuple 

110 with the ``scheme`` and ``host`` keys at a minimum. 

111 :type key_class: namedtuple 

112 :param request_context: 

113 A dictionary-like object that contain the context for a request. 

114 :type request_context: dict 

115 

116 :return: A namedtuple that can be used as a connection pool key. 

117 :rtype: PoolKey 

118 """ 

119 # Since we mutate the dictionary, make a copy first 

120 context = request_context.copy() 

121 context["scheme"] = context["scheme"].lower() 

122 context["host"] = context["host"].lower() 

123 

124 # These are both dictionaries and need to be transformed into frozensets 

125 for key in ("headers", "_proxy_headers", "_socks_options"): 

126 if key in context and context[key] is not None: 

127 context[key] = frozenset(context[key].items()) 

128 

129 # The socket_options key may be a list and needs to be transformed into a 

130 # tuple. 

131 socket_opts = context.get("socket_options") 

132 if socket_opts is not None: 

133 context["socket_options"] = tuple(socket_opts) 

134 

135 # Map the kwargs to the names in the namedtuple - this is necessary since 

136 # namedtuples can't have fields starting with '_'. 

137 for key in list(context.keys()): 

138 context["key_" + key] = context.pop(key) 

139 

140 # Default to ``None`` for keys missing from the context 

141 for field in key_class._fields: 

142 if field not in context: 

143 context[field] = None 

144 

145 # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context 

146 if context.get("key_blocksize") is None: 

147 context["key_blocksize"] = _DEFAULT_BLOCKSIZE 

148 

149 return key_class(**context) 

150 

151 

152#: A dictionary that maps a scheme to a callable that creates a pool key. 

153#: This can be used to alter the way pool keys are constructed, if desired. 

154#: Each PoolManager makes a copy of this dictionary so they can be configured 

155#: globally here, or individually on the instance. 

156key_fn_by_scheme = { 

157 "http": functools.partial(_default_key_normalizer, PoolKey), 

158 "https": functools.partial(_default_key_normalizer, PoolKey), 

159} 

160 

161pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} 

162 

163 

164class PoolManager(RequestMethods): 

165 """ 

166 Allows for arbitrary requests while transparently keeping track of 

167 necessary connection pools for you. 

168 

169 :param num_pools: 

170 Number of connection pools to cache before discarding the least 

171 recently used pool. 

172 

173 :param headers: 

174 Headers to include with all requests, unless other headers are given 

175 explicitly. 

176 

177 :param \\**connection_pool_kw: 

178 Additional parameters are used to create fresh 

179 :class:`urllib3.connectionpool.ConnectionPool` instances. 

180 

181 Example: 

182 

183 .. code-block:: python 

184 

185 import urllib3 

186 

187 http = urllib3.PoolManager(num_pools=2) 

188 

189 resp1 = http.request("GET", "https://google.com/") 

190 resp2 = http.request("GET", "https://google.com/mail") 

191 resp3 = http.request("GET", "https://yahoo.com/") 

192 

193 print(len(http.pools)) 

194 # 2 

195 

196 """ 

197 

198 proxy: Url | None = None 

199 proxy_config: ProxyConfig | None = None 

200 

201 def __init__( 

202 self, 

203 num_pools: int = 10, 

204 headers: typing.Mapping[str, str] | None = None, 

205 **connection_pool_kw: typing.Any, 

206 ) -> None: 

207 super().__init__(headers) 

208 # PoolManager handles redirects itself in PoolManager.urlopen(). 

209 # It always passes redirect=False to the underlying connection pool to 

210 # suppress per-pool redirect handling. If the user supplied a non-Retry 

211 # value (int/bool/etc) for retries and we let the pool normalize it 

212 # while redirect=False, the resulting Retry object would have redirect 

213 # handling disabled, which can interfere with PoolManager's own 

214 # redirect logic. Normalize here so redirects remain governed solely by 

215 # PoolManager logic. 

216 if "retries" in connection_pool_kw: 

217 retries = connection_pool_kw["retries"] 

218 if not isinstance(retries, Retry): 

219 retries = Retry.from_int(retries) 

220 connection_pool_kw = connection_pool_kw.copy() 

221 connection_pool_kw["retries"] = retries 

222 self.connection_pool_kw = connection_pool_kw 

223 

224 self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] 

225 self.pools = RecentlyUsedContainer(num_pools) 

226 

227 # Locally set the pool classes and keys so other PoolManagers can 

228 # override them. 

229 self.pool_classes_by_scheme = pool_classes_by_scheme 

230 self.key_fn_by_scheme = key_fn_by_scheme.copy() 

231 

232 def __enter__(self) -> Self: 

233 return self 

234 

235 def __exit__( 

236 self, 

237 exc_type: type[BaseException] | None, 

238 exc_val: BaseException | None, 

239 exc_tb: TracebackType | None, 

240 ) -> typing.Literal[False]: 

241 self.clear() 

242 # Return False to re-raise any potential exceptions 

243 return False 

244 

245 def _new_pool( 

246 self, 

247 scheme: str, 

248 host: str, 

249 port: int, 

250 request_context: dict[str, typing.Any] | None = None, 

251 ) -> HTTPConnectionPool: 

252 """ 

253 Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and 

254 any additional pool keyword arguments. 

255 

256 If ``request_context`` is provided, it is provided as keyword arguments 

257 to the pool class used. This method is used to actually create the 

258 connection pools handed out by :meth:`connection_from_url` and 

259 companion methods. It is intended to be overridden for customization. 

260 """ 

261 pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] 

262 if request_context is None: 

263 request_context = self.connection_pool_kw.copy() 

264 

265 # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly 

266 # set to 'None' in the request_context. 

267 if request_context.get("blocksize") is None: 

268 request_context["blocksize"] = _DEFAULT_BLOCKSIZE 

269 

270 # Although the context has everything necessary to create the pool, 

271 # this function has historically only used the scheme, host, and port 

272 # in the positional args. When an API change is acceptable these can 

273 # be removed. 

274 for key in ("scheme", "host", "port"): 

275 request_context.pop(key, None) 

276 

277 if scheme == "http": 

278 for kw in SSL_KEYWORDS: 

279 request_context.pop(kw, None) 

280 

281 return pool_cls(host, port, **request_context) 

282 

283 def clear(self) -> None: 

284 """ 

285 Empty our store of pools and direct them all to close. 

286 

287 This will not affect in-flight connections, but they will not be 

288 re-used after completion. 

289 """ 

290 self.pools.clear() 

291 

292 def connection_from_host( 

293 self, 

294 host: str | None, 

295 port: int | None = None, 

296 scheme: str | None = "http", 

297 pool_kwargs: dict[str, typing.Any] | None = None, 

298 ) -> HTTPConnectionPool: 

299 """ 

300 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. 

301 

302 If ``port`` isn't given, it will be derived from the ``scheme`` using 

303 ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is 

304 provided, it is merged with the instance's ``connection_pool_kw`` 

305 variable and used to create the new connection pool, if one is 

306 needed. 

307 """ 

308 

309 if not host: 

310 raise LocationValueError("No host specified.") 

311 

312 request_context = self._merge_pool_kwargs(pool_kwargs) 

313 request_context["scheme"] = scheme or "http" 

314 if port is None: 

315 port = port_by_scheme.get(request_context["scheme"].lower(), 80) 

316 request_context["port"] = port 

317 request_context["host"] = host 

318 

319 return self.connection_from_context(request_context) 

320 

321 def connection_from_context( 

322 self, request_context: dict[str, typing.Any] 

323 ) -> HTTPConnectionPool: 

324 """ 

325 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. 

326 

327 ``request_context`` must at least contain the ``scheme`` key and its 

328 value must be a key in ``key_fn_by_scheme`` instance variable. 

329 """ 

330 if "strict" in request_context: 

331 warnings.warn( 

332 "The 'strict' parameter is no longer needed on Python 3+. " 

333 "This will raise an error in urllib3 v3.0.", 

334 FutureWarning, 

335 ) 

336 request_context.pop("strict") 

337 

338 scheme = request_context["scheme"].lower() 

339 pool_key_constructor = self.key_fn_by_scheme.get(scheme) 

340 if not pool_key_constructor: 

341 raise URLSchemeUnknown(scheme) 

342 pool_key = pool_key_constructor(request_context) 

343 

344 return self.connection_from_pool_key(pool_key, request_context=request_context) 

345 

346 def connection_from_pool_key( 

347 self, pool_key: PoolKey, request_context: dict[str, typing.Any] 

348 ) -> HTTPConnectionPool: 

349 """ 

350 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. 

351 

352 ``pool_key`` should be a namedtuple that only contains immutable 

353 objects. At a minimum it must have the ``scheme``, ``host``, and 

354 ``port`` fields. 

355 """ 

356 with self.pools.lock: 

357 # If the scheme, host, or port doesn't match existing open 

358 # connections, open a new ConnectionPool. 

359 pool = self.pools.get(pool_key) 

360 if pool: 

361 return pool 

362 

363 # Make a fresh ConnectionPool of the desired type 

364 scheme = request_context["scheme"] 

365 host = request_context["host"] 

366 port = request_context["port"] 

367 pool = self._new_pool(scheme, host, port, request_context=request_context) 

368 self.pools[pool_key] = pool 

369 

370 return pool 

371 

372 def connection_from_url( 

373 self, url: str, pool_kwargs: dict[str, typing.Any] | None = None 

374 ) -> HTTPConnectionPool: 

375 """ 

376 Similar to :func:`urllib3.connectionpool.connection_from_url`. 

377 

378 If ``pool_kwargs`` is not provided and a new pool needs to be 

379 constructed, ``self.connection_pool_kw`` is used to initialize 

380 the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` 

381 is provided, it is used instead. Note that if a new pool does not 

382 need to be created for the request, the provided ``pool_kwargs`` are 

383 not used. 

384 """ 

385 u = parse_url(url) 

386 return self.connection_from_host( 

387 u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs 

388 ) 

389 

390 def _merge_pool_kwargs( 

391 self, override: dict[str, typing.Any] | None 

392 ) -> dict[str, typing.Any]: 

393 """ 

394 Merge a dictionary of override values for self.connection_pool_kw. 

395 

396 This does not modify self.connection_pool_kw and returns a new dict. 

397 Any keys in the override dictionary with a value of ``None`` are 

398 removed from the merged dictionary. 

399 """ 

400 base_pool_kwargs = self.connection_pool_kw.copy() 

401 if override: 

402 for key, value in override.items(): 

403 if value is None: 

404 try: 

405 del base_pool_kwargs[key] 

406 except KeyError: 

407 pass 

408 else: 

409 base_pool_kwargs[key] = value 

410 return base_pool_kwargs 

411 

412 def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: 

413 """ 

414 Indicates if the proxy requires the complete destination URL in the 

415 request. Normally this is only needed when not using an HTTP CONNECT 

416 tunnel. 

417 """ 

418 if self.proxy is None: 

419 return False 

420 

421 return not connection_requires_http_tunnel( 

422 self.proxy, self.proxy_config, parsed_url.scheme 

423 ) 

424 

425 def urlopen( # type: ignore[override] 

426 self, method: str, url: str, redirect: bool = True, **kw: typing.Any 

427 ) -> BaseHTTPResponse: 

428 """ 

429 Same as :meth:`urllib3.HTTPConnectionPool.urlopen` 

430 with custom cross-host redirect logic and only sends the request-uri 

431 portion of the ``url``. 

432 

433 The given ``url`` parameter must be absolute, such that an appropriate 

434 :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. 

435 """ 

436 u = parse_url(url) 

437 

438 if u.scheme is None: 

439 warnings.warn( 

440 "URLs without a scheme (ie 'https://') are deprecated and will raise an error " 

441 "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " 

442 "start with 'https://' or 'http://'. Read more in this issue: " 

443 "https://github.com/urllib3/urllib3/issues/2920", 

444 category=FutureWarning, 

445 stacklevel=2, 

446 ) 

447 

448 conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) 

449 

450 kw["assert_same_host"] = False 

451 kw["redirect"] = False 

452 

453 if "headers" not in kw: 

454 kw["headers"] = self.headers 

455 

456 if self._proxy_requires_url_absolute_form(u): 

457 response = conn.urlopen(method, u._replace(fragment=None).url, **kw) 

458 else: 

459 response = conn.urlopen(method, u.request_uri, **kw) 

460 

461 redirect_location = redirect and response.get_redirect_location() 

462 if not redirect_location: 

463 return response 

464 

465 # Support relative URLs for redirecting. 

466 redirect_location = urljoin(url, redirect_location) 

467 

468 if response.status == 303: 

469 # Change the method according to RFC 9110, Section 15.4.4. 

470 method = "GET" 

471 # And lose the body not to transfer anything sensitive. 

472 kw["body"] = None 

473 # The body is gone, so the state that describes it has to go too: 

474 # there is nothing left to frame with chunked transfer encoding, 

475 # and nothing left to rewind. 

476 kw["chunked"] = False 

477 kw["body_pos"] = None 

478 kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() 

479 

480 retries = kw.get("retries", response.retries) 

481 if not isinstance(retries, Retry): 

482 retries = Retry.from_int(retries, redirect=redirect) 

483 

484 # Strip headers marked as unsafe to forward to the redirected location. 

485 # Check remove_headers_on_redirect to avoid a potential network call within 

486 # conn.is_same_host() which may use socket.gethostbyname() in the future. 

487 if retries.remove_headers_on_redirect and not conn.is_same_host( 

488 redirect_location 

489 ): 

490 new_headers = kw["headers"].copy() 

491 for header in kw["headers"]: 

492 if header.lower() in retries.remove_headers_on_redirect: 

493 new_headers.pop(header, None) 

494 kw["headers"] = new_headers 

495 

496 try: 

497 retries = retries.increment(method, url, response=response, _pool=conn) 

498 except MaxRetryError: 

499 if retries.raise_on_redirect: 

500 response.drain_conn() 

501 raise 

502 return response 

503 

504 kw["retries"] = retries 

505 kw["redirect"] = redirect 

506 

507 log.info("Redirecting %s -> %s", url, redirect_location) 

508 

509 response.drain_conn() 

510 return self.urlopen(method, redirect_location, **kw) 

511 

512 

513class ProxyManager(PoolManager): 

514 """ 

515 Behaves just like :class:`PoolManager`, but sends all requests through 

516 the defined proxy, using the CONNECT method for HTTPS URLs. 

517 

518 :param proxy_url: 

519 The URL of the proxy to be used. 

520 

521 :param proxy_headers: 

522 A dictionary containing headers that will be sent to the proxy. In case 

523 of HTTP they are being sent with each request, while in the 

524 HTTPS/CONNECT case they are sent only once. Could be used for proxy 

525 authentication. 

526 

527 :param proxy_ssl_context: 

528 The proxy SSL context is used to establish the TLS connection to the 

529 proxy when using HTTPS proxies. 

530 

531 :param use_forwarding_for_https: 

532 (Defaults to False) If set to True will forward requests to the HTTPS 

533 proxy to be made on behalf of the client instead of creating a TLS 

534 tunnel via the CONNECT method. **Enabling this flag means that request 

535 and response headers and content will be visible from the HTTPS proxy** 

536 whereas tunneling keeps request and response headers and content 

537 private. IP address, target hostname, SNI, and port are always visible 

538 to an HTTPS proxy even when this flag is disabled. 

539 

540 :param proxy_assert_hostname: 

541 The hostname of the certificate to verify against. 

542 

543 :param proxy_assert_fingerprint: 

544 The fingerprint of the certificate to verify against. 

545 

546 Example: 

547 

548 .. code-block:: python 

549 

550 import urllib3 

551 

552 proxy = urllib3.ProxyManager("https://localhost:3128/") 

553 

554 resp1 = proxy.request("GET", "http://google.com/") 

555 resp2 = proxy.request("GET", "http://httpbin.org/") 

556 

557 # One pool was shared by both plain HTTP requests. 

558 print(len(proxy.pools)) 

559 # 1 

560 

561 resp3 = proxy.request("GET", "https://httpbin.org/") 

562 resp4 = proxy.request("GET", "https://twitter.com/") 

563 

564 # A separate pool was added for each HTTPS target. 

565 print(len(proxy.pools)) 

566 # 3 

567 

568 """ 

569 

570 def __init__( 

571 self, 

572 proxy_url: str, 

573 num_pools: int = 10, 

574 headers: typing.Mapping[str, str] | None = None, 

575 proxy_headers: typing.Mapping[str, str] | None = None, 

576 proxy_ssl_context: ssl.SSLContext | None = None, 

577 use_forwarding_for_https: bool = False, 

578 proxy_assert_hostname: None | str | typing.Literal[False] = None, 

579 proxy_assert_fingerprint: str | None = None, 

580 **connection_pool_kw: typing.Any, 

581 ) -> None: 

582 if isinstance(proxy_url, HTTPConnectionPool): 

583 str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" 

584 else: 

585 str_proxy_url = proxy_url 

586 proxy = parse_url(str_proxy_url) 

587 

588 if proxy.scheme not in ("http", "https"): 

589 raise ProxySchemeUnknown(proxy.scheme) 

590 

591 # Keep the deprecated ssl_context fallback on the manager for 

592 # compatibility, while passing only explicit proxy policy to connections. 

593 self.proxy_ssl_context = proxy_ssl_context 

594 if ( 

595 use_forwarding_for_https 

596 and proxy.scheme == "https" 

597 and connection_pool_kw.get("ssl_context") is not None 

598 ): 

599 warnings.warn( 

600 "Passing ssl_context when use_forwarding_for_https=True is deprecated " 

601 "and will raise an error in urllib3 v3.0. " 

602 "Use proxy_ssl_context to configure the TLS connection to the proxy.", 

603 FutureWarning, 

604 stacklevel=2, 

605 ) 

606 if self.proxy_ssl_context is None: 

607 self.proxy_ssl_context = connection_pool_kw.get("ssl_context") 

608 

609 if proxy.port is None: 

610 port = port_by_scheme.get(proxy.scheme, 80) 

611 proxy = proxy._replace(port=port) 

612 

613 self.proxy = proxy 

614 self.proxy_headers = proxy_headers or {} 

615 self.proxy_config = ProxyConfig( 

616 proxy_ssl_context, 

617 use_forwarding_for_https, 

618 proxy_assert_hostname, 

619 proxy_assert_fingerprint, 

620 ) 

621 

622 connection_pool_kw["_proxy"] = self.proxy 

623 connection_pool_kw["_proxy_headers"] = self.proxy_headers 

624 connection_pool_kw["_proxy_config"] = self.proxy_config 

625 

626 super().__init__(num_pools, headers, **connection_pool_kw) 

627 

628 def connection_from_host( 

629 self, 

630 host: str | None, 

631 port: int | None = None, 

632 scheme: str | None = "http", 

633 pool_kwargs: dict[str, typing.Any] | None = None, 

634 ) -> HTTPConnectionPool: 

635 if scheme == "https": 

636 return super().connection_from_host( 

637 host, port, scheme, pool_kwargs=pool_kwargs 

638 ) 

639 

640 return super().connection_from_host( 

641 self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] 

642 ) 

643 

644 def _set_proxy_headers( 

645 self, url: str, headers: typing.Mapping[str, str] | None = None 

646 ) -> typing.Mapping[str, str]: 

647 """ 

648 Sets headers needed by proxies: specifically, the Accept and Host 

649 headers. Only sets headers not provided by the user. 

650 """ 

651 headers_ = {"Accept": "*/*"} 

652 

653 netloc = parse_url(url).netloc 

654 if netloc: 

655 headers_["Host"] = netloc 

656 

657 if headers: 

658 headers_.update(headers) 

659 return headers_ 

660 

661 def urlopen( # type: ignore[override] 

662 self, method: str, url: str, redirect: bool = True, **kw: typing.Any 

663 ) -> BaseHTTPResponse: 

664 "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." 

665 u = parse_url(url) 

666 if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): 

667 # For connections using HTTP CONNECT, httplib sets the necessary 

668 # headers on the CONNECT to the proxy. If we're not using CONNECT, 

669 # we'll definitely need to set 'Host' at the very least. 

670 headers = kw.get("headers", self.headers) 

671 kw["headers"] = self._set_proxy_headers(url, headers) 

672 

673 return super().urlopen(method, url, redirect=redirect, **kw) 

674 

675 

676def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: 

677 return ProxyManager(proxy_url=url, **kw)