blob: 8ed0494b3d2f4832e1c8ce181c83a3fc2bb5fb2c [file] [log] [blame]
# Copyright (c) 2012 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 base64
import logging
import posixpath
import time
from appengine_wrappers import urlfetch
from environment import GetAppVersion
from future import Future
_MAX_RETRIES = 5
_RETRY_DELAY_SECONDS = 30
def _MakeHeaders(username, password, access_token):
headers = {
'User-Agent': 'Chromium docserver %s' % GetAppVersion(),
'Cache-Control': 'max-age=0',
}
if username is not None and password is not None:
headers['Authorization'] = 'Basic %s' % base64.b64encode(
'%s:%s' % (username, password))
if access_token is not None:
headers['Authorization'] = 'OAuth %s' % access_token
return headers
class AppEngineUrlFetcher(object):
"""A wrapper around the App Engine urlfetch module that allows for easy
async fetches.
"""
def __init__(self, base_path=None):
assert base_path is None or not base_path.endswith('/'), base_path
self._base_path = base_path
self._retries_left = _MAX_RETRIES
def Fetch(self, url, username=None, password=None, access_token=None):
"""Fetches a file synchronously.
"""
return urlfetch.fetch(self._FromBasePath(url),
deadline=20,
headers=_MakeHeaders(username,
password,
access_token))
def FetchAsync(self, url, username=None, password=None, access_token=None):
"""Fetches a file asynchronously, and returns a Future with the result.
"""
def process_result(result):
if result.status_code == 429:
if self._retries_left == 0:
logging.error('Still throttled. Giving up.')
return result
self._retries_left -= 1
logging.info('Throttled. Trying again in %s seconds.' %
_RETRY_DELAY_SECONDS)
time.sleep(_RETRY_DELAY_SECONDS)
return self.FetchAsync(url, username, password, access_token).Get()
return result
rpc = urlfetch.create_rpc(deadline=20)
urlfetch.make_fetch_call(rpc,
self._FromBasePath(url),
headers=_MakeHeaders(username,
password,
access_token))
return Future(callback=lambda: process_result(rpc.get_result()))
def _FromBasePath(self, url):
assert not url.startswith('/'), url
if self._base_path is not None:
url = posixpath.join(self._base_path, url) if url else self._base_path
return url