diff --git a/reflex/utils/net.py b/reflex/utils/net.py new file mode 100644 index 0000000000..83e25559cc --- /dev/null +++ b/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 diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index f71f707eec..697d51cf2c 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -34,7 +34,7 @@ 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 get_release_by_tag(tag: str) -> dict | None: 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 )