appengine.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # -*- coding: utf-8 -*-
  2. """The App Engine Transport Adapter for requests.
  3. .. versionadded:: 0.6.0
  4. This requires a version of requests >= 2.10.0 and Python 2.
  5. There are two ways to use this library:
  6. #. If you're using requests directly, you can use code like:
  7. .. code-block:: python
  8. >>> import requests
  9. >>> import ssl
  10. >>> import requests.packages.urllib3.contrib.appengine as ul_appengine
  11. >>> from requests_toolbelt.adapters import appengine
  12. >>> s = requests.Session()
  13. >>> if ul_appengine.is_appengine_sandbox():
  14. ... s.mount('http://', appengine.AppEngineAdapter())
  15. ... s.mount('https://', appengine.AppEngineAdapter())
  16. #. If you depend on external libraries which use requests, you can use code
  17. like:
  18. .. code-block:: python
  19. >>> from requests_toolbelt.adapters import appengine
  20. >>> appengine.monkeypatch()
  21. which will ensure all requests.Session objects use AppEngineAdapter properly.
  22. You are also able to :ref:`disable certificate validation <insecure_appengine>`
  23. when monkey-patching.
  24. """
  25. import requests
  26. import warnings
  27. from requests import adapters
  28. from requests import sessions
  29. from .. import exceptions as exc
  30. from .._compat import gaecontrib
  31. from .._compat import timeout
  32. class AppEngineMROHack(adapters.HTTPAdapter):
  33. """Resolves infinite recursion when monkeypatching.
  34. This works by injecting itself as the base class of both the
  35. :class:`AppEngineAdapter` and Requests' default HTTPAdapter, which needs to
  36. be done because default HTTPAdapter's MRO is recompiled when we
  37. monkeypatch, at which point this class becomes HTTPAdapter's base class.
  38. In addition, we use an instantiation flag to avoid infinite recursion.
  39. """
  40. _initialized = False
  41. def __init__(self, *args, **kwargs):
  42. if not self._initialized:
  43. self._initialized = True
  44. super(AppEngineMROHack, self).__init__(*args, **kwargs)
  45. class AppEngineAdapter(AppEngineMROHack, adapters.HTTPAdapter):
  46. """The transport adapter for Requests to use urllib3's GAE support.
  47. Implements Requests's HTTPAdapter API.
  48. When deploying to Google's App Engine service, some of Requests'
  49. functionality is broken. There is underlying support for GAE in urllib3.
  50. This functionality, however, is opt-in and needs to be enabled explicitly
  51. for Requests to be able to use it.
  52. """
  53. __attrs__ = adapters.HTTPAdapter.__attrs__ + ['_validate_certificate']
  54. def __init__(self, validate_certificate=True, *args, **kwargs):
  55. _check_version()
  56. self._validate_certificate = validate_certificate
  57. super(AppEngineAdapter, self).__init__(*args, **kwargs)
  58. def init_poolmanager(self, connections, maxsize, block=False):
  59. self.poolmanager = _AppEnginePoolManager(self._validate_certificate)
  60. class InsecureAppEngineAdapter(AppEngineAdapter):
  61. """An always-insecure GAE adapter for Requests.
  62. This is a variant of the the transport adapter for Requests to use
  63. urllib3's GAE support that does not validate certificates. Use with
  64. caution!
  65. .. note::
  66. The ``validate_certificate`` keyword argument will not be honored here
  67. and is not part of the signature because we always force it to
  68. ``False``.
  69. See :class:`AppEngineAdapter` for further details.
  70. """
  71. def __init__(self, *args, **kwargs):
  72. if kwargs.pop("validate_certificate", False):
  73. warnings.warn("Certificate validation cannot be specified on the "
  74. "InsecureAppEngineAdapter, but was present. This "
  75. "will be ignored and certificate validation will "
  76. "remain off.", exc.IgnoringGAECertificateValidation)
  77. super(InsecureAppEngineAdapter, self).__init__(
  78. validate_certificate=False, *args, **kwargs)
  79. class _AppEnginePoolManager(object):
  80. """Implements urllib3's PoolManager API expected by requests.
  81. While a real PoolManager map hostnames to reusable Connections,
  82. AppEngine has no concept of a reusable connection to a host.
  83. So instead, this class constructs a small Connection per request,
  84. that is returned to the Adapter and used to access the URL.
  85. """
  86. def __init__(self, validate_certificate=True):
  87. self.appengine_manager = gaecontrib.AppEngineManager(
  88. validate_certificate=validate_certificate)
  89. def connection_from_url(self, url):
  90. return _AppEngineConnection(self.appengine_manager, url)
  91. def clear(self):
  92. pass
  93. class _AppEngineConnection(object):
  94. """Implements urllib3's HTTPConnectionPool API's urlopen().
  95. This Connection's urlopen() is called with a host-relative path,
  96. so in order to properly support opening the URL, we need to store
  97. the full URL when this Connection is constructed from the PoolManager.
  98. This code wraps AppEngineManager.urlopen(), which exposes a different
  99. API than in the original urllib3 urlopen(), and thus needs this adapter.
  100. """
  101. def __init__(self, appengine_manager, url):
  102. self.appengine_manager = appengine_manager
  103. self.url = url
  104. def urlopen(self, method, url, body=None, headers=None, retries=None,
  105. redirect=True, assert_same_host=True,
  106. timeout=timeout.Timeout.DEFAULT_TIMEOUT,
  107. pool_timeout=None, release_conn=None, **response_kw):
  108. # This function's url argument is a host-relative URL,
  109. # but the AppEngineManager expects an absolute URL.
  110. # So we saved out the self.url when the AppEngineConnection
  111. # was constructed, which we then can use down below instead.
  112. # We once tried to verify our assumptions here, but sometimes the
  113. # passed-in URL differs on url fragments, or "http://a.com" vs "/".
  114. # urllib3's App Engine adapter only uses Timeout.total, not read or
  115. # connect.
  116. if not timeout.total:
  117. timeout.total = timeout._read or timeout._connect
  118. # Jump through the hoops necessary to call AppEngineManager's API.
  119. return self.appengine_manager.urlopen(
  120. method,
  121. self.url,
  122. body=body,
  123. headers=headers,
  124. retries=retries,
  125. redirect=redirect,
  126. timeout=timeout,
  127. **response_kw)
  128. def monkeypatch(validate_certificate=True):
  129. """Sets up all Sessions to use AppEngineAdapter by default.
  130. If you don't want to deal with configuring your own Sessions,
  131. or if you use libraries that use requests directly (ie requests.post),
  132. then you may prefer to monkeypatch and auto-configure all Sessions.
  133. .. warning: :
  134. If ``validate_certificate`` is ``False``, certification validation will
  135. effectively be disabled for all requests.
  136. """
  137. _check_version()
  138. # HACK: We should consider modifying urllib3 to support this cleanly,
  139. # so that we can set a module-level variable in the sessions module,
  140. # instead of overriding an imported HTTPAdapter as is done here.
  141. adapter = AppEngineAdapter
  142. if not validate_certificate:
  143. adapter = InsecureAppEngineAdapter
  144. sessions.HTTPAdapter = adapter
  145. adapters.HTTPAdapter = adapter
  146. def _check_version():
  147. if gaecontrib is None:
  148. raise exc.VersionMismatchError(
  149. "The toolbelt requires at least Requests 2.10.0 to be "
  150. "installed. Version {0} was found instead.".format(
  151. requests.__version__
  152. )
  153. )