feat: 为出站请求统一接入 HTTP 代理
This commit is contained in:
+27
-1
@@ -8,9 +8,11 @@
|
||||
「通用 / 仅抓取 / 仅交易 / 仅网关 / 仅交易 worker」分区标注,各进程只读自己那部分。
|
||||
"""
|
||||
import socket
|
||||
from fnmatch import fnmatchcase
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
from urllib.parse import quote, urlsplit
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
@@ -88,6 +90,8 @@ class Settings(BaseSettings):
|
||||
proxy_server: str | None = None
|
||||
proxy_username: str | None = None
|
||||
proxy_password: str | None = None
|
||||
# 逗号分隔的直连主机名或通配符。服务间请求不应绕到公网代理。
|
||||
proxy_bypass: str = "localhost,127.0.0.1,::1,rakuten-api,rakuten-trading,rakuten-gateway"
|
||||
|
||||
# ---- OpenTelemetry traces(通用,可选;默认关闭)----
|
||||
# 启用后把抓取-解析链路以 span 导出到 OTLP/HTTP endpoint,用于排查"抓到
|
||||
@@ -192,6 +196,8 @@ class Settings(BaseSettings):
|
||||
proxy["username"] = self.proxy_username
|
||||
if self.proxy_password:
|
||||
proxy["password"] = self.proxy_password
|
||||
if self.proxy_bypass.strip():
|
||||
proxy["bypass"] = self.proxy_bypass
|
||||
return proxy
|
||||
|
||||
@property
|
||||
@@ -205,9 +211,29 @@ class Settings(BaseSettings):
|
||||
scheme, _, rest = self.proxy_server.partition("://")
|
||||
if not rest:
|
||||
return self.proxy_server
|
||||
credentials = f"{self.proxy_username}:{self.proxy_password or ''}"
|
||||
credentials = f"{quote(self.proxy_username, safe='')}:{quote(self.proxy_password or '', safe='')}"
|
||||
return f"{scheme}://{credentials}@{rest}"
|
||||
|
||||
def proxy_bypasses(self, url: str) -> bool:
|
||||
"""目标 URL 的主机是否应绕过外部代理。"""
|
||||
host = urlsplit(url).hostname
|
||||
if not host:
|
||||
return False
|
||||
|
||||
normalized_host = host.rstrip(".").lower()
|
||||
for raw_pattern in self.proxy_bypass.split(","):
|
||||
pattern = raw_pattern.strip().rstrip(".").lower()
|
||||
if not pattern:
|
||||
continue
|
||||
if pattern.startswith("."):
|
||||
suffix = pattern[1:]
|
||||
if normalized_host == suffix or normalized_host.endswith(f".{suffix}"):
|
||||
return True
|
||||
continue
|
||||
if fnmatchcase(normalized_host, pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def auth_state_path(self) -> Path:
|
||||
"""登录态目录的绝对路径,不存在时创建"""
|
||||
|
||||
Reference in New Issue
Block a user