def _send_request(self, url, method, redirect, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError: msg = 'SSL exception connecting to %s' % url raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = 'Request to %s timed out' % url raise exceptions.Timeout(msg) except requests.exceptions.ConnectionError: msg = 'Unable to establish connection to %s' % url raise exceptions.ConnectionError(msg) _logger.debug('RESP: [%s] %s\nRESP BODY: %s\n', resp.status_code, resp.headers, resp.text) if resp.status_code in self.REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: _logger.warn( "Failed to redirect request to %s as new " "location was not provided.", resp.url) else: new_resp = self._send_request(location, method, redirect, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp
def _send_request(self, url, method, redirect, log, logger, connect_retries, connect_retry_delay=0.5, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get # NOTE(jamielennox): The interaction between retries and redirects are # handled naively. We will attempt only a maximum number of retries and # redirects rather than per request limits. Otherwise the extreme case # could be redirects * retries requests. This will be sufficient in # most cases and can be fixed properly if there's ever a need. try: try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError as e: msg = _('SSL exception connecting to %(url)s: ' '%(error)s') % { 'url': url, 'error': e } raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), { 'e': e, 'delay': connect_retry_delay }) time.sleep(connect_retry_delay) return self._send_request(url, method, redirect, log, logger, connect_retries=connect_retries - 1, connect_retry_delay=connect_retry_delay * 2, **kwargs) if log: self._http_log_response(resp, logger) if resp.status_code in self._REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: logger.warning( _LW("Failed to redirect request to %s as new " "location was not provided."), resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. new_resp = self._send_request(location, method, redirect, log, logger, connect_retries=connect_retries, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp