net.py 3.1 KB

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