downloader.py (3827B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\imagecaching\client\repository\downloader.py 2 import logging 3 import blue 4 import requests 5 from imagecaching.client.compat import to_text 6 from imagecaching.client.const import LOGGER_NAME, extension_for_content_type 7 from six.moves.http_client import OK, NOT_MODIFIED 8 logger = logging.getLogger(LOGGER_NAME) 9 MAX_ATTEMPTS = 4 10 REQUEST_TIMEOUT_SEC = 10 11 HTTPS_PREFIX = u'https://' 12 13 def _default_sleep(milliseconds): 14 blue.pyos.synchro.SleepWallclock(milliseconds) 15 16 17 class DownloadResult(object): 18 __slots__ = ['status', 19 'data', 20 'e_tag', 21 'status_code', 22 'error', 23 'extension'] 24 25 def __init__(self, status, data = None, e_tag = None, status_code = None, error = None, extension = None): 26 self.status = status 27 self.data = data 28 self.e_tag = e_tag 29 self.status_code = status_code 30 self.error = error 31 self.extension = extension 32 33 34 class ImageDownloader(object): 35 36 def __init__(self, session = None, sleep = None): 37 self._session = session or requests.session() 38 self._sleep = sleep or _default_sleep 39 40 def fetch(self, url, e_tag = None): 41 url = url.strip() 42 if not url.lower().startswith(HTTPS_PREFIX): 43 logger.warn('Refusing to fetch a non-https url: %s', url) 44 return DownloadResult(status=None, error=u'url is not https') 45 request_url = url.encode('ascii') 46 headers = {'If-None-Match': e_tag} if e_tag else {} 47 response, exc = self._make_request(request_url, headers) 48 if response is None: 49 logger.warn('Retries exhausted for %s: %s', url, exc) 50 return DownloadResult(status=None, error=u'%s' % (exc,) if exc else u'request failed') 51 final_url = to_text(response.url) or u'' 52 if not final_url.lower().startswith(HTTPS_PREFIX): 53 logger.warn('Refusing a redirect off https for %s: ended at %s', url, final_url) 54 return DownloadResult(status=None, status_code=response.status_code, error=u'redirected to a non-https url') 55 if response.status_code == NOT_MODIFIED: 56 return DownloadResult(status=NOT_MODIFIED, status_code=response.status_code) 57 if response.status_code != OK: 58 logger.warn('Unexpected response for %s: status=%s', url, response.status_code) 59 return DownloadResult(status=None, status_code=response.status_code, error=u'unexpected response (status=%s)' % response.status_code) 60 content_type = response.headers.get('Content-Type', None) 61 extension = extension_for_content_type(content_type) 62 if extension is None: 63 logger.warn('Refusing an unsupported content type for %s: %s', url, content_type) 64 return DownloadResult(status=None, status_code=response.status_code, error=u'unsupported image content-type (%s)' % content_type) 65 return DownloadResult(status=OK, data=response.content, e_tag=response.headers.get('ETag'), status_code=response.status_code, extension=extension) 66 67 def _make_request(self, request_url, headers): 68 response = None 69 exc = None 70 for attempt in range(MAX_ATTEMPTS): 71 try: 72 response = self._session.get(request_url, headers=headers, timeout=REQUEST_TIMEOUT_SEC, allow_redirects=True) 73 break 74 except Exception as e: 75 exc = e 76 if attempt + 1 < MAX_ATTEMPTS: 77 backoff_seconds = 2 ** attempt 78 logger.warn('Retrying requesting image for url: %s. Sleeping for %s seconds. Attempts done so far: (%s/%s). Exception: %s', request_url, backoff_seconds, attempt + 1, MAX_ATTEMPTS, e) 79 self._sleep(backoff_seconds * 1000) 80 81 return (response, exc)