Index: third_party/google-endpoints/requests/packages/urllib3/contrib/appengine.py |
diff --git a/third_party/google-endpoints/requests/packages/urllib3/contrib/appengine.py b/third_party/google-endpoints/requests/packages/urllib3/contrib/appengine.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..814b0222d9a4c0249c61431f57aaa76326ac963a |
--- /dev/null |
+++ b/third_party/google-endpoints/requests/packages/urllib3/contrib/appengine.py |
@@ -0,0 +1,296 @@ |
+""" |
+This module provides a pool manager that uses Google App Engine's |
+`URLFetch Service <https://cloud.google.com/appengine/docs/python/urlfetch>`_. |
+ |
+Example usage:: |
+ |
+ from urllib3 import PoolManager |
+ from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox |
+ |
+ if is_appengine_sandbox(): |
+ # AppEngineManager uses AppEngine's URLFetch API behind the scenes |
+ http = AppEngineManager() |
+ else: |
+ # PoolManager uses a socket-level API behind the scenes |
+ http = PoolManager() |
+ |
+ r = http.request('GET', 'https://google.com/') |
+ |
+There are `limitations <https://cloud.google.com/appengine/docs/python/\ |
+urlfetch/#Python_Quotas_and_limits>`_ to the URLFetch service and it may not be |
+the best choice for your application. There are three options for using |
+urllib3 on Google App Engine: |
+ |
+1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is |
+ cost-effective in many circumstances as long as your usage is within the |
+ limitations. |
+2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. |
+ Sockets also have `limitations and restrictions |
+ <https://cloud.google.com/appengine/docs/python/sockets/\ |
+ #limitations-and-restrictions>`_ and have a lower free quota than URLFetch. |
+ To use sockets, be sure to specify the following in your ``app.yaml``:: |
+ |
+ env_variables: |
+ GAE_USE_SOCKETS_HTTPLIB : 'true' |
+ |
+3. If you are using `App Engine Flexible |
+<https://cloud.google.com/appengine/docs/flexible/>`_, you can use the standard |
+:class:`PoolManager` without any configuration or special environment variables. |
+""" |
+ |
+from __future__ import absolute_import |
+import logging |
+import os |
+import warnings |
+from ..packages.six.moves.urllib.parse import urljoin |
+ |
+from ..exceptions import ( |
+ HTTPError, |
+ HTTPWarning, |
+ MaxRetryError, |
+ ProtocolError, |
+ TimeoutError, |
+ SSLError |
+) |
+ |
+from ..packages.six import BytesIO |
+from ..request import RequestMethods |
+from ..response import HTTPResponse |
+from ..util.timeout import Timeout |
+from ..util.retry import Retry |
+ |
+try: |
+ from google.appengine.api import urlfetch |
+except ImportError: |
+ urlfetch = None |
+ |
+ |
+log = logging.getLogger(__name__) |
+ |
+ |
+class AppEnginePlatformWarning(HTTPWarning): |
+ pass |
+ |
+ |
+class AppEnginePlatformError(HTTPError): |
+ pass |
+ |
+ |
+class AppEngineManager(RequestMethods): |
+ """ |
+ Connection manager for Google App Engine sandbox applications. |
+ |
+ This manager uses the URLFetch service directly instead of using the |
+ emulated httplib, and is subject to URLFetch limitations as described in |
+ the App Engine documentation `here |
+ <https://cloud.google.com/appengine/docs/python/urlfetch>`_. |
+ |
+ Notably it will raise an :class:`AppEnginePlatformError` if: |
+ * URLFetch is not available. |
+ * If you attempt to use this on App Engine Flexible, as full socket |
+ support is available. |
+ * If a request size is more than 10 megabytes. |
+ * If a response size is more than 32 megabtyes. |
+ * If you use an unsupported request method such as OPTIONS. |
+ |
+ Beyond those cases, it will raise normal urllib3 errors. |
+ """ |
+ |
+ def __init__(self, headers=None, retries=None, validate_certificate=True, |
+ urlfetch_retries=True): |
+ if not urlfetch: |
+ raise AppEnginePlatformError( |
+ "URLFetch is not available in this environment.") |
+ |
+ if is_prod_appengine_mvms(): |
+ raise AppEnginePlatformError( |
+ "Use normal urllib3.PoolManager instead of AppEngineManager" |
+ "on Managed VMs, as using URLFetch is not necessary in " |
+ "this environment.") |
+ |
+ warnings.warn( |
+ "urllib3 is using URLFetch on Google App Engine sandbox instead " |
+ "of sockets. To use sockets directly instead of URLFetch see " |
+ "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", |
+ AppEnginePlatformWarning) |
+ |
+ RequestMethods.__init__(self, headers) |
+ self.validate_certificate = validate_certificate |
+ self.urlfetch_retries = urlfetch_retries |
+ |
+ self.retries = retries or Retry.DEFAULT |
+ |
+ def __enter__(self): |
+ return self |
+ |
+ def __exit__(self, exc_type, exc_val, exc_tb): |
+ # Return False to re-raise any potential exceptions |
+ return False |
+ |
+ def urlopen(self, method, url, body=None, headers=None, |
+ retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, |
+ **response_kw): |
+ |
+ retries = self._get_retries(retries, redirect) |
+ |
+ try: |
+ follow_redirects = ( |
+ redirect and |
+ retries.redirect != 0 and |
+ retries.total) |
+ response = urlfetch.fetch( |
+ url, |
+ payload=body, |
+ method=method, |
+ headers=headers or {}, |
+ allow_truncated=False, |
+ follow_redirects=self.urlfetch_retries and follow_redirects, |
+ deadline=self._get_absolute_timeout(timeout), |
+ validate_certificate=self.validate_certificate, |
+ ) |
+ except urlfetch.DeadlineExceededError as e: |
+ raise TimeoutError(self, e) |
+ |
+ except urlfetch.InvalidURLError as e: |
+ if 'too large' in str(e): |
+ raise AppEnginePlatformError( |
+ "URLFetch request too large, URLFetch only " |
+ "supports requests up to 10mb in size.", e) |
+ raise ProtocolError(e) |
+ |
+ except urlfetch.DownloadError as e: |
+ if 'Too many redirects' in str(e): |
+ raise MaxRetryError(self, url, reason=e) |
+ raise ProtocolError(e) |
+ |
+ except urlfetch.ResponseTooLargeError as e: |
+ raise AppEnginePlatformError( |
+ "URLFetch response too large, URLFetch only supports" |
+ "responses up to 32mb in size.", e) |
+ |
+ except urlfetch.SSLCertificateError as e: |
+ raise SSLError(e) |
+ |
+ except urlfetch.InvalidMethodError as e: |
+ raise AppEnginePlatformError( |
+ "URLFetch does not support method: %s" % method, e) |
+ |
+ http_response = self._urlfetch_response_to_http_response( |
+ response, retries=retries, **response_kw) |
+ |
+ # Handle redirect? |
+ redirect_location = redirect and http_response.get_redirect_location() |
+ if redirect_location: |
+ # Check for redirect response |
+ if (self.urlfetch_retries and retries.raise_on_redirect): |
+ raise MaxRetryError(self, url, "too many redirects") |
+ else: |
+ if http_response.status == 303: |
+ method = 'GET' |
+ |
+ try: |
+ retries = retries.increment(method, url, response=http_response, _pool=self) |
+ except MaxRetryError: |
+ if retries.raise_on_redirect: |
+ raise MaxRetryError(self, url, "too many redirects") |
+ return http_response |
+ |
+ retries.sleep_for_retry(http_response) |
+ log.debug("Redirecting %s -> %s", url, redirect_location) |
+ redirect_url = urljoin(url, redirect_location) |
+ return self.urlopen( |
+ method, redirect_url, body, headers, |
+ retries=retries, redirect=redirect, |
+ timeout=timeout, **response_kw) |
+ |
+ # Check if we should retry the HTTP response. |
+ has_retry_after = bool(http_response.getheader('Retry-After')) |
+ if retries.is_retry(method, http_response.status, has_retry_after): |
+ retries = retries.increment( |
+ method, url, response=http_response, _pool=self) |
+ log.debug("Retry: %s", url) |
+ retries.sleep(http_response) |
+ return self.urlopen( |
+ method, url, |
+ body=body, headers=headers, |
+ retries=retries, redirect=redirect, |
+ timeout=timeout, **response_kw) |
+ |
+ return http_response |
+ |
+ def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): |
+ |
+ if is_prod_appengine(): |
+ # Production GAE handles deflate encoding automatically, but does |
+ # not remove the encoding header. |
+ content_encoding = urlfetch_resp.headers.get('content-encoding') |
+ |
+ if content_encoding == 'deflate': |
+ del urlfetch_resp.headers['content-encoding'] |
+ |
+ transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') |
+ # We have a full response's content, |
+ # so let's make sure we don't report ourselves as chunked data. |
+ if transfer_encoding == 'chunked': |
+ encodings = transfer_encoding.split(",") |
+ encodings.remove('chunked') |
+ urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) |
+ |
+ return HTTPResponse( |
+ # In order for decoding to work, we must present the content as |
+ # a file-like object. |
+ body=BytesIO(urlfetch_resp.content), |
+ headers=urlfetch_resp.headers, |
+ status=urlfetch_resp.status_code, |
+ **response_kw |
+ ) |
+ |
+ def _get_absolute_timeout(self, timeout): |
+ if timeout is Timeout.DEFAULT_TIMEOUT: |
+ return None # Defer to URLFetch's default. |
+ if isinstance(timeout, Timeout): |
+ if timeout._read is not None or timeout._connect is not None: |
+ warnings.warn( |
+ "URLFetch does not support granular timeout settings, " |
+ "reverting to total or default URLFetch timeout.", |
+ AppEnginePlatformWarning) |
+ return timeout.total |
+ return timeout |
+ |
+ def _get_retries(self, retries, redirect): |
+ if not isinstance(retries, Retry): |
+ retries = Retry.from_int( |
+ retries, redirect=redirect, default=self.retries) |
+ |
+ if retries.connect or retries.read or retries.redirect: |
+ warnings.warn( |
+ "URLFetch only supports total retries and does not " |
+ "recognize connect, read, or redirect retry parameters.", |
+ AppEnginePlatformWarning) |
+ |
+ return retries |
+ |
+ |
+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' |