Pārlūkot izejas kodu

[REF-3633] Introduce a workaround for enterprise users who get stuck with httpx.get SSL (#3846)

* [REF-3633] Introduce a workaround for enterprise users who get stuck with httpx.get SSL

Setting SSL_NO_VERIFY=1 will disable SSL verification during `reflex init`

* Also install fnm using `reflex.utils.net.get`
Masen Furer 8 mēneši atpakaļ
vecāks
revīzija
ecf222d4b0
2 mainītis faili ar 55 papildinājumiem un 12 dzēšanām
  1. 43 0
      reflex/utils/net.py
  2. 12 12
      reflex/utils/prerequisites.py

+ 43 - 0
reflex/utils/net.py

@@ -0,0 +1,43 @@
+"""Helpers for downloading files from the network."""
+
+import os
+
+import httpx
+
+from . import console
+
+
+def _httpx_verify_kwarg() -> bool:
+    """Get the value of the HTTPX verify keyword argument.
+
+    Returns:
+        True if SSL verification is enabled, False otherwise
+    """
+    ssl_no_verify = os.environ.get("SSL_NO_VERIFY", "").lower() in ["true", "1", "yes"]
+    return not ssl_no_verify
+
+
+def get(url: str, **kwargs) -> httpx.Response:
+    """Make an HTTP GET request.
+
+    Args:
+        url: The URL to request.
+        **kwargs: Additional keyword arguments to pass to httpx.get.
+
+    Returns:
+        The response object.
+
+    Raises:
+        httpx.ConnectError: If the connection cannot be established.
+    """
+    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

+ 12 - 12
reflex/utils/prerequisites.py

@@ -34,7 +34,7 @@ from reflex import constants, model
 from reflex.base import Base
 from reflex.compiler import templates
 from reflex.config import Config, get_config
-from reflex.utils import console, path_ops, processes
+from reflex.utils import console, net, path_ops, processes
 from reflex.utils.format import format_library_name
 from reflex.utils.registry import _get_best_registry
 
@@ -80,7 +80,7 @@ def check_latest_package_version(package_name: str):
         # Get the latest version from PyPI
         current_version = importlib.metadata.version(package_name)
         url = f"https://pypi.org/pypi/{package_name}/json"
-        response = httpx.get(url)
+        response = net.get(url)
         latest_version = response.json()["info"]["version"]
         if (
             version.parse(current_version) < version.parse(latest_version)
@@ -670,7 +670,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
     """
     # Download the script
     console.debug(f"Downloading {url}")
-    response = httpx.get(url)
+    response = net.get(url)
     if response.status_code != httpx.codes.OK:
         response.raise_for_status()
 
@@ -700,11 +700,11 @@ def download_and_extract_fnm_zip():
     try:
         # Download the FNM zip release.
         # TODO: show progress to improve UX
-        with httpx.stream("GET", url, follow_redirects=True) as response:
-            response.raise_for_status()
-            with open(fnm_zip_file, "wb") as output_file:
-                for chunk in response.iter_bytes():
-                    output_file.write(chunk)
+        response = net.get(url, follow_redirects=True)
+        response.raise_for_status()
+        with open(fnm_zip_file, "wb") as output_file:
+            for chunk in response.iter_bytes():
+                output_file.write(chunk)
 
         # Extract the downloaded zip file.
         with zipfile.ZipFile(fnm_zip_file, "r") as zip_ref:
@@ -1222,7 +1222,7 @@ def fetch_app_templates(version: str) -> dict[str, Template]:
     """
 
     def get_release_by_tag(tag: str) -> dict | None:
-        response = httpx.get(constants.Reflex.RELEASES_URL)
+        response = net.get(constants.Reflex.RELEASES_URL)
         response.raise_for_status()
         releases = response.json()
         for release in releases:
@@ -1243,7 +1243,7 @@ def fetch_app_templates(version: str) -> dict[str, Template]:
     else:
         templates_url = asset["browser_download_url"]
 
-    templates_data = httpx.get(templates_url, follow_redirects=True).json()["templates"]
+    templates_data = net.get(templates_url, follow_redirects=True).json()["templates"]
 
     for template in templates_data:
         if template["name"] == "blank":
@@ -1286,7 +1286,7 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
     zip_file_path = Path(temp_dir) / "template.zip"
     try:
         # Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
-        response = httpx.get(template_url, follow_redirects=True)
+        response = net.get(template_url, follow_redirects=True)
         console.debug(f"Server responded download request: {response}")
         response.raise_for_status()
     except httpx.HTTPError as he:
@@ -1417,7 +1417,7 @@ def initialize_main_module_index_from_generation(app_name: str, generation_hash:
         generation_hash: The generation hash from reflex.build.
     """
     # Download the reflex code for the generation.
-    resp = httpx.get(
+    resp = net.get(
         constants.Templates.REFLEX_BUILD_CODE_URL.format(
             generation_hash=generation_hash
         )