connection.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. from __future__ import annotations
  2. import os
  3. import typing
  4. # use http.client.HTTPException for consistency with non-emscripten
  5. from http.client import HTTPException as HTTPException # noqa: F401
  6. from http.client import ResponseNotReady
  7. from ..._base_connection import _TYPE_BODY
  8. from ...connection import HTTPConnection, ProxyConfig, port_by_scheme
  9. from ...exceptions import TimeoutError
  10. from ...response import BaseHTTPResponse
  11. from ...util.connection import _TYPE_SOCKET_OPTIONS
  12. from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT
  13. from ...util.url import Url
  14. from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request
  15. from .request import EmscriptenRequest
  16. from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse
  17. if typing.TYPE_CHECKING:
  18. from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection
  19. class EmscriptenHTTPConnection:
  20. default_port: typing.ClassVar[int] = port_by_scheme["http"]
  21. default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS]
  22. timeout: None | (float)
  23. host: str
  24. port: int
  25. blocksize: int
  26. source_address: tuple[str, int] | None
  27. socket_options: _TYPE_SOCKET_OPTIONS | None
  28. proxy: Url | None
  29. proxy_config: ProxyConfig | None
  30. is_verified: bool = False
  31. proxy_is_verified: bool | None = None
  32. _response: EmscriptenResponse | None
  33. def __init__(
  34. self,
  35. host: str,
  36. port: int = 0,
  37. *,
  38. timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
  39. source_address: tuple[str, int] | None = None,
  40. blocksize: int = 8192,
  41. socket_options: _TYPE_SOCKET_OPTIONS | None = None,
  42. proxy: Url | None = None,
  43. proxy_config: ProxyConfig | None = None,
  44. ) -> None:
  45. self.host = host
  46. self.port = port
  47. self.timeout = timeout if isinstance(timeout, float) else 0.0
  48. self.scheme = "http"
  49. self._closed = True
  50. self._response = None
  51. # ignore these things because we don't
  52. # have control over that stuff
  53. self.proxy = None
  54. self.proxy_config = None
  55. self.blocksize = blocksize
  56. self.source_address = None
  57. self.socket_options = None
  58. self.is_verified = False
  59. def set_tunnel(
  60. self,
  61. host: str,
  62. port: int | None = 0,
  63. headers: typing.Mapping[str, str] | None = None,
  64. scheme: str = "http",
  65. ) -> None:
  66. pass
  67. def connect(self) -> None:
  68. pass
  69. def request(
  70. self,
  71. method: str,
  72. url: str,
  73. body: _TYPE_BODY | None = None,
  74. headers: typing.Mapping[str, str] | None = None,
  75. # We know *at least* botocore is depending on the order of the
  76. # first 3 parameters so to be safe we only mark the later ones
  77. # as keyword-only to ensure we have space to extend.
  78. *,
  79. chunked: bool = False,
  80. preload_content: bool = True,
  81. decode_content: bool = True,
  82. enforce_content_length: bool = True,
  83. ) -> None:
  84. self._closed = False
  85. if url.startswith("/"):
  86. # no scheme / host / port included, make a full url
  87. url = f"{self.scheme}://{self.host}:{self.port}" + url
  88. request = EmscriptenRequest(
  89. url=url,
  90. method=method,
  91. timeout=self.timeout if self.timeout else 0,
  92. decode_content=decode_content,
  93. )
  94. request.set_body(body)
  95. if headers:
  96. for k, v in headers.items():
  97. request.set_header(k, v)
  98. self._response = None
  99. try:
  100. if not preload_content:
  101. self._response = send_streaming_request(request)
  102. if self._response is None:
  103. self._response = send_request(request)
  104. except _TimeoutError as e:
  105. raise TimeoutError(e.message) from e
  106. except _RequestError as e:
  107. raise HTTPException(e.message) from e
  108. def getresponse(self) -> BaseHTTPResponse:
  109. if self._response is not None:
  110. return EmscriptenHttpResponseWrapper(
  111. internal_response=self._response,
  112. url=self._response.request.url,
  113. connection=self,
  114. )
  115. else:
  116. raise ResponseNotReady()
  117. def close(self) -> None:
  118. self._closed = True
  119. self._response = None
  120. @property
  121. def is_closed(self) -> bool:
  122. """Whether the connection either is brand new or has been previously closed.
  123. If this property is True then both ``is_connected`` and ``has_connected_to_proxy``
  124. properties must be False.
  125. """
  126. return self._closed
  127. @property
  128. def is_connected(self) -> bool:
  129. """Whether the connection is actively connected to any origin (proxy or target)"""
  130. return True
  131. @property
  132. def has_connected_to_proxy(self) -> bool:
  133. """Whether the connection has successfully connected to its proxy.
  134. This returns False if no proxy is in use. Used to determine whether
  135. errors are coming from the proxy layer or from tunnelling to the target origin.
  136. """
  137. return False
  138. class EmscriptenHTTPSConnection(EmscriptenHTTPConnection):
  139. default_port = port_by_scheme["https"]
  140. # all this is basically ignored, as browser handles https
  141. cert_reqs: int | str | None = None
  142. ca_certs: str | None = None
  143. ca_cert_dir: str | None = None
  144. ca_cert_data: None | str | bytes = None
  145. cert_file: str | None
  146. key_file: str | None
  147. key_password: str | None
  148. ssl_context: typing.Any | None
  149. ssl_version: int | str | None = None
  150. ssl_minimum_version: int | None = None
  151. ssl_maximum_version: int | None = None
  152. assert_hostname: None | str | typing.Literal[False]
  153. assert_fingerprint: str | None = None
  154. def __init__(
  155. self,
  156. host: str,
  157. port: int = 0,
  158. *,
  159. timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
  160. source_address: tuple[str, int] | None = None,
  161. blocksize: int = 16384,
  162. socket_options: (
  163. None | _TYPE_SOCKET_OPTIONS
  164. ) = HTTPConnection.default_socket_options,
  165. proxy: Url | None = None,
  166. proxy_config: ProxyConfig | None = None,
  167. cert_reqs: int | str | None = None,
  168. assert_hostname: None | str | typing.Literal[False] = None,
  169. assert_fingerprint: str | None = None,
  170. server_hostname: str | None = None,
  171. ssl_context: typing.Any | None = None,
  172. ca_certs: str | None = None,
  173. ca_cert_dir: str | None = None,
  174. ca_cert_data: None | str | bytes = None,
  175. ssl_minimum_version: int | None = None,
  176. ssl_maximum_version: int | None = None,
  177. ssl_version: int | str | None = None, # Deprecated
  178. cert_file: str | None = None,
  179. key_file: str | None = None,
  180. key_password: str | None = None,
  181. ) -> None:
  182. super().__init__(
  183. host,
  184. port=port,
  185. timeout=timeout,
  186. source_address=source_address,
  187. blocksize=blocksize,
  188. socket_options=socket_options,
  189. proxy=proxy,
  190. proxy_config=proxy_config,
  191. )
  192. self.scheme = "https"
  193. self.key_file = key_file
  194. self.cert_file = cert_file
  195. self.key_password = key_password
  196. self.ssl_context = ssl_context
  197. self.server_hostname = server_hostname
  198. self.assert_hostname = assert_hostname
  199. self.assert_fingerprint = assert_fingerprint
  200. self.ssl_version = ssl_version
  201. self.ssl_minimum_version = ssl_minimum_version
  202. self.ssl_maximum_version = ssl_maximum_version
  203. self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
  204. self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
  205. self.ca_cert_data = ca_cert_data
  206. self.cert_reqs = None
  207. # The browser will automatically verify all requests.
  208. # We have no control over that setting.
  209. self.is_verified = True
  210. def set_cert(
  211. self,
  212. key_file: str | None = None,
  213. cert_file: str | None = None,
  214. cert_reqs: int | str | None = None,
  215. key_password: str | None = None,
  216. ca_certs: str | None = None,
  217. assert_hostname: None | str | typing.Literal[False] = None,
  218. assert_fingerprint: str | None = None,
  219. ca_cert_dir: str | None = None,
  220. ca_cert_data: None | str | bytes = None,
  221. ) -> None:
  222. pass
  223. # verify that this class implements BaseHTTP(s) connection correctly
  224. if typing.TYPE_CHECKING:
  225. _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0)
  226. _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0)