net.py 3.1 KB

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