1
0

net.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. """Helpers for downloading files from the network."""
  2. import functools
  3. import time
  4. from collections.abc import Callable
  5. from typing import ParamSpec, TypeVar
  6. import httpx
  7. from reflex.utils.decorator import once
  8. from reflex.utils.types import Unset
  9. from . import console
  10. def _httpx_verify_kwarg() -> bool:
  11. """Get the value of the HTTPX verify keyword argument.
  12. Returns:
  13. True if SSL verification is enabled, False otherwise
  14. """
  15. from ..config import environment
  16. return not environment.SSL_NO_VERIFY.get()
  17. _P = ParamSpec("_P")
  18. _T = TypeVar("_T")
  19. def _wrap_https_func(
  20. func: Callable[_P, _T],
  21. ) -> Callable[_P, _T]:
  22. """Wrap an HTTPS function with logging.
  23. Args:
  24. func: The function to wrap.
  25. Returns:
  26. The wrapped function.
  27. """
  28. @functools.wraps(func)
  29. def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
  30. url = args[0]
  31. console.debug(f"Sending HTTPS request to {args[0]}")
  32. initial_time = time.time()
  33. try:
  34. response = func(*args, **kwargs)
  35. except httpx.ConnectError as err:
  36. if "CERTIFICATE_VERIFY_FAILED" in str(err):
  37. # If the error is a certificate verification error, recommend mitigating steps.
  38. console.error(
  39. f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
  40. "path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
  41. )
  42. raise
  43. else:
  44. console.debug(
  45. f"Received response from {url} in {time.time() - initial_time:.3f} seconds"
  46. )
  47. return response
  48. return wrapper
  49. def _wrap_https_lazy_func(
  50. func: Callable[[], Callable[_P, _T]],
  51. ) -> Callable[_P, _T]:
  52. """Wrap an HTTPS function with logging.
  53. Args:
  54. func: The function to wrap.
  55. Returns:
  56. The wrapped function.
  57. """
  58. unset = Unset()
  59. f: Callable[_P, _T] | Unset = unset
  60. def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
  61. nonlocal f
  62. if isinstance(f, Unset):
  63. f = _wrap_https_func(func())
  64. functools.update_wrapper(wrapper, f)
  65. return f(*args, **kwargs)
  66. return wrapper
  67. def _is_ipv4_supported() -> bool:
  68. """Determine if the system supports IPv4.
  69. Returns:
  70. True if the system supports IPv4, False otherwise.
  71. """
  72. try:
  73. httpx.head("http://1.1.1.1", timeout=3)
  74. except httpx.RequestError:
  75. return False
  76. else:
  77. return True
  78. def _is_ipv6_supported() -> bool:
  79. """Determine if the system supports IPv6.
  80. Returns:
  81. True if the system supports IPv6, False otherwise.
  82. """
  83. try:
  84. httpx.head("http://[2606:4700:4700::1111]", timeout=3)
  85. except httpx.RequestError:
  86. return False
  87. else:
  88. return True
  89. def _should_use_ipv6() -> bool:
  90. """Determine if the system supports IPv6.
  91. Returns:
  92. True if the system supports IPv6, False otherwise.
  93. """
  94. return not _is_ipv4_supported() and _is_ipv6_supported()
  95. def _httpx_local_address_kwarg() -> str:
  96. """Get the value of the HTTPX local_address keyword argument.
  97. Returns:
  98. The local address to bind to
  99. """
  100. from ..config import environment
  101. return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or (
  102. "::" if _should_use_ipv6() else "0.0.0.0"
  103. )
  104. @once
  105. def _httpx_client() -> httpx.Client:
  106. """Get an HTTPX client.
  107. Returns:
  108. An HTTPX client.
  109. """
  110. from httpx._utils import get_environment_proxies
  111. return httpx.Client(
  112. transport=httpx.HTTPTransport(
  113. local_address=_httpx_local_address_kwarg(),
  114. verify=_httpx_verify_kwarg(),
  115. ),
  116. mounts={
  117. key: (
  118. None if url is None else httpx.HTTPTransport(proxy=httpx.Proxy(url=url))
  119. )
  120. for key, url in get_environment_proxies().items()
  121. },
  122. )
  123. get = _wrap_https_lazy_func(lambda: _httpx_client().get)