Plex plugin to to play various online streams (mostly Latvian).

appengine.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. from __future__ import absolute_import
  2. import logging
  3. import os
  4. import warnings
  5. from ..exceptions import (
  6. HTTPError,
  7. HTTPWarning,
  8. MaxRetryError,
  9. ProtocolError,
  10. TimeoutError,
  11. SSLError
  12. )
  13. from ..packages.six import BytesIO
  14. from ..request import RequestMethods
  15. from ..response import HTTPResponse
  16. from ..util.timeout import Timeout
  17. from ..util.retry import Retry
  18. try:
  19. from google.appengine.api import urlfetch
  20. except ImportError:
  21. urlfetch = None
  22. log = logging.getLogger(__name__)
  23. class AppEnginePlatformWarning(HTTPWarning):
  24. pass
  25. class AppEnginePlatformError(HTTPError):
  26. pass
  27. class AppEngineManager(RequestMethods):
  28. """
  29. Connection manager for Google App Engine sandbox applications.
  30. This manager uses the URLFetch service directly instead of using the
  31. emulated httplib, and is subject to URLFetch limitations as described in
  32. the App Engine documentation here:
  33. https://cloud.google.com/appengine/docs/python/urlfetch
  34. Notably it will raise an AppEnginePlatformError if:
  35. * URLFetch is not available.
  36. * If you attempt to use this on GAEv2 (Managed VMs), as full socket
  37. support is available.
  38. * If a request size is more than 10 megabytes.
  39. * If a response size is more than 32 megabtyes.
  40. * If you use an unsupported request method such as OPTIONS.
  41. Beyond those cases, it will raise normal urllib3 errors.
  42. """
  43. def __init__(self, headers=None, retries=None, validate_certificate=True):
  44. if not urlfetch:
  45. raise AppEnginePlatformError(
  46. "URLFetch is not available in this environment.")
  47. if is_prod_appengine_mvms():
  48. raise AppEnginePlatformError(
  49. "Use normal urllib3.PoolManager instead of AppEngineManager"
  50. "on Managed VMs, as using URLFetch is not necessary in "
  51. "this environment.")
  52. warnings.warn(
  53. "urllib3 is using URLFetch on Google App Engine sandbox instead "
  54. "of sockets. To use sockets directly instead of URLFetch see "
  55. "https://urllib3.readthedocs.io/en/latest/contrib.html.",
  56. AppEnginePlatformWarning)
  57. RequestMethods.__init__(self, headers)
  58. self.validate_certificate = validate_certificate
  59. self.retries = retries or Retry.DEFAULT
  60. def __enter__(self):
  61. return self
  62. def __exit__(self, exc_type, exc_val, exc_tb):
  63. # Return False to re-raise any potential exceptions
  64. return False
  65. def urlopen(self, method, url, body=None, headers=None,
  66. retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT,
  67. **response_kw):
  68. retries = self._get_retries(retries, redirect)
  69. try:
  70. response = urlfetch.fetch(
  71. url,
  72. payload=body,
  73. method=method,
  74. headers=headers or {},
  75. allow_truncated=False,
  76. follow_redirects=(
  77. redirect and
  78. retries.redirect != 0 and
  79. retries.total),
  80. deadline=self._get_absolute_timeout(timeout),
  81. validate_certificate=self.validate_certificate,
  82. )
  83. except urlfetch.DeadlineExceededError as e:
  84. raise TimeoutError(self, e)
  85. except urlfetch.InvalidURLError as e:
  86. if 'too large' in str(e):
  87. raise AppEnginePlatformError(
  88. "URLFetch request too large, URLFetch only "
  89. "supports requests up to 10mb in size.", e)
  90. raise ProtocolError(e)
  91. except urlfetch.DownloadError as e:
  92. if 'Too many redirects' in str(e):
  93. raise MaxRetryError(self, url, reason=e)
  94. raise ProtocolError(e)
  95. except urlfetch.ResponseTooLargeError as e:
  96. raise AppEnginePlatformError(
  97. "URLFetch response too large, URLFetch only supports"
  98. "responses up to 32mb in size.", e)
  99. except urlfetch.SSLCertificateError as e:
  100. raise SSLError(e)
  101. except urlfetch.InvalidMethodError as e:
  102. raise AppEnginePlatformError(
  103. "URLFetch does not support method: %s" % method, e)
  104. http_response = self._urlfetch_response_to_http_response(
  105. response, **response_kw)
  106. # Check for redirect response
  107. if (http_response.get_redirect_location() and
  108. retries.raise_on_redirect and redirect):
  109. raise MaxRetryError(self, url, "too many redirects")
  110. # Check if we should retry the HTTP response.
  111. if retries.is_forced_retry(method, status_code=http_response.status):
  112. retries = retries.increment(
  113. method, url, response=http_response, _pool=self)
  114. log.info("Forced retry: %s", url)
  115. retries.sleep()
  116. return self.urlopen(
  117. method, url,
  118. body=body, headers=headers,
  119. retries=retries, redirect=redirect,
  120. timeout=timeout, **response_kw)
  121. return http_response
  122. def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
  123. if is_prod_appengine():
  124. # Production GAE handles deflate encoding automatically, but does
  125. # not remove the encoding header.
  126. content_encoding = urlfetch_resp.headers.get('content-encoding')
  127. if content_encoding == 'deflate':
  128. del urlfetch_resp.headers['content-encoding']
  129. transfer_encoding = urlfetch_resp.headers.get('transfer-encoding')
  130. # We have a full response's content,
  131. # so let's make sure we don't report ourselves as chunked data.
  132. if transfer_encoding == 'chunked':
  133. encodings = transfer_encoding.split(",")
  134. encodings.remove('chunked')
  135. urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings)
  136. return HTTPResponse(
  137. # In order for decoding to work, we must present the content as
  138. # a file-like object.
  139. body=BytesIO(urlfetch_resp.content),
  140. headers=urlfetch_resp.headers,
  141. status=urlfetch_resp.status_code,
  142. **response_kw
  143. )
  144. def _get_absolute_timeout(self, timeout):
  145. if timeout is Timeout.DEFAULT_TIMEOUT:
  146. return 5 # 5s is the default timeout for URLFetch.
  147. if isinstance(timeout, Timeout):
  148. if timeout._read is not timeout._connect:
  149. warnings.warn(
  150. "URLFetch does not support granular timeout settings, "
  151. "reverting to total timeout.", AppEnginePlatformWarning)
  152. return timeout.total
  153. return timeout
  154. def _get_retries(self, retries, redirect):
  155. if not isinstance(retries, Retry):
  156. retries = Retry.from_int(
  157. retries, redirect=redirect, default=self.retries)
  158. if retries.connect or retries.read or retries.redirect:
  159. warnings.warn(
  160. "URLFetch only supports total retries and does not "
  161. "recognize connect, read, or redirect retry parameters.",
  162. AppEnginePlatformWarning)
  163. return retries
  164. def is_appengine():
  165. return (is_local_appengine() or
  166. is_prod_appengine() or
  167. is_prod_appengine_mvms())
  168. def is_appengine_sandbox():
  169. return is_appengine() and not is_prod_appengine_mvms()
  170. def is_local_appengine():
  171. return ('APPENGINE_RUNTIME' in os.environ and
  172. 'Development/' in os.environ['SERVER_SOFTWARE'])
  173. def is_prod_appengine():
  174. return ('APPENGINE_RUNTIME' in os.environ and
  175. 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and
  176. not is_prod_appengine_mvms())
  177. def is_prod_appengine_mvms():
  178. return os.environ.get('GAE_VM', False) == 'true'