in fxa/_utils.py [0:0]
def request(self, method, url, json=None, retry_auth_errors=True, **kwds):
"""Make a request to the API and process the response.
This method implements the low-level details of interacting with an
FxA Web API, stripping away most of the details of HTTP. It will
return the parsed JSON of a successful responses, or raise an exception
for an error response. It's also responsible for backoff handling
and clock-skew tracking.
"""
# Don't make requests if we're in backoff.
# Instead just synthesize a backoff response.
if self._backoff_response is not None:
if self._backoff_until >= self.client_curtime():
resp = pickle.loads(self._backoff_response)
resp.request = None
resp.headers["Timestamp"] = str(int(self.server_curtime()))
return resp
else:
self._backoff_until = 0
self._backoff_response = None
# Apply defaults and perform the request.
while url.startswith("/"):
url = url[1:]
if self.server_url.endswith("/"):
url = urljoin(self.server_url, url)
else:
url = urljoin(self.server_url + "/", url)
if self.timeout is not None:
kwds.setdefault("timeout", self.timeout)
# Configure the user agent
headers = kwds.get('headers', {})
headers.setdefault('User-Agent', USER_AGENT_HEADER)
kwds['headers'] = headers
resp = self._session.request(method, url, json=json, **kwds)
# Everything should return a valid JSON response. Even errors.
content_type = resp.headers.get("content-type", "")
if not content_type.startswith("application/json"):
msg = "API responded with non-json content-type: {0}"
raise fxa.errors.OutOfProtocolError(msg.format(content_type))
try:
body = resp.json()
except ValueError as e:
msg = "API responded with invalid json: {0}"
raise fxa.errors.OutOfProtocolError(msg.format(e))
# Check for backoff indicator from the server.
# If found, backoff up to the client-specified max time.
if resp.status_code in (429, 500, 503):
try:
retry_after = int(resp.headers["retry-after"])
except (KeyError, ValueError):
pass
else:
if self.max_retry_after is not None:
retry_after = max(retry_after, self.max_retry_after)
self._backoff_until = self.client_curtime() + retry_after
self._backoff_response = pickle.dumps(resp)
# If we get a 401 with "serverTime" field in the body, then we're
# probably out of sync with the server's clock. Check our skew,
# adjust if necessary and try again.
if retry_auth_errors:
if resp.status_code == 401 and "serverTime" in body:
try:
server_timestamp = int(body["serverTime"])
except ValueError:
msg = "API responded with non-integer serverTime: {0}"
msg = msg.format(body["serverTime"])
raise fxa.errors.OutOfProtocolError(msg)
# If our guestimate is more than 30 seconds out, try again.
# This assumes the auth hook will use the updated clockskew.
if abs(server_timestamp - self.server_curtime()) > 30:
self._clockskew = server_timestamp - self.client_curtime()
return self.request(method, url, json, False, **kwds)
# See if we need to adjust for clock skew between client and server.
# We do this automatically once per session in the hopes of avoiding
# having to retry subsequent auth failures. We do it *after* the retry
# checking above, because it wrecks the "were we out of sync?" check.
if self._clockskew is None and "timestamp" in resp.headers:
try:
server_timestamp = int(resp.headers["timestamp"])
except ValueError:
msg = "API responded with non-integer timestamp: {0}"
msg = msg.format(resp.headers["timestamp"])
raise fxa.errors.OutOfProtocolError(msg)
else:
self._clockskew = server_timestamp - self.client_curtime()
# Raise exceptions for any error responses.
# XXX TODO: hooks for raising error subclass based on errno.
if 400 <= resp.status_code < 500:
raise fxa.errors.ClientError(body)
if 500 <= resp.status_code < 600:
raise fxa.errors.ServerError(body)
if resp.status_code < 200 or resp.status_code >= 300:
msg = "API responded with unexpected status code: {0}"
raise fxa.errors.OutOfProtocolError(msg.format(resp.status_code))
# Return the parsed JSON body for successful responses.
return body