| 263 | |
| 264 | @contextmanager |
| 265 | def get_body_stream( |
| 266 | self, |
| 267 | url, # type: Text |
| 268 | extra_headers=None, # type: Optional[Mapping[str, str]] |
| 269 | ): |
| 270 | # type: (...) -> Iterator[BinaryIO] |
| 271 | |
| 272 | handlers = list(self._handlers) |
| 273 | if self._password_database.entries: |
| 274 | password_manager = HTTPPasswordMgrWithDefaultRealm() |
| 275 | for password_entry in self._password_database.entries: |
| 276 | # N.B.: The password manager adds a second entry implicitly if the URI we hand it |
| 277 | # does not include port information (80 for http URIs and 443 for https URIs). |
| 278 | password_manager.add_password( |
| 279 | realm=None, |
| 280 | uri=password_entry.uri_or_default(url), |
| 281 | user=password_entry.username, |
| 282 | passwd=password_entry.password, |
| 283 | ) |
| 284 | handlers.extend( |
| 285 | (HTTPBasicAuthHandler(password_manager), HTTPDigestAuthHandler(password_manager)) |
| 286 | ) |
| 287 | |
| 288 | retries = 0 |
| 289 | retry_delay_secs = 0.1 |
| 290 | last_error = None # type: Optional[Exception] |
| 291 | while retries <= self._max_retries: |
| 292 | if retries > 0: |
| 293 | time.sleep(retry_delay_secs) |
| 294 | retry_delay_secs *= 2 |
| 295 | |
| 296 | opener = build_opener(*handlers) |
| 297 | headers = dict(extra_headers) if extra_headers else {} |
| 298 | headers["User-Agent"] = self.USER_AGENT |
| 299 | request = Request( |
| 300 | # N.B.: MyPy incorrectly thinks url must be a str in Python 2 where a unicode url |
| 301 | # actually works fine. |
| 302 | url, # type: ignore[arg-type] |
| 303 | headers=headers, |
| 304 | ) |
| 305 | # The fp is typed as Optional[...] for Python 2 only in the typeshed. A `None` |
| 306 | # can only be returned if a faulty custom handler is installed and we only |
| 307 | # install stdlib handlers. |
| 308 | fp = cast(BinaryIO, opener.open(request, timeout=self._timeout)) |
| 309 | try: |
| 310 | with closing(fp) as body_stream: |
| 311 | yield body_stream |
| 312 | return |
| 313 | except HTTPError as e: |
| 314 | # See: https://tools.ietf.org/html/rfc2616#page-39 |
| 315 | if e.code not in ( |
| 316 | 408, # Request Time-out |
| 317 | 500, # Internal Server Error |
| 318 | 503, # Service Unavailable |
| 319 | 504, # Gateway Time-out |
| 320 | ): |
| 321 | raise e |
| 322 | last_error = e |