diff --git a/README.md b/README.md index f1d0ffc..c20917b 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,17 @@ you want; importing the package never fails because an extra is missing. `requirements.txt` (pick the extra you need): ``` -aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2 -aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2 -aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2 +aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3 +aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3 +aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3 ``` Direct: ```bash -pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2" -pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2" -pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.2" +pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3" +pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3" +pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.3" ``` - `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both. diff --git a/pyproject.toml b/pyproject.toml index 2a16762..ae40ebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "hatchling.build" [project] name = "aioweb_tls" -version = "0.1.2" -description = "TLS-fingerprinting backends for aioweb — curl_cffi / noble_tls ExtendedSession subclasses, config-free, installable." +version = "0.1.3" +description = "TLS-fingerprinting backends (curl_cffi / noble_tls) for aioweb via one injectable TLSSession, config-free, installable." requires-python = ">=3.10" dependencies = [ "aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.0", diff --git a/src/aioweb_tls/backends.py b/src/aioweb_tls/backends.py index 68c17f9..475d45a 100644 --- a/src/aioweb_tls/backends.py +++ b/src/aioweb_tls/backends.py @@ -15,10 +15,23 @@ import asyncio import logging import math +import aiohttp from aioweb import Response log = logging.getLogger(__name__) + +def _as_client_error(error: Exception, backend: str) -> aiohttp.ClientError: + """wrap a backend-native network exception as an aiohttp.ClientError + + aioweb's request() only re-wraps aiohttp.ClientError; curl_cffi raises + RequestException(OSError) and noble_tls raises TLSClientException(IOError), neither + of which is an aiohttp.ClientError. translating here gives TLS backends the same + typed failure contract as the aiohttp path on the bare request() route. + """ + return aiohttp.ClientError(f"{backend} request failed: {error}") + + try: from curl_cffi import AsyncSession as _CurlAsyncSession _CURL_ERROR = None @@ -94,10 +107,13 @@ class CurlCffi: if proxy: kwargs["proxy"] = proxy - response = await session.request(method, url, impersonate=impersonate, **kwargs) - content = response.content - if content is None: - content = response.text.encode() if response.text else b"" + try: + response = await session.request(method, url, impersonate=impersonate, **kwargs) + except aiohttp.ClientError: + raise + except Exception as error: + raise _as_client_error(error, "curl_cffi") from error + content = response.content if response.content is not None else b"" return Response( status_code=response.status_code, headers=dict(response.headers), @@ -161,9 +177,9 @@ class Noble: async def setup(self) -> None: """fetch the noble_tls Go shared library once; idempotent and concurrency-safe - download_if_necessary handles the first-time fetch (no lib present); - update_if_necessary refreshes an existing one. try download first so a - clean environment works, falling back to update. + uses noble_tls.download_if_necessary (the current API: it fetches the asset on + first use and no-ops when it already exists). older noble_tls without that name + is handled via update_if_necessary as a fallback. guarded by an asyncio.Lock with a check-lock-recheck so concurrent first requests don't both run the fetch: the fast path returns once _updated is @@ -177,7 +193,7 @@ class Noble: download = getattr(noble_tls, "download_if_necessary", None) if download is not None: await download() - else: + elif hasattr(noble_tls, "update_if_necessary"): await noble_tls.update_if_necessary() self._updated = True @@ -210,7 +226,12 @@ class Noble: if proxy: kwargs["proxy"] = proxy - response = await session.execute_request(method=method.upper(), url=url, **kwargs) + try: + response = await session.execute_request(method=method.upper(), url=url, **kwargs) + except aiohttp.ClientError: + raise + except Exception as error: + raise _as_client_error(error, "noble_tls") from error content = getattr(response, "content", None) if content is None: text = getattr(response, "text", "") or ""