diff --git a/src/urllib3/contrib/_appengine_environ.py b/src/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000000..f3e00942cb --- /dev/null +++ b/src/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,30 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return (is_local_appengine() or + is_prod_appengine() or + is_prod_appengine_mvms()) + + +def is_appengine_sandbox(): + return is_appengine() and not is_prod_appengine_mvms() + + +def is_local_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) + + +def is_prod_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and + not is_prod_appengine_mvms()) + + +def is_prod_appengine_mvms(): + return os.environ.get('GAE_VM', False) == 'true' diff --git a/src/urllib3/contrib/appengine.py b/src/urllib3/contrib/appengine.py index b2951cf8eb..2952f114df 100644 --- a/src/urllib3/contrib/appengine.py +++ b/src/urllib3/contrib/appengine.py @@ -41,7 +41,6 @@ from __future__ import absolute_import import io import logging -import os import warnings from ..packages.six.moves.urllib.parse import urljoin @@ -58,6 +57,7 @@ from ..response import HTTPResponse from ..util.timeout import Timeout from ..util.retry import Retry +from . import _appengine_environ try: from google.appengine.api import urlfetch @@ -280,26 +280,10 @@ def _get_retries(self, retries, redirect): return retries -def is_appengine(): - return (is_local_appengine() or - is_prod_appengine() or - is_prod_appengine_mvms()) +# Alias methods from _appengine_environ to maintain public API interface. - -def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_mvms() - - -def is_local_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) - - -def is_prod_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_mvms()) - - -def is_prod_appengine_mvms(): - return os.environ.get('GAE_VM', False) == 'true' +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/src/urllib3/util/connection.py b/src/urllib3/util/connection.py index 5cf488f4b5..5ad70b2f1c 100644 --- a/src/urllib3/util/connection.py +++ b/src/urllib3/util/connection.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import socket from .wait import NoWayToWaitForSocketError, wait_for_read +from ..contrib import _appengine_environ def is_connection_dropped(conn): # Platform-specific @@ -105,6 +106,13 @@ def _has_ipv6(host): sock = None has_ipv6 = False + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + if socket.has_ipv6: # has_ipv6 returns true if cPython was compiled with IPv6 support. # It does not tell us if the system has IPv6 support enabled. To diff --git a/test/test_util.py b/test/test_util.py index 73d9452a48..a0c99a163e 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -540,6 +540,13 @@ def test_has_ipv6_enabled_and_working(self): instance.bind.return_value = True assert _has_ipv6('::1') + def test_has_ipv6_disabled_on_appengine(self): + gae_patch = patch( + 'urllib3.contrib._appengine_environ.is_appengine_sandbox', + return_value=True) + with gae_patch: + assert not _has_ipv6('::1') + def test_ip_family_ipv6_enabled(self): with patch('urllib3.util.connection.HAS_IPV6', True): assert allowed_gai_family() == socket.AF_UNSPEC