blob: 1d1282e259ee6e75c76a311d7b751f91b95dda58 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import time
import urllib
from libs.http.interceptor import LoggingInterceptor
class RetryHttpClient(object):
"""Represents a http client to send http/https request to a remote server.
Subclasses should implement abstract functions below.
"""
def __init__(self,
no_error_logging_statuses=None,
interceptor=LoggingInterceptor()):
# If an http request results in the given statuses, the subclasses should
# not log an error.
self.interceptor = interceptor
if isinstance(self.interceptor, LoggingInterceptor):
no_error_logging_statuses = no_error_logging_statuses or []
self.interceptor.no_error_logging_statuses = no_error_logging_statuses
def _Get(self, url, timeout_seconds, headers): # pylint: disable=W0613, R0201
"""Sends the actual HTTP GET request.
Returns:
(status_code, content, response_headers)
status_code: the HTTP status code of the response.
content: the content of the response.
response_headers (dict): str => str map containing response headers.
"""
raise NotImplementedError(
'_Get() should be implemented in the child class') # pragma: no cover
def _Post(self, url, data, timeout_seconds, headers): # pylint: disable=W0613, R0201
"""Sends the actual HTTP POST request.
Returns:
(status_code, content, response_headers)
"""
raise NotImplementedError(
'_Post() should be implemented in the child class') # pragma: no cover
def _Put(self, url, data, timeout_seconds, headers): # pylint: disable=W0613, R0201
"""Sends the actual HTTP PUT request.
Returns:
(status_code, content, response_headers)
"""
raise NotImplementedError(
'_Put() should be implemented in the child class') # pragma: no cover
def GetBackoff(self, retry_backoff, tries):
"""Returns how many seconds to wait before next retry.
When ``retry_backoff`` is more than 1, return an exponential backoff;
otherwise we keep it the same.
Params:
retry_backoff (float): The base backoff in seconds.
tries (int): Indicates how many tries have been done.
"""
if retry_backoff > 1:
return retry_backoff * (2**(tries - 1))
else:
return retry_backoff
def _Retry(self,
url,
method,
data=None,
params=None,
timeout_seconds=60,
max_retries=5,
retry_backoff=1.5,
headers=None):
if params and method == 'GET':
url = '%s?%s' % (url, urllib.urlencode(params))
if self.interceptor:
request = {'url': url, 'headers': headers or {}}
request = self.interceptor.OnRequest(request)
url = request.get('url')
headers = request.get('headers')
# Inject auth-headers.
headers.update(self.interceptor.GetAuthenticationHeaders(request))
# Default values in case retries are exhausted with retriable exception.
status_code = 0
content = ""
response_headers = {}
tries = 0
while tries < max_retries:
tries += 1
try:
if method == 'POST':
status_code, content, response_headers = self._Post(
url, data, timeout_seconds, headers)
elif method == 'PUT':
status_code, content, response_headers = self._Put(
url, data, timeout_seconds, headers)
else:
status_code, content, response_headers = self._Get(
url, timeout_seconds, headers)
retry = False
if self.interceptor:
response, retry = self.interceptor.OnResponse(
request, {
'status_code': status_code,
'content': content,
'headers': response_headers
})
status_code, content, response_headers = response.get(
'status_code'), response.get('content'), response.get('headers')
if not retry:
break
except Exception as e:
if self.interceptor:
e = self.interceptor.OnException(
request, e, can_retry=tries < max_retries)
if e is not None:
raise e
time.sleep(self.GetBackoff(retry_backoff, tries))
return status_code, content, response_headers
def Get(self,
url,
params=None,
timeout_seconds=60,
max_retries=5,
retry_backoff=1.5,
headers=None):
"""Sends a GET request to the url with the given parameters and headers.
Params:
url (str): The raw url to send request to. If ``params`` is specified, the
url should not include any parameter in it.
params (dict): A key-value dict of parameters to send in the request.
timeout_seconds (int): The timeout for read/write of the http request.
max_retries (int): The maxmium times of retries for the request when the
returning http status code is not in 200, 302, 401, 403, 404, or 501.
retry_backoff (float): The base backoff in seconds for retry.
Returns:
(status_code, content, response_headers)
"""
return self._Retry(
url,
method='GET',
data=None,
params=params,
timeout_seconds=timeout_seconds,
max_retries=max_retries,
retry_backoff=retry_backoff,
headers=headers)
def Post(self,
url,
data,
timeout_seconds=60,
max_retries=5,
retry_backoff=1.5,
headers=None):
"""Sends a POST request to the url with the given parameters and headers.
Params:
url (str): The raw url to send request to. If ``params`` is specified, the
url should not include any parameter in it.
data (dict): The data to send for post request.
timeout_seconds (int): The timeout for read/write of the http request.
max_retries (int): The maximum times of retries for the request when the
returning http status code is not in 200, 302, 401, 403, 404, or 501.
retry_backoff (float): The base backoff in seconds for retry.
Returns:
(status_code, content, response_headers)
"""
return self._Retry(
url,
method='POST',
data=data,
params=None,
timeout_seconds=timeout_seconds,
max_retries=max_retries,
retry_backoff=retry_backoff,
headers=headers)
def Put(self,
url,
data,
timeout_seconds=60,
max_retries=5,
retry_backoff=1.5,
headers=None):
"""Sends a PUT request to the url with the given parameters and headers.
Params:
url (str): The raw url to send request to. If ``params`` is specified, the
url should not include any parameter in it.
data (dict): The data to send for post request.
timeout_seconds (int): The timeout for read/write of the http request.
max_retries (int): The maximum times of retries for the request when the
returning http status code is not in 200, 302, 401, 403, 404, or 501.
retry_backoff (float): The base backoff in seconds for retry.
Returns:
(status_code, content, response_headers)
"""
return self._Retry(
url,
method='PUT',
data=data,
params=None,
timeout_seconds=timeout_seconds,
max_retries=max_retries,
retry_backoff=retry_backoff,
headers=headers)