浏览代码

disable ipv6 (#5013)

* disable ipv6

* add escape hatch

* add retry logic

* logic wasn't quite right

* add note about IPv6 and remove fallback

* demote it to a debug

* simplify config to make bogus calls

* use httpx head and add scheme
Khaleel Al-Adhami 1 月之前
父节点
当前提交
a25ceadebc
共有 5 个文件被更改,包括 138 次插入23 次删除
  1. 3 0
      reflex/config.py
  2. 107 18
      reflex/utils/net.py
  3. 14 1
      reflex/utils/prerequisites.py
  4. 3 1
      reflex/utils/redir.py
  5. 11 3
      reflex/utils/registry.py

+ 3 - 0
reflex/config.py

@@ -720,6 +720,9 @@ class EnvironmentVariables:
     # Used by flexgen to enumerate the pages.
     REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
 
+    # The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
+    REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
+
 
 environment = EnvironmentVariables()
 

+ 107 - 18
reflex/utils/net.py

@@ -1,8 +1,13 @@
 """Helpers for downloading files from the network."""
 
+import functools
+import time
+from typing import Callable, ParamSpec, TypeVar
+
 import httpx
 
-from ..config import environment
+from reflex.utils.decorator import once
+
 from . import console
 
 
@@ -12,30 +17,114 @@ def _httpx_verify_kwarg() -> bool:
     Returns:
         True if SSL verification is enabled, False otherwise
     """
+    from ..config import environment
+
     return not environment.SSL_NO_VERIFY.get()
 
 
-def get(url: str, **kwargs) -> httpx.Response:
-    """Make an HTTP GET request.
+_P = ParamSpec("_P")
+_T = TypeVar("_T")
+
+
+def _wrap_https_func(
+    func: Callable[_P, _T],
+) -> Callable[_P, _T]:
+    """Wrap an HTTPS function with logging.
 
     Args:
-        url: The URL to request.
-        **kwargs: Additional keyword arguments to pass to httpx.get.
+        func: The function to wrap.
 
     Returns:
-        The response object.
+        The wrapped function.
+    """
+
+    @functools.wraps(func)
+    def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
+        url = args[0]
+        console.debug(f"Sending HTTPS request to {args[0]}")
+        initial_time = time.time()
+        try:
+            response = func(*args, **kwargs)
+        except httpx.ConnectError as err:
+            if "CERTIFICATE_VERIFY_FAILED" in str(err):
+                # If the error is a certificate verification error, recommend mitigating steps.
+                console.error(
+                    f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
+                    "path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
+                )
+            raise
+        else:
+            console.debug(
+                f"Received response from {url} in {time.time() - initial_time:.3f} seconds"
+            )
+            return response
+
+    return wrapper
+
 
-    Raises:
-        httpx.ConnectError: If the connection cannot be established.
+def _is_ipv4_supported() -> bool:
+    """Determine if the system supports IPv4.
+
+    Returns:
+        True if the system supports IPv4, False otherwise.
     """
-    kwargs.setdefault("verify", _httpx_verify_kwarg())
     try:
-        return httpx.get(url, **kwargs)
-    except httpx.ConnectError as err:
-        if "CERTIFICATE_VERIFY_FAILED" in str(err):
-            # If the error is a certificate verification error, recommend mitigating steps.
-            console.error(
-                f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
-                "path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
-            )
-        raise
+        httpx.head("http://1.1.1.1", timeout=3)
+    except httpx.RequestError:
+        return False
+    else:
+        return True
+
+
+def _is_ipv6_supported() -> bool:
+    """Determine if the system supports IPv6.
+
+    Returns:
+        True if the system supports IPv6, False otherwise.
+    """
+    try:
+        httpx.head("http://[2606:4700:4700::1111]", timeout=3)
+    except httpx.RequestError:
+        return False
+    else:
+        return True
+
+
+def _should_use_ipv6() -> bool:
+    """Determine if the system supports IPv6.
+
+    Returns:
+        True if the system supports IPv6, False otherwise.
+    """
+    return not _is_ipv4_supported() and _is_ipv6_supported()
+
+
+def _httpx_local_address_kwarg() -> str:
+    """Get the value of the HTTPX local_address keyword argument.
+
+    Returns:
+        The local address to bind to
+    """
+    from ..config import environment
+
+    return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or (
+        "::" if _should_use_ipv6() else "0.0.0.0"
+    )
+
+
+@once
+def _httpx_client() -> httpx.Client:
+    """Get an HTTPX client.
+
+    Returns:
+        An HTTPX client.
+    """
+    return httpx.Client(
+        transport=httpx.HTTPTransport(
+            local_address=_httpx_local_address_kwarg(),
+            verify=_httpx_verify_kwarg(),
+        )
+    )
+
+
+get = _wrap_https_func(_httpx_client().get)

+ 14 - 1
reflex/utils/prerequisites.py

@@ -115,11 +115,13 @@ def check_latest_package_version(package_name: str):
     if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
         return
     try:
+        console.debug(f"Checking for the latest version of {package_name}...")
         # Get the latest version from PyPI
         current_version = importlib.metadata.version(package_name)
         url = f"https://pypi.org/pypi/{package_name}/json"
         response = net.get(url)
         latest_version = response.json()["info"]["version"]
+        console.debug(f"Latest version of {package_name}: {latest_version}")
         if get_or_set_last_reflex_version_check_datetime():
             # Versions were already checked and saved in reflex.json, no need to warn again
             return
@@ -129,6 +131,7 @@ def check_latest_package_version(package_name: str):
                 f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
             )
     except Exception:
+        console.debug(f"Failed to check for the latest version of {package_name}.")
         pass
 
 
@@ -950,16 +953,22 @@ def initialize_web_directory():
     # Reuse the hash if one is already created, so we don't over-write it when running reflex init
     project_hash = get_project_hash()
 
+    console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
     path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
 
+    console.debug("Initializing the web directory.")
     initialize_package_json()
 
+    console.debug("Initializing the bun config file.")
     initialize_bun_config()
 
+    console.debug("Initializing the public directory.")
     path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
 
+    console.debug("Initializing the next.config.js file.")
     update_next_config()
 
+    console.debug("Initializing the reflex.json file.")
     # Initialize the reflex json file.
     init_reflex_json(project_hash=project_hash)
 
@@ -1392,6 +1401,7 @@ def ensure_reflex_installation_id() -> int | None:
         Distinct id.
     """
     try:
+        console.debug("Ensuring reflex installation id.")
         initialize_reflex_user_directory()
         installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
 
@@ -1418,6 +1428,7 @@ def ensure_reflex_installation_id() -> int | None:
 
 def initialize_reflex_user_directory():
     """Initialize the reflex user directory."""
+    console.debug(f"Creating {environment.REFLEX_DIR.get()}")
     # Create the reflex directory.
     path_ops.mkdir(environment.REFLEX_DIR.get())
 
@@ -1425,9 +1436,11 @@ def initialize_reflex_user_directory():
 def initialize_frontend_dependencies():
     """Initialize all the frontend dependencies."""
     # validate dependencies before install
+    console.debug("Validating frontend dependencies.")
     validate_frontend_dependencies()
     # Install the frontend dependencies.
-    processes.run_concurrently(install_bun)
+    console.debug("Installing or validating bun.")
+    install_bun()
     # Set up the web directory.
     initialize_web_directory()
 

+ 3 - 1
reflex/utils/redir.py

@@ -5,6 +5,8 @@ import webbrowser
 
 import httpx
 
+from reflex.utils import net
+
 from .. import constants
 from . import console
 
@@ -38,7 +40,7 @@ def open_browser_and_wait(
     console.info("[b]Complete the workflow in the browser to continue.[/b]")
     while True:
         try:
-            response = httpx.get(poll_url, follow_redirects=True)
+            response = net.get(poll_url, follow_redirects=True)
             if response.is_success:
                 break
         except httpx.RequestError as err:

+ 11 - 3
reflex/utils/registry.py

@@ -16,10 +16,13 @@ def latency(registry: str) -> int:
         int: The latency of the registry in microseconds.
     """
     try:
-        return net.get(registry).elapsed.microseconds
+        time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
     except httpx.HTTPError:
         console.info(f"Failed to connect to {registry}.")
         return 10_000_000
+    else:
+        console.debug(f"Latency of {registry}: {time_to_respond}")
+        return time_to_respond
 
 
 def average_latency(registry: str, attempts: int = 3) -> int:
@@ -32,7 +35,9 @@ def average_latency(registry: str, attempts: int = 3) -> int:
     Returns:
         The average latency of the registry in microseconds.
     """
-    return sum(latency(registry) for _ in range(attempts)) // attempts
+    registry_latency = sum(latency(registry) for _ in range(attempts)) // attempts
+    console.debug(f"Average latency of {registry}: {registry_latency}")
+    return registry_latency
 
 
 def _get_best_registry() -> str:
@@ -41,12 +46,15 @@ def _get_best_registry() -> str:
     Returns:
         The best registry.
     """
+    console.debug("Getting best registry...")
     registries = [
         "https://registry.npmjs.org",
         "https://r.cnpmjs.org",
     ]
 
-    return min(registries, key=average_latency)
+    best_registry = min(registries, key=average_latency)
+    console.debug(f"Best registry: {best_registry}")
+    return best_registry
 
 
 def get_npm_registry() -> str: