Set default HTTP timeout of 60s (#320)
diff --git a/describe.py b/describe.py
index 6d577de..e3881ae 100755
--- a/describe.py
+++ b/describe.py
@@ -34,7 +34,7 @@
from googleapiclient.discovery import build
from googleapiclient.discovery import build_from_document
from googleapiclient.discovery import UnknownApiNameOrVersion
-import httplib2
+from googleapiclient.http import build_http
import uritemplate
CSS = """<style>
@@ -346,6 +346,7 @@
print 'Warning: {} {} found but could not be built.'.format(name, version)
return
+ http = build_http()
response, content = http.request(
uritemplate.expand(
FLAGS.discovery_uri_template, {
@@ -366,7 +367,7 @@
Args:
uri: string, URI of discovery document.
"""
- http = httplib2.Http()
+ http = build_http()
response, content = http.request(FLAGS.discovery_uri)
discovery = json.loads(content)
@@ -384,7 +385,7 @@
if FLAGS.discovery_uri:
document_api_from_discovery_document(FLAGS.discovery_uri)
else:
- http = httplib2.Http()
+ http = build_http()
resp, content = http.request(
FLAGS.directory_uri,
headers={'X-User-IP': '0.0.0.0'})
diff --git a/googleapiclient/_auth.py b/googleapiclient/_auth.py
index 044ed12..87d3709 100644
--- a/googleapiclient/_auth.py
+++ b/googleapiclient/_auth.py
@@ -14,8 +14,6 @@
"""Helpers for authentication using oauth2client or google-auth."""
-import httplib2
-
try:
import google.auth
import google.auth.credentials
@@ -31,6 +29,8 @@
except ImportError: # pragma: NO COVER
HAS_OAUTH2CLIENT = False
+from googleapiclient.http import build_http
+
def default_credentials():
"""Returns Application Default Credentials."""
@@ -86,6 +86,7 @@
"""
if HAS_GOOGLE_AUTH and isinstance(
credentials, google.auth.credentials.Credentials):
- return google_auth_httplib2.AuthorizedHttp(credentials)
+ return google_auth_httplib2.AuthorizedHttp(credentials,
+ http=build_http())
else:
- return credentials.authorize(httplib2.Http())
+ return credentials.authorize(build_http())
diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
index 74f0a09..6291f34 100644
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -61,6 +61,7 @@
from googleapiclient.errors import UnacceptableMimeTypeError
from googleapiclient.errors import UnknownApiNameOrVersion
from googleapiclient.errors import UnknownFileType
+from googleapiclient.http import build_http
from googleapiclient.http import BatchHttpRequest
from googleapiclient.http import HttpMock
from googleapiclient.http import HttpMockSequence
@@ -97,6 +98,7 @@
'version={apiVersion}')
DEFAULT_METHOD_DOC = 'A description of how to use this function'
HTTP_PAYLOAD_METHODS = frozenset(['PUT', 'POST', 'PATCH'])
+
_MEDIA_SIZE_BIT_SHIFTS = {'KB': 10, 'MB': 20, 'GB': 30, 'TB': 40}
BODY_PARAMETER_DEFAULT_VALUE = {
'description': 'The request body.',
@@ -213,7 +215,10 @@
'apiVersion': version
}
- discovery_http = http if http is not None else httplib2.Http()
+ if http is None:
+ discovery_http = build_http()
+ else:
+ discovery_http = http
for discovery_url in (discoveryServiceUrl, V2_DISCOVERY_URI,):
requested_url = uritemplate.expand(discovery_url, params)
@@ -366,7 +371,7 @@
# If the service doesn't require scopes then there is no need for
# authentication.
else:
- http = httplib2.Http()
+ http = build_http()
if model is None:
features = service.get('features', [])
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
index 0ef10b9..aece933 100644
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -80,6 +80,8 @@
_TOO_MANY_REQUESTS = 429
+DEFAULT_HTTP_TIMEOUT_SEC = 60
+
def _should_retry_response(resp_status, content):
"""Determines whether a response should be retried.
@@ -1732,3 +1734,21 @@
http.request = new_request
return http
+
+
+def build_http():
+ """Builds httplib2.Http object
+
+ Returns:
+ A httplib2.Http object, which is used to make http requests, and which has timeout set by default.
+ To override default timeout call
+
+ socket.setdefaulttimeout(timeout_in_sec)
+
+ before interacting with this method.
+ """
+ if socket.getdefaulttimeout() is not None:
+ http_timeout = socket.getdefaulttimeout()
+ else:
+ http_timeout = DEFAULT_HTTP_TIMEOUT_SEC
+ return httplib2.Http(timeout=http_timeout)
diff --git a/googleapiclient/sample_tools.py b/googleapiclient/sample_tools.py
index 2b4e7b4..5ed632d 100644
--- a/googleapiclient/sample_tools.py
+++ b/googleapiclient/sample_tools.py
@@ -23,10 +23,10 @@
import argparse
-import httplib2
import os
from googleapiclient import discovery
+from googleapiclient.http import build_http
from oauth2client import client
from oauth2client import file
from oauth2client import tools
@@ -88,7 +88,7 @@
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = tools.run_flow(flow, storage, flags)
- http = credentials.authorize(http = httplib2.Http())
+ http = credentials.authorize(http=build_http())
if discovery_filename is None:
# Construct a service object via the discovery service.
diff --git a/tests/test__auth.py b/tests/test__auth.py
index 6711ffe..68e7aae 100644
--- a/tests/test__auth.py
+++ b/tests/test__auth.py
@@ -66,10 +66,13 @@
def test_authorized_http(self):
credentials = mock.Mock(spec=google.auth.credentials.Credentials)
- http = _auth.authorized_http(credentials)
+ authorized_http = _auth.authorized_http(credentials)
- self.assertIsInstance(http, google_auth_httplib2.AuthorizedHttp)
- self.assertEqual(http.credentials, credentials)
+ self.assertIsInstance(authorized_http, google_auth_httplib2.AuthorizedHttp)
+ self.assertEqual(authorized_http.credentials, credentials)
+ self.assertIsInstance(authorized_http.http, httplib2.Http)
+ self.assertIsInstance(authorized_http.http.timeout, int)
+ self.assertGreater(authorized_http.http.timeout, 0)
class TestAuthWithOAuth2Client(unittest2.TestCase):
@@ -112,11 +115,14 @@
def test_authorized_http(self):
credentials = mock.Mock(spec=oauth2client.client.Credentials)
- http = _auth.authorized_http(credentials)
+ authorized_http = _auth.authorized_http(credentials)
- self.assertEqual(http, credentials.authorize.return_value)
- self.assertIsInstance(
- credentials.authorize.call_args[0][0], httplib2.Http)
+ http = credentials.authorize.call_args[0][0]
+
+ self.assertEqual(authorized_http, credentials.authorize.return_value)
+ self.assertIsInstance(http, httplib2.Http)
+ self.assertIsInstance(http.timeout, int)
+ self.assertGreater(http.timeout, 0)
class TestAuthWithoutAuth(unittest2.TestCase):
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 3e26db9..47adeb7 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -65,6 +65,7 @@
from googleapiclient.errors import UnacceptableMimeTypeError
from googleapiclient.errors import UnknownApiNameOrVersion
from googleapiclient.errors import UnknownFileType
+from googleapiclient.http import build_http
from googleapiclient.http import BatchHttpRequest
from googleapiclient.http import HttpMock
from googleapiclient.http import HttpMockSequence
@@ -402,13 +403,32 @@
discovery, base=base, credentials=self.MOCK_CREDENTIALS)
self.assertEquals("https://www.googleapis.com/plus/v1/", plus._baseUrl)
- def test_building_with_optional_http(self):
+ def test_building_with_optional_http_with_authorization(self):
discovery = open(datafile('plus.json')).read()
plus = build_from_document(
discovery, base="https://www.googleapis.com/",
credentials=self.MOCK_CREDENTIALS)
- self.assertIsInstance(
- plus._http, (httplib2.Http, google_auth_httplib2.AuthorizedHttp))
+
+ # plus service requires Authorization, hence we expect to see AuthorizedHttp object here
+ self.assertIsInstance(plus._http, google_auth_httplib2.AuthorizedHttp)
+ self.assertIsInstance(plus._http.http, httplib2.Http)
+ self.assertIsInstance(plus._http.http.timeout, int)
+ self.assertGreater(plus._http.http.timeout, 0)
+
+ def test_building_with_optional_http_with_no_authorization(self):
+ discovery = open(datafile('plus.json')).read()
+ # Cleanup auth field, so we would use plain http client
+ discovery = json.loads(discovery)
+ discovery['auth'] = {}
+ discovery = json.dumps(discovery)
+
+ plus = build_from_document(
+ discovery, base="https://www.googleapis.com/",
+ credentials=self.MOCK_CREDENTIALS)
+ # plus service requires Authorization
+ self.assertIsInstance(plus._http, httplib2.Http)
+ self.assertIsInstance(plus._http.timeout, int)
+ self.assertGreater(plus._http.timeout, 0)
def test_building_with_explicit_http(self):
http = HttpMock()
@@ -1316,7 +1336,7 @@
zoo_uri = util._add_query_parameter(zoo_uri, 'userIp',
os.environ['REMOTE_ADDR'])
- http = httplib2.Http()
+ http = build_http()
original_request = http.request
def wrapped_request(uri, method='GET', *args, **kwargs):
if uri == zoo_uri:
diff --git a/tests/test_http.py b/tests/test_http.py
index 36b43bf..512e8e1 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -43,6 +43,7 @@
from googleapiclient.errors import BatchError
from googleapiclient.errors import HttpError
from googleapiclient.errors import InvalidChunkSizeError
+from googleapiclient.http import build_http
from googleapiclient.http import BatchHttpRequest
from googleapiclient.http import HttpMock
from googleapiclient.http import HttpMockSequence
@@ -235,7 +236,7 @@
def _postproc(*kwargs):
pass
- http = httplib2.Http()
+ http = build_http()
media_upload = MediaFileUpload(
datafile('small.png'), chunksize=500, resumable=True)
req = HttpRequest(
@@ -1341,6 +1342,34 @@
self.assertRaises(HttpError, request.execute)
+class TestHttpBuild(unittest.TestCase):
+ original_socket_default_timeout = None
+
+ @classmethod
+ def setUpClass(cls):
+ cls.original_socket_default_timeout = socket.getdefaulttimeout()
+
+ @classmethod
+ def tearDownClass(cls):
+ socket.setdefaulttimeout(cls.original_socket_default_timeout)
+
+ def test_build_http_sets_default_timeout_if_none_specified(self):
+ socket.setdefaulttimeout(None)
+ http = build_http()
+ self.assertIsInstance(http.timeout, int)
+ self.assertGreater(http.timeout, 0)
+
+ def test_build_http_default_timeout_can_be_overridden(self):
+ socket.setdefaulttimeout(1.5)
+ http = build_http()
+ self.assertAlmostEqual(http.timeout, 1.5, delta=0.001)
+
+ def test_build_http_default_timeout_can_be_set_to_zero(self):
+ socket.setdefaulttimeout(0)
+ http = build_http()
+ self.assertEquals(http.timeout, 0)
+
+
if __name__ == '__main__':
logging.getLogger().setLevel(logging.ERROR)
unittest.main()