From 5429d3a5b44de2693cfe4155e2477bb9296f591a Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:09:51 +0700 Subject: [PATCH 1/8] update app.py --- fasthttp/app.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/fasthttp/app.py b/fasthttp/app.py index 0710b60..6283d53 100644 --- a/fasthttp/app.py +++ b/fasthttp/app.py @@ -350,8 +350,15 @@ async def lifespan(app: FastHTTP): self.secret_key = secret_key or secrets.token_bytes(32) self.security = Security(secret_key=self.secret_key) if security else None self.startup_uuid = None - if self.generate_startup_uuid and self.startup_uuid_version == "v4": - self.startup_uuid = str(uuid.uuid4()) + if self.generate_startup_uuid: + if self.startup_uuid_version == "v7": + import sys + if sys.version_info >= (3, 13): + self.startup_uuid = str(uuid.uuid7()) + else: + self.startup_uuid = str(uuid.uuid4()) + else: + self.startup_uuid = str(uuid.uuid4()) self.client = HTTPClient( self.request_configs, @@ -875,7 +882,10 @@ def _log_result( ) if route.response_model: - json_data = result.json() + try: + json_data = result.json() + except Exception: + json_data = None if json_data is not None: if get_origin(route.response_model) is list: item_model = get_args(route.response_model)[0] @@ -1223,11 +1233,8 @@ def _find_route(self, url: str, method: str) -> Route | None: normalized_url = self._normalize_url(url) for route in self.fasthttp.routes: route_normalized = self._normalize_url(route.url) - if route.method.upper() == method.upper(): - if route_normalized == normalized_url: - return route - if route_normalized in normalized_url or normalized_url in route_normalized: - return route + if route.method.upper() == method.upper() and route_normalized == normalized_url: + return route return None async def _handle_proxy( @@ -1300,7 +1307,7 @@ async def _handle_proxy( else: result["json"] = json_data except Exception as e: - print(f"DEBUG: validation error={e}") + self.fasthttp.logger.debug("validation error=%s", e) await self._send_json(send, result) From f05b7b3ab7ed156939da6c607dd9ace498d41155 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:09:54 +0700 Subject: [PATCH 2/8] update client.py --- fasthttp/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fasthttp/client.py b/fasthttp/client.py index afff8a6..4b818aa 100644 --- a/fasthttp/client.py +++ b/fasthttp/client.py @@ -108,6 +108,7 @@ def _validate_request(self, route: Route) -> bool: return False async def _prepare_config(self, route: Route, config: dict) -> dict: + config = dict(config) headers = dict(config.get("headers") or {}) headers.setdefault("User-Agent", f"fasthttp/{__version__}") From 0bc1b255dd6f9fcff4ef20566ce952c63ad0685e Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:09:57 +0700 Subject: [PATCH 3/8] update middleware.py --- fasthttp/middleware.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fasthttp/middleware.py b/fasthttp/middleware.py index c8de4bf..8403c36 100644 --- a/fasthttp/middleware.py +++ b/fasthttp/middleware.py @@ -520,7 +520,6 @@ def __init__( self.cache_methods = cache_methods or ["GET"] self._cache: OrderedDict[str, CacheEntry] = OrderedDict() self._lock = asyncio.Lock() - self._cached_response: Response | None = None def _generate_key(self, route: "Route") -> str: key_data = f"{route.method}:{route.url}:{json.dumps(route.params or {}, sort_keys=True)}" @@ -551,7 +550,7 @@ async def before_request( if time.time() < entry.expires_at: self._cache.move_to_end(key) - self._cached_response = entry.response + config["_cache_hit"] = entry.response return config del self._cache[key] @@ -578,9 +577,8 @@ async def after_response( if route.method not in self.cache_methods: return response - if self._cached_response is not None: - cached = self._cached_response - self._cached_response = None + cached = config.get("_cache_hit") + if cached is not None: return cached key = self._generate_key(route) From 852fac76b529dc99d1b1a972c968ff02849e01f5 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:10:01 +0700 Subject: [PATCH 4/8] update response.py --- fasthttp/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/response.py b/fasthttp/response.py index 82917b5..8e74079 100644 --- a/fasthttp/response.py +++ b/fasthttp/response.py @@ -229,7 +229,7 @@ def path_params( ]: return {} - def json(self) -> dict[str, Any]: + def json(self) -> Any: """ Parse the response body as JSON. From 28a69f8f61cedc0702415029fc43679a8a429f5a Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:10:04 +0700 Subject: [PATCH 5/8] update limits.py --- fasthttp/security/limits.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fasthttp/security/limits.py b/fasthttp/security/limits.py index 14074b3..a9046e1 100644 --- a/fasthttp/security/limits.py +++ b/fasthttp/security/limits.py @@ -19,7 +19,11 @@ def __init__( config: LimitsConfig | None = None ) -> None: self._config = config or LimitsConfig() - self._semaphore: asyncio.Semaphore | None = None + self._semaphore: asyncio.Semaphore | None = ( + asyncio.Semaphore(self._config.max_concurrent_requests) + if self._config.max_concurrent_requests > 0 + else None + ) self._last_request_time = 0.0 self._cooldown_lock = asyncio.Lock() @@ -47,11 +51,7 @@ def validate_url_length(self, url: str) -> bool: return len(url) <= self._config.max_url_length async def acquire(self) -> None: - if self._config.max_concurrent_requests > 0: - if not self._semaphore: - self._semaphore = asyncio.Semaphore( - self._config.max_concurrent_requests - ) + if self._semaphore: await self._semaphore.acquire() def release(self) -> None: From 3c5b7f2550a488c83b185d5376ba4612d8a8370f Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:10:07 +0700 Subject: [PATCH 6/8] update response.py --- fasthttp/security/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/security/response.py b/fasthttp/security/response.py index 45fe0d9..753267a 100644 --- a/fasthttp/security/response.py +++ b/fasthttp/security/response.py @@ -117,7 +117,7 @@ def validate_response( text = content.decode("utf-8", errors="ignore") xss_check = self.detect_xss(text) if xss_check[0]: - return xss_check + return False, xss_check[1] except Exception: pass From 7253fb87195e07def2ffad0bf5c10bee07bfff93 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:10:10 +0700 Subject: [PATCH 7/8] update security.py --- fasthttp/security/security.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fasthttp/security/security.py b/fasthttp/security/security.py index b0a1006..dbeada2 100644 --- a/fasthttp/security/security.py +++ b/fasthttp/security/security.py @@ -53,7 +53,7 @@ def secret_key(self) -> bytes: return self._signer._secret_key async def pre_request(self, url: str, method: str) -> None: - self._ssrf.validate_request(url) + await self._ssrf.validate_request(url) if not self._limits.validate_url_length(url): raise SecurityError(f"URL too long: {len(url)} chars") From 24b8fac8742f8525be31a488e7afa76edb0cd759 Mon Sep 17 00:00:00 2001 From: NEFORCEO Date: Fri, 17 Apr 2026 14:10:13 +0700 Subject: [PATCH 8/8] update ssrf.py --- fasthttp/security/ssrf.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fasthttp/security/ssrf.py b/fasthttp/security/ssrf.py index 278f2ef..4cb139c 100644 --- a/fasthttp/security/ssrf.py +++ b/fasthttp/security/ssrf.py @@ -1,3 +1,4 @@ +import asyncio import ipaddress import socket from urllib.parse import urlparse @@ -41,7 +42,7 @@ class SSRFProtection: def __init__(self): self._dns_cache = {} - def check_url(self, url: str) -> bool: + async def check_url(self, url: str) -> bool: parsed = urlparse(url) hostname = parsed.hostname @@ -60,7 +61,7 @@ def check_url(self, url: str) -> bool: return False try: - ip_str = socket.gethostbyname(hostname) + ip_str = await asyncio.to_thread(socket.gethostbyname, hostname) ip = ipaddress.ip_address(ip_str) if ip.is_loopback: @@ -83,8 +84,8 @@ def check_url(self, url: str) -> bool: return True - def validate_request(self, url: str) -> None: - if not self.check_url(url): + async def validate_request(self, url: str) -> None: + if not await self.check_url(url): raise SSRFBlockedError(f"SSRF protection blocked request to: {url}")