OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import base64 | 5 import base64 |
| 6 import logging |
6 import posixpath | 7 import posixpath |
| 8 import time |
7 | 9 |
8 from appengine_wrappers import urlfetch | 10 from appengine_wrappers import urlfetch |
9 from environment import GetAppVersion | 11 from environment import GetAppVersion |
10 from future import Future | 12 from future import Future |
11 | 13 |
12 | 14 |
| 15 _MAX_RETRIES = 5 |
| 16 _RETRY_DELAY_SECONDS = 30 |
| 17 |
| 18 |
13 def _MakeHeaders(username, password, access_token): | 19 def _MakeHeaders(username, password, access_token): |
14 headers = { | 20 headers = { |
15 'User-Agent': 'Chromium docserver %s' % GetAppVersion(), | 21 'User-Agent': 'Chromium docserver %s' % GetAppVersion(), |
16 'Cache-Control': 'max-age=0', | 22 'Cache-Control': 'max-age=0', |
17 } | 23 } |
18 if username is not None and password is not None: | 24 if username is not None and password is not None: |
19 headers['Authorization'] = 'Basic %s' % base64.b64encode( | 25 headers['Authorization'] = 'Basic %s' % base64.b64encode( |
20 '%s:%s' % (username, password)) | 26 '%s:%s' % (username, password)) |
21 if access_token is not None: | 27 if access_token is not None: |
22 headers['Authorization'] = 'OAuth %s' % access_token | 28 headers['Authorization'] = 'OAuth %s' % access_token |
23 return headers | 29 return headers |
24 | 30 |
25 | 31 |
26 class AppEngineUrlFetcher(object): | 32 class AppEngineUrlFetcher(object): |
27 """A wrapper around the App Engine urlfetch module that allows for easy | 33 """A wrapper around the App Engine urlfetch module that allows for easy |
28 async fetches. | 34 async fetches. |
29 """ | 35 """ |
30 def __init__(self, base_path=None): | 36 def __init__(self, base_path=None): |
31 assert base_path is None or not base_path.endswith('/'), base_path | 37 assert base_path is None or not base_path.endswith('/'), base_path |
32 self._base_path = base_path | 38 self._base_path = base_path |
| 39 self._retries_left = _MAX_RETRIES |
33 | 40 |
34 def Fetch(self, url, username=None, password=None, access_token=None): | 41 def Fetch(self, url, username=None, password=None, access_token=None): |
35 """Fetches a file synchronously. | 42 """Fetches a file synchronously. |
36 """ | 43 """ |
37 return urlfetch.fetch(self._FromBasePath(url), | 44 return urlfetch.fetch(self._FromBasePath(url), |
38 deadline=20, | 45 deadline=20, |
39 headers=_MakeHeaders(username, | 46 headers=_MakeHeaders(username, |
40 password, | 47 password, |
41 access_token)) | 48 access_token)) |
42 | 49 |
43 def FetchAsync(self, url, username=None, password=None, access_token=None): | 50 def FetchAsync(self, url, username=None, password=None, access_token=None): |
44 """Fetches a file asynchronously, and returns a Future with the result. | 51 """Fetches a file asynchronously, and returns a Future with the result. |
45 """ | 52 """ |
| 53 def process_result(result): |
| 54 if result.status_code == 429: |
| 55 if self._retries_left == 0: |
| 56 logging.error('Still throttled. Giving up.') |
| 57 return result |
| 58 self._retries_left -= 1 |
| 59 logging.info('Throttled. Trying again in %s seconds.' % |
| 60 _RETRY_DELAY_SECONDS) |
| 61 time.sleep(_RETRY_DELAY_SECONDS) |
| 62 return self.FetchAsync(url, username, password, access_token).Get() |
| 63 return result |
| 64 |
46 rpc = urlfetch.create_rpc(deadline=20) | 65 rpc = urlfetch.create_rpc(deadline=20) |
47 urlfetch.make_fetch_call(rpc, | 66 urlfetch.make_fetch_call(rpc, |
48 self._FromBasePath(url), | 67 self._FromBasePath(url), |
49 headers=_MakeHeaders(username, | 68 headers=_MakeHeaders(username, |
50 password, | 69 password, |
51 access_token)) | 70 access_token)) |
52 return Future(callback=lambda: rpc.get_result()) | 71 return Future(callback=lambda: process_result(rpc.get_result())) |
53 | 72 |
54 def _FromBasePath(self, url): | 73 def _FromBasePath(self, url): |
55 assert not url.startswith('/'), url | 74 assert not url.startswith('/'), url |
56 if self._base_path is not None: | 75 if self._base_path is not None: |
57 url = posixpath.join(self._base_path, url) if url else self._base_path | 76 url = posixpath.join(self._base_path, url) if url else self._base_path |
58 return url | 77 return url |
OLD | NEW |