feat: add HTTP anonymity checker with SOCKS support

This commit is contained in:
agatha 2026-03-15 16:37:19 -04:00
parent 20b8d0a917
commit 328caec770
3 changed files with 141 additions and 0 deletions

View File

@ -14,6 +14,7 @@ dependencies = [
"asyncpg>=0.31.0", "asyncpg>=0.31.0",
"email-validator>=2.3.0", "email-validator>=2.3.0",
"fastapi>=0.135.1", "fastapi>=0.135.1",
"httpx-socks[asyncio]>=0.11.0",
"httpx[socks]>=0.28.1", "httpx[socks]>=0.28.1",
"pydantic>=2.12.5", "pydantic>=2.12.5",
"pydantic-settings>=2.13.1", "pydantic-settings>=2.13.1",

View File

@ -0,0 +1,105 @@
from __future__ import annotations
import httpx
from proxy_pool.config import Settings
from proxy_pool.plugins.protocols import CheckContext, CheckResult
class HttpAnonymityChecker:
name = "http_anonymity"
stage = 2
priority = 0
timeout = 15.0
def __init__(self, judge_url: str, timeout: float = 15.0) -> None:
self._judge_url = judge_url
self.timeout = timeout
async def check(
self,
proxy_ip: str,
proxy_port: int,
proxy_protocol: str,
context: CheckContext,
) -> CheckResult:
if proxy_protocol in ("socks4", "socks5"):
proxy_url = f"{proxy_protocol}://{proxy_ip}:{proxy_port}"
try:
from httpx_socks import AsyncProxyTransport
transport = AsyncProxyTransport.from_url(proxy_url)
client_kwargs = {"transport": transport, "timeout": self.timeout}
except ImportError:
return CheckResult(
passed=False,
detail="httpx-socks not installed, cannot check SOCKS proxies",
)
else:
proxy_url = f"http://{proxy_ip}:{proxy_port}"
client_kwargs = {
"proxy": proxy_url,
"timeout": self.timeout,
"verify": False,
}
try:
async with httpx.AsyncClient(**client_kwargs) as client:
response = await client.get(self._judge_url)
response.raise_for_status()
except httpx.TimeoutException:
return CheckResult(
passed=False,
detail=f"HTTP request through proxy timed out after {self.timeout}s",
)
except httpx.ProxyError as err:
return CheckResult(
passed=False,
detail=f"Proxy error: {err}",
)
except httpx.HTTPError as err:
return CheckResult(
passed=False,
detail=f"HTTP error: {err}",
)
except Exception as err:
return CheckResult(
passed=False,
detail=f"Connection error: {err}",
)
latency = context.elapsed_ms() - (context.tcp_latency_ms or 0)
context.http_latency_ms = latency
try:
data = response.json()
exit_ip = data.get("origin") or data.get("ip")
except Exception:
exit_ip = response.text.strip()
if exit_ip:
context.exit_ip = exit_ip
if exit_ip and exit_ip != proxy_ip:
context.anonymity_level = "elite"
elif exit_ip and exit_ip == proxy_ip:
context.anonymity_level = "anonymous"
else:
context.anonymity_level = "transparent"
return CheckResult(
passed=True,
detail=f"Exit IP: {exit_ip}",
latency_ms=latency,
metadata={"exit_ip": exit_ip},
)
def should_skip(self, proxy_protocol: str) -> bool:
return False # We handle all protocols now
def create_plugin(settings: Settings) -> HttpAnonymityChecker:
return HttpAnonymityChecker(
judge_url=settings.proxy.judge_url,
timeout=settings.proxy.check_http_timeout,
)

35
uv.lock generated
View File

@ -459,6 +459,21 @@ socks = [
{ name = "socksio" }, { name = "socksio" },
] ]
[[package]]
name = "httpx-socks"
version = "0.11.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpcore" },
{ name = "httpx" },
{ name = "python-socks" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/b4/1a5a0f67207a117ca554677ccde6f30679d4b5c10a1bf838b93c7644f468/httpx_socks-0.11.0.tar.gz", hash = "sha256:2e2de097d87dfc228dd36e3c5ae0588c836e48159f5996b33cef540497af9b32", size = 117971, upload-time = "2025-12-05T05:46:41.997Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/2a/78b08da3f2c8eb4dd31420d0a38ed4fd4cce272dbe6a8a0d154c0300002b/httpx_socks-0.11.0-py3-none-any.whl", hash = "sha256:8c28ad569ccf681b45437ea8465203cbc082206659b6f623e4ea509b1eb4e8a7", size = 13308, upload-time = "2025-12-05T05:46:40.193Z" },
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.11" version = "3.11"
@ -693,6 +708,7 @@ dependencies = [
{ name = "email-validator" }, { name = "email-validator" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "httpx", extra = ["socks"] }, { name = "httpx", extra = ["socks"] },
{ name = "httpx-socks" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "redis", extra = ["hiredis"] }, { name = "redis", extra = ["hiredis"] },
@ -721,6 +737,7 @@ requires-dist = [
{ name = "email-validator", specifier = ">=2.3.0" }, { name = "email-validator", specifier = ">=2.3.0" },
{ name = "fastapi", specifier = ">=0.135.1" }, { name = "fastapi", specifier = ">=0.135.1" },
{ name = "httpx", extras = ["socks"], specifier = ">=0.28.1" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1" },
{ name = "httpx-socks", extras = ["asyncio"], specifier = ">=0.11.0" },
{ name = "pydantic", specifier = ">=2.12.5" }, { name = "pydantic", specifier = ">=2.12.5" },
{ name = "pydantic-settings", specifier = ">=2.13.1" }, { name = "pydantic-settings", specifier = ">=2.13.1" },
{ name = "redis", extras = ["hiredis"], specifier = ">=5.3.1" }, { name = "redis", extras = ["hiredis"], specifier = ">=5.3.1" },
@ -908,6 +925,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
] ]
[[package]]
name = "python-socks"
version = "2.8.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/36/0b/cd77011c1bc01b76404f7aba07fca18aca02a19c7626e329b40201217624/python_socks-2.8.1.tar.gz", hash = "sha256:698daa9616d46dddaffe65b87db222f2902177a2d2b2c0b9a9361df607ab3687", size = 38909, upload-time = "2026-02-16T05:24:00.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/fe/9a58cb6eec633ff6afae150ca53c16f8cc8b65862ccb3d088051efdfceb7/python_socks-2.8.1-py3-none-any.whl", hash = "sha256:28232739c4988064e725cdbcd15be194743dd23f1c910f784163365b9d7be035", size = 55087, upload-time = "2026-02-16T05:23:59.147Z" },
]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.3" version = "6.0.3"
@ -996,6 +1022,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" },
] ]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]] [[package]]
name = "socksio" name = "socksio"
version = "1.0.0" version = "1.0.0"