connection.py 8.8 KB

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