Skip to content

Commit a25cead

Browse files
authored
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
1 parent c661669 commit a25cead

File tree

5 files changed

+138
-23
lines changed

5 files changed

+138
-23
lines changed

reflex/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,9 @@ class EnvironmentVariables:
720720
# Used by flexgen to enumerate the pages.
721721
REFLEX_ADD_ALL_ROUTES_ENDPOINT: EnvVar[bool] = env_var(False)
722722

723+
# The address to bind the HTTP client to. You can set this to "::" to enable IPv6.
724+
REFLEX_HTTP_CLIENT_BIND_ADDRESS: EnvVar[str | None] = env_var(None)
725+
723726

724727
environment = EnvironmentVariables()
725728

reflex/utils/net.py

Lines changed: 107 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
"""Helpers for downloading files from the network."""
22

3+
import functools
4+
import time
5+
from typing import Callable, ParamSpec, TypeVar
6+
37
import httpx
48

5-
from ..config import environment
9+
from reflex.utils.decorator import once
10+
611
from . import console
712

813

@@ -12,30 +17,114 @@ def _httpx_verify_kwarg() -> bool:
1217
Returns:
1318
True if SSL verification is enabled, False otherwise
1419
"""
20+
from ..config import environment
21+
1522
return not environment.SSL_NO_VERIFY.get()
1623

1724

18-
def get(url: str, **kwargs) -> httpx.Response:
19-
"""Make an HTTP GET request.
25+
_P = ParamSpec("_P")
26+
_T = TypeVar("_T")
27+
28+
29+
def _wrap_https_func(
30+
func: Callable[_P, _T],
31+
) -> Callable[_P, _T]:
32+
"""Wrap an HTTPS function with logging.
2033
2134
Args:
22-
url: The URL to request.
23-
**kwargs: Additional keyword arguments to pass to httpx.get.
35+
func: The function to wrap.
2436
2537
Returns:
26-
The response object.
38+
The wrapped function.
39+
"""
40+
41+
@functools.wraps(func)
42+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
43+
url = args[0]
44+
console.debug(f"Sending HTTPS request to {args[0]}")
45+
initial_time = time.time()
46+
try:
47+
response = func(*args, **kwargs)
48+
except httpx.ConnectError as err:
49+
if "CERTIFICATE_VERIFY_FAILED" in str(err):
50+
# If the error is a certificate verification error, recommend mitigating steps.
51+
console.error(
52+
f"Certificate verification failed for {url}. Set environment variable SSL_CERT_FILE to the "
53+
"path of the certificate file or SSL_NO_VERIFY=1 to disable verification."
54+
)
55+
raise
56+
else:
57+
console.debug(
58+
f"Received response from {url} in {time.time() - initial_time:.3f} seconds"
59+
)
60+
return response
61+
62+
return wrapper
63+
2764

28-
Raises:
29-
httpx.ConnectError: If the connection cannot be established.
65+
def _is_ipv4_supported() -> bool:
66+
"""Determine if the system supports IPv4.
67+
68+
Returns:
69+
True if the system supports IPv4, False otherwise.
3070
"""
31-
kwargs.setdefault("verify", _httpx_verify_kwarg())
3271
try:
33-
return httpx.get(url, **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
72+
httpx.head("http://1.1.1.1", timeout=3)
73+
except httpx.RequestError:
74+
return False
75+
else:
76+
return True
77+
78+
79+
def _is_ipv6_supported() -> bool:
80+
"""Determine if the system supports IPv6.
81+
82+
Returns:
83+
True if the system supports IPv6, False otherwise.
84+
"""
85+
try:
86+
httpx.head("http://[2606:4700:4700::1111]", timeout=3)
87+
except httpx.RequestError:
88+
return False
89+
else:
90+
return True
91+
92+
93+
def _should_use_ipv6() -> bool:
94+
"""Determine if the system supports IPv6.
95+
96+
Returns:
97+
True if the system supports IPv6, False otherwise.
98+
"""
99+
return not _is_ipv4_supported() and _is_ipv6_supported()
100+
101+
102+
def _httpx_local_address_kwarg() -> str:
103+
"""Get the value of the HTTPX local_address keyword argument.
104+
105+
Returns:
106+
The local address to bind to
107+
"""
108+
from ..config import environment
109+
110+
return environment.REFLEX_HTTP_CLIENT_BIND_ADDRESS.get() or (
111+
"::" if _should_use_ipv6() else "0.0.0.0"
112+
)
113+
114+
115+
@once
116+
def _httpx_client() -> httpx.Client:
117+
"""Get an HTTPX client.
118+
119+
Returns:
120+
An HTTPX client.
121+
"""
122+
return httpx.Client(
123+
transport=httpx.HTTPTransport(
124+
local_address=_httpx_local_address_kwarg(),
125+
verify=_httpx_verify_kwarg(),
126+
)
127+
)
128+
129+
130+
get = _wrap_https_func(_httpx_client().get)

reflex/utils/prerequisites.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ def check_latest_package_version(package_name: str):
115115
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
116116
return
117117
try:
118+
console.debug(f"Checking for the latest version of {package_name}...")
118119
# Get the latest version from PyPI
119120
current_version = importlib.metadata.version(package_name)
120121
url = f"https://pypi.org/pypi/{package_name}/json"
121122
response = net.get(url)
122123
latest_version = response.json()["info"]["version"]
124+
console.debug(f"Latest version of {package_name}: {latest_version}")
123125
if get_or_set_last_reflex_version_check_datetime():
124126
# Versions were already checked and saved in reflex.json, no need to warn again
125127
return
@@ -129,6 +131,7 @@ def check_latest_package_version(package_name: str):
129131
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
130132
)
131133
except Exception:
134+
console.debug(f"Failed to check for the latest version of {package_name}.")
132135
pass
133136

134137

@@ -950,16 +953,22 @@ def initialize_web_directory():
950953
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
951954
project_hash = get_project_hash()
952955

956+
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
953957
path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
954958

959+
console.debug("Initializing the web directory.")
955960
initialize_package_json()
956961

962+
console.debug("Initializing the bun config file.")
957963
initialize_bun_config()
958964

965+
console.debug("Initializing the public directory.")
959966
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
960967

968+
console.debug("Initializing the next.config.js file.")
961969
update_next_config()
962970

971+
console.debug("Initializing the reflex.json file.")
963972
# Initialize the reflex json file.
964973
init_reflex_json(project_hash=project_hash)
965974

@@ -1392,6 +1401,7 @@ def ensure_reflex_installation_id() -> int | None:
13921401
Distinct id.
13931402
"""
13941403
try:
1404+
console.debug("Ensuring reflex installation id.")
13951405
initialize_reflex_user_directory()
13961406
installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
13971407

@@ -1418,16 +1428,19 @@ def ensure_reflex_installation_id() -> int | None:
14181428

14191429
def initialize_reflex_user_directory():
14201430
"""Initialize the reflex user directory."""
1431+
console.debug(f"Creating {environment.REFLEX_DIR.get()}")
14211432
# Create the reflex directory.
14221433
path_ops.mkdir(environment.REFLEX_DIR.get())
14231434

14241435

14251436
def initialize_frontend_dependencies():
14261437
"""Initialize all the frontend dependencies."""
14271438
# validate dependencies before install
1439+
console.debug("Validating frontend dependencies.")
14281440
validate_frontend_dependencies()
14291441
# Install the frontend dependencies.
1430-
processes.run_concurrently(install_bun)
1442+
console.debug("Installing or validating bun.")
1443+
install_bun()
14311444
# Set up the web directory.
14321445
initialize_web_directory()
14331446

reflex/utils/redir.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import httpx
77

8+
from reflex.utils import net
9+
810
from .. import constants
911
from . import console
1012

@@ -38,7 +40,7 @@ def open_browser_and_wait(
3840
console.info("[b]Complete the workflow in the browser to continue.[/b]")
3941
while True:
4042
try:
41-
response = httpx.get(poll_url, follow_redirects=True)
43+
response = net.get(poll_url, follow_redirects=True)
4244
if response.is_success:
4345
break
4446
except httpx.RequestError as err:

reflex/utils/registry.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ def latency(registry: str) -> int:
1616
int: The latency of the registry in microseconds.
1717
"""
1818
try:
19-
return net.get(registry).elapsed.microseconds
19+
time_to_respond = net.get(registry, timeout=2).elapsed.microseconds
2020
except httpx.HTTPError:
2121
console.info(f"Failed to connect to {registry}.")
2222
return 10_000_000
23+
else:
24+
console.debug(f"Latency of {registry}: {time_to_respond}")
25+
return time_to_respond
2326

2427

2528
def average_latency(registry: str, attempts: int = 3) -> int:
@@ -32,7 +35,9 @@ def average_latency(registry: str, attempts: int = 3) -> int:
3235
Returns:
3336
The average latency of the registry in microseconds.
3437
"""
35-
return sum(latency(registry) for _ in range(attempts)) // attempts
38+
registry_latency = sum(latency(registry) for _ in range(attempts)) // attempts
39+
console.debug(f"Average latency of {registry}: {registry_latency}")
40+
return registry_latency
3641

3742

3843
def _get_best_registry() -> str:
@@ -41,12 +46,15 @@ def _get_best_registry() -> str:
4146
Returns:
4247
The best registry.
4348
"""
49+
console.debug("Getting best registry...")
4450
registries = [
4551
"https://registry.npmjs.org",
4652
"https://r.cnpmjs.org",
4753
]
4854

49-
return min(registries, key=average_latency)
55+
best_registry = min(registries, key=average_latency)
56+
console.debug(f"Best registry: {best_registry}")
57+
return best_registry
5058

5159

5260
def get_npm_registry() -> str:

0 commit comments

Comments
 (0)