From 7fd2c56d20dd7521afcdbc232815678a602da624 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 03:57:57 +0000 Subject: [PATCH] perf: optimize captive portal checks to run concurrently Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src/netsleuth/probes/captive_portal.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/netsleuth/probes/captive_portal.py b/src/netsleuth/probes/captive_portal.py index ce51948..857aa47 100644 --- a/src/netsleuth/probes/captive_portal.py +++ b/src/netsleuth/probes/captive_portal.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio from urllib.parse import urlsplit import httpx @@ -28,7 +29,10 @@ def classify_portal_response( if redirect_host and redirect_host != requested_host: return "portal", f"redirected to a different host ({redirect_host})" if status == 200 and body: - return "portal", f"200 with a non-empty body instead of the expected {expected_status}" + return ( + "portal", + f"200 with a non-empty body instead of the expected {expected_status}", + ) return "suspect", f"unexpected status {status}" @@ -39,18 +43,24 @@ async def check_captive_portal( expected_status: int = 204, timeout: float, ) -> CaptivePortal: - checks: list[PortalCheck] = [] - for url in urls: + async def _check_url(url: str) -> PortalCheck: try: response = await client.get(url, follow_redirects=False, timeout=timeout) verdict, evidence = classify_portal_response( - url, response.status_code, response.text, response.headers.get("location"), expected_status + url, + response.status_code, + response.text, + response.headers.get("location"), + expected_status, + ) + return PortalCheck( + url=url, status=response.status_code, verdict=verdict, evidence=evidence ) except httpx.HTTPError as exc: verdict, evidence = "error", str(exc) - checks.append(PortalCheck(url=url, status=None, verdict=verdict, evidence=evidence)) - continue - checks.append(PortalCheck(url=url, status=response.status_code, verdict=verdict, evidence=evidence)) + return PortalCheck(url=url, status=None, verdict=verdict, evidence=evidence) + + checks = list(await asyncio.gather(*(_check_url(url) for url in urls))) portal_checks = [c for c in checks if c.verdict == "portal"] if portal_checks: