Path: blob/master/venv/Lib/site-packages/urllib3/contrib/appengine.py
811 views
"""1This module provides a pool manager that uses Google App Engine's2`URLFetch Service <https://cloud.google.com/appengine/docs/python/urlfetch>`_.34Example usage::56from urllib3 import PoolManager7from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox89if is_appengine_sandbox():10# AppEngineManager uses AppEngine's URLFetch API behind the scenes11http = AppEngineManager()12else:13# PoolManager uses a socket-level API behind the scenes14http = PoolManager()1516r = http.request('GET', 'https://google.com/')1718There are `limitations <https://cloud.google.com/appengine/docs/python/\19urlfetch/#Python_Quotas_and_limits>`_ to the URLFetch service and it may not be20the best choice for your application. There are three options for using21urllib3 on Google App Engine:22231. You can use :class:`AppEngineManager` with URLFetch. URLFetch is24cost-effective in many circumstances as long as your usage is within the25limitations.262. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets.27Sockets also have `limitations and restrictions28<https://cloud.google.com/appengine/docs/python/sockets/\29#limitations-and-restrictions>`_ and have a lower free quota than URLFetch.30To use sockets, be sure to specify the following in your ``app.yaml``::3132env_variables:33GAE_USE_SOCKETS_HTTPLIB : 'true'34353. If you are using `App Engine Flexible36<https://cloud.google.com/appengine/docs/flexible/>`_, you can use the standard37:class:`PoolManager` without any configuration or special environment variables.38"""3940from __future__ import absolute_import41import io42import logging43import warnings44from ..packages.six.moves.urllib.parse import urljoin4546from ..exceptions import (47HTTPError,48HTTPWarning,49MaxRetryError,50ProtocolError,51TimeoutError,52SSLError,53)5455from ..request import RequestMethods56from ..response import HTTPResponse57from ..util.timeout import Timeout58from ..util.retry import Retry59from . import _appengine_environ6061try:62from google.appengine.api import urlfetch63except ImportError:64urlfetch = None656667log = logging.getLogger(__name__)686970class AppEnginePlatformWarning(HTTPWarning):71pass727374class AppEnginePlatformError(HTTPError):75pass767778class AppEngineManager(RequestMethods):79"""80Connection manager for Google App Engine sandbox applications.8182This manager uses the URLFetch service directly instead of using the83emulated httplib, and is subject to URLFetch limitations as described in84the App Engine documentation `here85<https://cloud.google.com/appengine/docs/python/urlfetch>`_.8687Notably it will raise an :class:`AppEnginePlatformError` if:88* URLFetch is not available.89* If you attempt to use this on App Engine Flexible, as full socket90support is available.91* If a request size is more than 10 megabytes.92* If a response size is more than 32 megabtyes.93* If you use an unsupported request method such as OPTIONS.9495Beyond those cases, it will raise normal urllib3 errors.96"""9798def __init__(99self,100headers=None,101retries=None,102validate_certificate=True,103urlfetch_retries=True,104):105if not urlfetch:106raise AppEnginePlatformError(107"URLFetch is not available in this environment."108)109110warnings.warn(111"urllib3 is using URLFetch on Google App Engine sandbox instead "112"of sockets. To use sockets directly instead of URLFetch see "113"https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.",114AppEnginePlatformWarning,115)116117RequestMethods.__init__(self, headers)118self.validate_certificate = validate_certificate119self.urlfetch_retries = urlfetch_retries120121self.retries = retries or Retry.DEFAULT122123def __enter__(self):124return self125126def __exit__(self, exc_type, exc_val, exc_tb):127# Return False to re-raise any potential exceptions128return False129130def urlopen(131self,132method,133url,134body=None,135headers=None,136retries=None,137redirect=True,138timeout=Timeout.DEFAULT_TIMEOUT,139**response_kw140):141142retries = self._get_retries(retries, redirect)143144try:145follow_redirects = redirect and retries.redirect != 0 and retries.total146response = urlfetch.fetch(147url,148payload=body,149method=method,150headers=headers or {},151allow_truncated=False,152follow_redirects=self.urlfetch_retries and follow_redirects,153deadline=self._get_absolute_timeout(timeout),154validate_certificate=self.validate_certificate,155)156except urlfetch.DeadlineExceededError as e:157raise TimeoutError(self, e)158159except urlfetch.InvalidURLError as e:160if "too large" in str(e):161raise AppEnginePlatformError(162"URLFetch request too large, URLFetch only "163"supports requests up to 10mb in size.",164e,165)166raise ProtocolError(e)167168except urlfetch.DownloadError as e:169if "Too many redirects" in str(e):170raise MaxRetryError(self, url, reason=e)171raise ProtocolError(e)172173except urlfetch.ResponseTooLargeError as e:174raise AppEnginePlatformError(175"URLFetch response too large, URLFetch only supports"176"responses up to 32mb in size.",177e,178)179180except urlfetch.SSLCertificateError as e:181raise SSLError(e)182183except urlfetch.InvalidMethodError as e:184raise AppEnginePlatformError(185"URLFetch does not support method: %s" % method, e186)187188http_response = self._urlfetch_response_to_http_response(189response, retries=retries, **response_kw190)191192# Handle redirect?193redirect_location = redirect and http_response.get_redirect_location()194if redirect_location:195# Check for redirect response196if self.urlfetch_retries and retries.raise_on_redirect:197raise MaxRetryError(self, url, "too many redirects")198else:199if http_response.status == 303:200method = "GET"201202try:203retries = retries.increment(204method, url, response=http_response, _pool=self205)206except MaxRetryError:207if retries.raise_on_redirect:208raise MaxRetryError(self, url, "too many redirects")209return http_response210211retries.sleep_for_retry(http_response)212log.debug("Redirecting %s -> %s", url, redirect_location)213redirect_url = urljoin(url, redirect_location)214return self.urlopen(215method,216redirect_url,217body,218headers,219retries=retries,220redirect=redirect,221timeout=timeout,222**response_kw223)224225# Check if we should retry the HTTP response.226has_retry_after = bool(http_response.getheader("Retry-After"))227if retries.is_retry(method, http_response.status, has_retry_after):228retries = retries.increment(method, url, response=http_response, _pool=self)229log.debug("Retry: %s", url)230retries.sleep(http_response)231return self.urlopen(232method,233url,234body=body,235headers=headers,236retries=retries,237redirect=redirect,238timeout=timeout,239**response_kw240)241242return http_response243244def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):245246if is_prod_appengine():247# Production GAE handles deflate encoding automatically, but does248# not remove the encoding header.249content_encoding = urlfetch_resp.headers.get("content-encoding")250251if content_encoding == "deflate":252del urlfetch_resp.headers["content-encoding"]253254transfer_encoding = urlfetch_resp.headers.get("transfer-encoding")255# We have a full response's content,256# so let's make sure we don't report ourselves as chunked data.257if transfer_encoding == "chunked":258encodings = transfer_encoding.split(",")259encodings.remove("chunked")260urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings)261262original_response = HTTPResponse(263# In order for decoding to work, we must present the content as264# a file-like object.265body=io.BytesIO(urlfetch_resp.content),266msg=urlfetch_resp.header_msg,267headers=urlfetch_resp.headers,268status=urlfetch_resp.status_code,269**response_kw270)271272return HTTPResponse(273body=io.BytesIO(urlfetch_resp.content),274headers=urlfetch_resp.headers,275status=urlfetch_resp.status_code,276original_response=original_response,277**response_kw278)279280def _get_absolute_timeout(self, timeout):281if timeout is Timeout.DEFAULT_TIMEOUT:282return None # Defer to URLFetch's default.283if isinstance(timeout, Timeout):284if timeout._read is not None or timeout._connect is not None:285warnings.warn(286"URLFetch does not support granular timeout settings, "287"reverting to total or default URLFetch timeout.",288AppEnginePlatformWarning,289)290return timeout.total291return timeout292293def _get_retries(self, retries, redirect):294if not isinstance(retries, Retry):295retries = Retry.from_int(retries, redirect=redirect, default=self.retries)296297if retries.connect or retries.read or retries.redirect:298warnings.warn(299"URLFetch only supports total retries and does not "300"recognize connect, read, or redirect retry parameters.",301AppEnginePlatformWarning,302)303304return retries305306307# Alias methods from _appengine_environ to maintain public API interface.308309is_appengine = _appengine_environ.is_appengine310is_appengine_sandbox = _appengine_environ.is_appengine_sandbox311is_local_appengine = _appengine_environ.is_local_appengine312is_prod_appengine = _appengine_environ.is_prod_appengine313is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms314315316