v1.8.4: Add two-proxy support for custom exit IP region

This commit is contained in:
violettools
2026-01-22 17:22:28 +08:00
parent fffb675838
commit 5da73175ec
13 changed files with 929 additions and 118 deletions

View File

@@ -73,6 +73,13 @@ from .batch import batch, abatch, BatchResult, BatchItem
# 数据导出
from .export import export
# 本地代理服务器(支持双层代理)
from .proxy_server import (
generate_vless_link,
start_proxy_server,
TwoProxyServer
)
# 异步 API基于 httpx
from .async_api import (
aget, apost, aput, adelete, ahead, aoptions, apatch,
@@ -210,7 +217,7 @@ class PlaywrightNotInstalledError(CFSpiderError):
pass
__version__ = "1.8.3"
__version__ = "1.8.4"
__all__ = [
# 同步 API (requests)
"get", "post", "put", "delete", "head", "options", "patch", "request",
@@ -242,4 +249,6 @@ __all__ = [
"batch", "abatch", "BatchResult", "BatchItem",
# 数据导出
"export",
# 本地代理服务器(双层代理)
"generate_vless_link", "start_proxy_server", "TwoProxyServer",
]

View File

@@ -382,7 +382,7 @@ class CFSpiderResponse:
def request(method, url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""
发送 HTTP 请求
@@ -404,6 +404,12 @@ def request(method, url, cf_proxies=None, uuid=None, http2=False, impersonate=No
- False: 每次请求获取新的出口 IP适合大规模采集
- True: 保持使用同一个 IP适合需要会话一致性的场景
two_proxy: 第二层代理(可选)
格式: "host:port:user:pass""host:port"
例如: "us.cliproxy.io:3010:username:password"
用于无法直连第二层代理时,通过 Workers 作为跳板
流程: 本地 → Workers (VLESS) → 第二层代理 → 目标网站
http2: 是否启用 HTTP/2 协议(默认 False
impersonate: TLS 指纹模拟(可选)
@@ -471,7 +477,7 @@ def request(method, url, cf_proxies=None, uuid=None, http2=False, impersonate=No
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser,
static_ip=static_ip,
static_ip=static_ip, two_proxy=two_proxy,
**kwargs
)
@@ -652,7 +658,7 @@ def _request_vless(method, url, cf_proxies, uuid=None,
http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome',
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""
使用 VLESS 协议发送请求
@@ -667,27 +673,33 @@ def _request_vless(method, url, cf_proxies, uuid=None,
static_ip: 是否使用固定 IP默认 False
- False: 每次请求获取新的出口 IP适合大规模采集
- True: 保持使用同一个 IP适合需要会话一致性的场景
two_proxy: 第二层代理(可选)
格式: "host:port:user:pass"
流程: 本地 → Workers (VLESS) → 第二层代理 → 目标网站
其他参数与 request() 相同
"""
from .vless_client import LocalVlessProxy
import uuid as uuid_mod
# 获取 Workers 配置
# 解析 Workers 地址获取 host
parsed = urlparse(cf_proxies)
if parsed.scheme:
host = parsed.netloc or parsed.path.split('/')[0]
else:
host = cf_proxies.split('/')[0]
# 尝试从 Workers 获取配置
config = _get_workers_config(cf_proxies)
if not config:
raise ValueError(
f"无法从 {cf_proxies} 获取配置。\n"
"请确保 Workers 已正确部署。"
)
# 解析配置
host = config.get('host')
is_default_uuid = config.get('is_default_uuid', True)
# 如果用户提供了 uuid使用用户的
# 如果用户没提供,尝试从 Workers 获取(仅默认 UUID 可获取)
# 如果用户没提供 uuid使用 Workers 配置的 uuid
if not uuid:
if not config:
raise ValueError(
f"无法从 {cf_proxies} 获取配置。\n"
"请确保 Workers 已正确部署,或手动指定 uuid 参数。"
)
uuid = config.get('uuid')
host = config.get('host', host)
if not uuid:
# Workers 配置了自定义 UUID需要用户手动填写
@@ -697,23 +709,42 @@ def _request_vless(method, url, cf_proxies, uuid=None,
"提示: UUID 可在 Workers 界面或 Cloudflare Dashboard 中查看。"
)
# 自动获取 Workers 配置的 two_proxy
# 优先级:用户传入的 two_proxy > Workers 配置的 two_proxy
if two_proxy is None and config:
# 检查 Workers 是否配置了双层代理
if config.get('two_proxy_enabled'):
# 优先使用完整的 two_proxy 配置
workers_two_proxy = config.get('two_proxy')
if workers_two_proxy:
two_proxy = workers_two_proxy
else:
# 尝试从 vless_path 中解析
vless_path = config.get('vless_path', '')
if 'two_proxy=' in vless_path:
import re
match = re.search(r'two_proxy=([^&]+)', vless_path)
if match:
from urllib.parse import unquote
two_proxy = unquote(match.group(1))
# 构建 VLESS WebSocket URL: wss://host/uuid
vless_url = f"wss://{host}/{uuid}"
# 根据 static_ip 参数决定是否复用连接
if static_ip:
# 固定 IP 模式:复用同一个连接
cache_key = f"{host}:{uuid}"
cache_key = f"{host}:{uuid}:{two_proxy or ''}"
if cache_key in _vless_proxy_cache:
proxy = _vless_proxy_cache[cache_key]
port = proxy.port
else:
proxy = LocalVlessProxy(vless_url, uuid)
proxy = LocalVlessProxy(vless_url, uuid, two_proxy=two_proxy)
port = proxy.start()
_vless_proxy_cache[cache_key] = proxy
else:
# 动态 IP 模式:每次创建新连接
proxy = LocalVlessProxy(vless_url, uuid)
proxy = LocalVlessProxy(vless_url, uuid, two_proxy=two_proxy)
port = proxy.start()
# 构建本地代理 URL
@@ -827,7 +858,7 @@ def stop_vless_proxies():
def get(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""
发送 GET 请求 / Send GET request
@@ -845,6 +876,11 @@ def get(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
- False: 每次请求获取新的出口 IP适合大规模采集
- True: 保持使用同一个 IP适合需要会话一致性的场景
two_proxy: 第二层代理(可选)
格式: "host:port:user:pass""host:port"
例如: "us.cliproxy.io:3010:username:password"
流程: 本地 → Workers (VLESS) → 第二层代理 → 目标网站
http2: 是否启用 HTTP/2 协议(默认 False
impersonate: TLS 指纹模拟(可选)
@@ -872,90 +908,90 @@ def get(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
... cf_proxies="https://cfspider.violetqqcom.workers.dev"
... )
>>>
>>> # 固定 IP保持同一个 IP
>>> # 使用第二层代理(通过 Workers 连接到日本代理
>>> response = cfspider.get(
... "https://httpbin.org/ip",
... cf_proxies="https://cfspider.violetqqcom.workers.dev",
... static_ip=True
... two_proxy="us.cliproxy.io:3010:username:password"
... )
"""
return request("GET", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def post(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 POST 请求 / Send POST request"""
return request("POST", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def put(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 PUT 请求 / Send PUT request"""
return request("PUT", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def delete(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 DELETE 请求 / Send DELETE request"""
return request("DELETE", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def head(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 HEAD 请求 / Send HEAD request"""
return request("HEAD", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def options(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 OPTIONS 请求 / Send OPTIONS request"""
return request("OPTIONS", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def patch(url, cf_proxies=None, uuid=None, http2=False, impersonate=None,
map_output=False, map_file="cfspider_map.html",
stealth=False, stealth_browser='chrome', delay=None,
static_ip=False, **kwargs):
static_ip=False, two_proxy=None, **kwargs):
"""发送 PATCH 请求 / Send PATCH request"""
return request("PATCH", url, cf_proxies=cf_proxies, uuid=uuid,
http2=http2, impersonate=impersonate,
map_output=map_output, map_file=map_file,
stealth=stealth, stealth_browser=stealth_browser, delay=delay,
static_ip=static_ip, **kwargs)
static_ip=static_ip, two_proxy=two_proxy, **kwargs)
def clear_map_records():

View File

@@ -117,8 +117,8 @@ class Browser:
- CFspider Workers URL推荐: "https://cfspider.violetqqcom.workers.dev"
UUID 将自动从 Workers 获取 / UUID auto-fetched from Workers
- VLESS 链接: "vless://uuid@host:port?path=/xxx#name"
- HTTP 代理: "http://ip:port""ip:port"
- SOCKS5 代理: "socks5://ip:port"
- HTTP 代理: "http://ip:port""ip:port"
- SOCKS5 代理: "socks5://ip:port"
不填则直接使用本地网络 / None for direct connection
headless (bool): 是否无头模式,默认 True / Headless mode (default: True)
timeout (int): 请求超时时间(秒),默认 30 / Timeout in seconds (default: 30)
@@ -176,7 +176,7 @@ class Browser:
proxy_url = f"http://127.0.0.1:{port}"
else:
# 直接使用 HTTP 代理
proxy_url = cf_proxies
proxy_url = cf_proxies
# 3. IP:PORT 格式
elif ':' in cf_proxies and cf_proxies.replace('.', '').replace(':', '').isdigit():
proxy_url = f"http://{cf_proxies}"
@@ -187,10 +187,10 @@ class Browser:
if uuid:
ws_url = f'wss://{hostname}/{uuid}'
self._vless_proxy = LocalVlessProxy(ws_url, uuid)
port = self._vless_proxy.start()
proxy_url = f"http://127.0.0.1:{port}"
else:
proxy_url = f"http://{cf_proxies}"
port = self._vless_proxy.start()
proxy_url = f"http://127.0.0.1:{port}"
else:
proxy_url = f"http://{cf_proxies}"
def _get_workers_uuid(self, workers_url):
"""从 Workers 获取 UUID / Get UUID from Workers"""

350
cfspider/proxy_server.py Normal file
View File

@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
"""
CFspider 本地代理服务器
支持双层代理two_proxy可供 v2ray 或浏览器使用
"""
import socket
import threading
import time
from urllib.parse import urlparse, quote
def generate_vless_link(
cf_proxies: str,
uuid: str,
name: str = "CFspider",
two_proxy: str = None
) -> dict:
"""
生成 VLESS 导入链接
Args:
cf_proxies: Workers 地址,如 "https://your-workers.dev"
uuid: VLESS UUID
name: 节点名称
two_proxy: 第二层代理(可选),格式 "host:port:user:pass"
Returns:
dict: {
'vless_link': str, # VLESS 链接(可导入 v2ray
'local_proxy_cmd': str, # 启动本地代理的命令
'note': str # 使用说明
}
Example:
>>> result = cfspider.generate_vless_link(
... cf_proxies="https://your-workers.dev",
... uuid="your-uuid",
... two_proxy="us.cliproxy.io:3010:user:pass"
... )
>>> print(result['vless_link'])
"""
# 解析 Workers 地址
parsed = urlparse(cf_proxies)
if parsed.scheme:
host = parsed.netloc or parsed.path.split('/')[0]
else:
host = cf_proxies.split('/')[0]
# 生成基础 VLESS 链接(连接到 Workers
vless_params = {
'security': 'tls',
'type': 'ws',
'host': host,
'sni': host,
'path': f'/{uuid}',
'encryption': 'none'
}
params_str = '&'.join([f'{k}={quote(str(v))}' for k, v in vless_params.items()])
vless_link = f"vless://{uuid}@{host}:443?{params_str}#{quote(name)}"
result = {
'vless_link': vless_link,
'host': host,
'uuid': uuid,
}
if two_proxy:
# 解析第二层代理
parts = two_proxy.split(':')
if len(parts) >= 2:
proxy_host = parts[0]
proxy_port = parts[1]
proxy_info = f"{proxy_host}:{proxy_port}"
if len(parts) == 4:
proxy_info += f" (user: {parts[2][:4]}***)"
else:
proxy_info = two_proxy
# 生成本地代理启动命令
result['local_proxy_cmd'] = (
f'python -m cfspider.proxy_server '
f'--cf-proxies "{cf_proxies}" '
f'--uuid "{uuid}" '
f'--two-proxy "{two_proxy}" '
f'--port 1080'
)
result['note'] = f"""
=== 双层代理使用说明 ===
由于 VLESS 协议本身不支持代理链,有两种方式使用双层代理:
【方式 1】使用 CFspider 本地代理(推荐)
运行以下命令启动本地代理服务器:
{result['local_proxy_cmd']}
然后在浏览器或系统中配置代理为:
HTTP/SOCKS5 代理: 127.0.0.1:1080
流量链路: 浏览器 → 本地代理(1080) → Workers(VLESS) → {proxy_info} → 目标网站
【方式 2】在 Workers 端配置 PROXYIP
1. 打开 Cloudflare Dashboard → Workers
2. 选择你的 Worker → Settings → Variables
3. 添加环境变量: PROXYIP = {parts[0] if len(parts) >= 2 else two_proxy}
4. 然后使用以下 VLESS 链接导入 v2ray
{vless_link}
注意:方式 2 需要第二层代理支持无认证连接,或在 Workers 代码中配置认证。
"""
else:
result['note'] = f"""
=== VLESS 链接使用说明 ===
将以下链接导入 v2ray/clash 等客户端:
{vless_link}
流量链路: v2ray 客户端 → Workers(VLESS) → 目标网站
出口 IP: Cloudflare WARP 网络 IP
"""
return result
class TwoProxyServer:
"""
双层代理本地服务器
提供 HTTP/SOCKS5 代理接口,内部通过 VLESS + two_proxy 实现
"""
def __init__(
self,
cf_proxies: str,
uuid: str,
two_proxy: str = None,
host: str = '127.0.0.1',
port: int = 1080
):
"""
初始化本地代理服务器
Args:
cf_proxies: Workers 地址
uuid: VLESS UUID
two_proxy: 第二层代理,格式 "host:port:user:pass"
host: 监听地址(默认 127.0.0.1
port: 监听端口(默认 1080
"""
self.cf_proxies = cf_proxies
self.uuid = uuid
self.two_proxy = two_proxy
self.host = host
self.port = port
self.server = None
self.running = False
self._vless_proxy = None
self._vless_port = None
def _ensure_vless_proxy(self):
"""确保 VLESS 代理已启动"""
if self._vless_proxy is None:
from .vless_client import LocalVlessProxy
# 解析 Workers 地址
parsed = urlparse(self.cf_proxies)
if parsed.scheme:
host = parsed.netloc or parsed.path.split('/')[0]
else:
host = self.cf_proxies.split('/')[0]
vless_url = f"wss://{host}/{self.uuid}"
print(f"[INIT] Starting VLESS proxy: {vless_url}")
if self.two_proxy:
print(f"[INIT] Two-proxy enabled: {self.two_proxy.split(':')[0]}:{self.two_proxy.split(':')[1]}")
self._vless_proxy = LocalVlessProxy(vless_url, self.uuid, two_proxy=self.two_proxy)
self._vless_port = self._vless_proxy.start()
print(f"[INIT] VLESS proxy started on port {self._vless_port}")
def start(self):
"""启动代理服务器"""
self._ensure_vless_proxy()
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind((self.host, self.port))
self.server.listen(50)
self.running = True
print(f"=" * 60)
print(f"CFspider Two-Proxy Server Started")
print(f"=" * 60)
print(f"Local Proxy: {self.host}:{self.port}")
print(f"Workers: {self.cf_proxies}")
if self.two_proxy:
parts = self.two_proxy.split(':')
print(f"Second Proxy: {parts[0]}:{parts[1]}")
print(f"-" * 60)
print(f"Configure your browser/system proxy to: {self.host}:{self.port}")
print(f"Press Ctrl+C to stop")
print(f"=" * 60)
try:
while self.running:
try:
self.server.settimeout(1)
client, addr = self.server.accept()
handler = threading.Thread(
target=self._handle_client,
args=(client,),
daemon=True
)
handler.start()
except socket.timeout:
continue
except KeyboardInterrupt:
print("\nShutting down...")
finally:
self.stop()
def _handle_client(self, client):
"""处理客户端连接,转发到内部 VLESS 代理"""
client_addr = None
try:
client_addr = client.getpeername()
client.settimeout(30)
# 连接到内部 VLESS 代理
vless_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
vless_sock.settimeout(30)
vless_sock.connect(('127.0.0.1', self._vless_port))
print(f"[CONN] {client_addr} -> VLESS proxy :{self._vless_port}")
# 双向转发
stop_event = threading.Event()
def forward(src, dst, name):
try:
while not stop_event.is_set():
try:
src.settimeout(1)
data = src.recv(8192)
if not data:
break
dst.sendall(data)
except socket.timeout:
continue
except Exception as e:
break
except Exception as e:
pass
finally:
stop_event.set()
t1 = threading.Thread(target=forward, args=(client, vless_sock, "C->V"), daemon=True)
t2 = threading.Thread(target=forward, args=(vless_sock, client, "V->C"), daemon=True)
t1.start()
t2.start()
# 等待任一方向结束
while not stop_event.is_set():
time.sleep(0.1)
print(f"[CLOSE] {client_addr}")
except Exception as e:
print(f"[ERROR] {client_addr}: {e}")
finally:
try:
client.close()
except:
pass
try:
vless_sock.close()
except:
pass
def stop(self):
"""停止服务器"""
self.running = False
if self.server:
try:
self.server.close()
except:
pass
if self._vless_proxy:
try:
self._vless_proxy.stop()
except:
pass
def start_proxy_server(
cf_proxies: str,
uuid: str,
two_proxy: str = None,
host: str = '127.0.0.1',
port: int = 1080
):
"""
启动本地代理服务器
Args:
cf_proxies: Workers 地址
uuid: VLESS UUID
two_proxy: 第二层代理(可选)
host: 监听地址(默认 127.0.0.1
port: 监听端口(默认 1080
Example:
>>> cfspider.start_proxy_server(
... cf_proxies="https://your-workers.dev",
... uuid="your-uuid",
... two_proxy="us.cliproxy.io:3010:user:pass",
... port=1080
... )
"""
server = TwoProxyServer(cf_proxies, uuid, two_proxy, host, port)
server.start()
# 命令行入口
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='CFspider Two-Proxy Server')
parser.add_argument('--cf-proxies', required=True, help='Workers address')
parser.add_argument('--uuid', required=True, help='VLESS UUID')
parser.add_argument('--two-proxy', help='Second layer proxy (host:port:user:pass)')
parser.add_argument('--host', default='127.0.0.1', help='Listen host (default: 127.0.0.1)')
parser.add_argument('--port', type=int, default=1080, help='Listen port (default: 1080)')
args = parser.parse_args()
start_proxy_server(
cf_proxies=args.cf_proxies,
uuid=args.uuid,
two_proxy=args.two_proxy,
host=args.host,
port=args.port
)

View File

@@ -270,21 +270,52 @@ class VlessConnection:
class LocalVlessProxy:
"""本地 VLESS HTTP 代理服务器"""
def __init__(self, ws_url, vless_uuid=None):
def __init__(self, ws_url, vless_uuid=None, two_proxy=None):
"""
初始化本地代理
Args:
ws_url: edgetunnel WebSocket 地址
vless_uuid: VLESS UUID
two_proxy: 第二层代理,格式为 "host:port:user:pass"
例如 "us.cliproxy.io:3010:username:password"
"""
self.ws_url = ws_url
self.vless_uuid = vless_uuid
self.two_proxy = self._parse_two_proxy(two_proxy) if two_proxy else None
self.server = None
self.thread = None
self.port = None
self.running = False
def _parse_two_proxy(self, two_proxy):
"""解析第二层代理配置"""
if not two_proxy:
return None
# 格式: host:port:user:pass
parts = two_proxy.split(':')
if len(parts) == 4:
return {
'host': parts[0],
'port': int(parts[1]),
'user': parts[2],
'pass': parts[3]
}
elif len(parts) == 2:
# 无认证: host:port
return {
'host': parts[0],
'port': int(parts[1]),
'user': None,
'pass': None
}
else:
raise ValueError(
f"Invalid two_proxy format: {two_proxy}\n"
"Expected format: host:port:user:pass or host:port"
)
def start(self):
"""启动代理服务器"""
# 找可用端口
@@ -369,9 +400,41 @@ class LocalVlessProxy:
def _handle_connect(self, client, host, port):
"""处理 HTTPS CONNECT 请求"""
try:
# 连接到 VLESS
vless = VlessClient(self.ws_url, self.vless_uuid)
conn = vless.connect(host, port)
if self.two_proxy:
# 使用第二层代理:通过 VLESS 连接到第二层代理
proxy = self.two_proxy
conn = vless.connect(proxy['host'], proxy['port'])
# 向第二层代理发送 CONNECT 请求
connect_request = f"CONNECT {host}:{port} HTTP/1.1\r\n"
connect_request += f"Host: {host}:{port}\r\n"
# 添加代理认证
if proxy['user'] and proxy['pass']:
import base64
auth = base64.b64encode(f"{proxy['user']}:{proxy['pass']}".encode()).decode()
connect_request += f"Proxy-Authorization: Basic {auth}\r\n"
connect_request += "\r\n"
conn.send(connect_request.encode())
# 读取代理响应
response = b''
while b'\r\n\r\n' not in response:
chunk = conn.recv(4096)
if not chunk:
raise Exception("Second proxy connection failed")
response += chunk
# 检查代理是否连接成功
status_line = response.split(b'\r\n')[0].decode()
if '200' not in status_line:
raise Exception(f"Second proxy CONNECT failed: {status_line}")
else:
# 直接连接目标
conn = vless.connect(host, port)
# 发送连接成功
client.sendall(b'HTTP/1.1 200 Connection Established\r\n\r\n')
@@ -395,31 +458,64 @@ class LocalVlessProxy:
if parsed.query:
path += '?' + parsed.query
# 连接到 VLESS
vless = VlessClient(self.ws_url, self.vless_uuid)
conn = vless.connect(host, port)
# 重建请求
lines = original_request.split(b'\r\n')
lines[0] = f'{method} {path} HTTP/1.1'.encode('utf-8')
# 更新 Host 头
new_lines = [lines[0]]
has_host = False
for line in lines[1:]:
if line.lower().startswith(b'host:'):
new_lines.append(f'Host: {host}'.encode('utf-8'))
has_host = True
elif line.lower().startswith(b'proxy-'):
continue
else:
new_lines.append(line)
if not has_host:
new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
request = b'\r\n'.join(new_lines)
conn.send(request)
if self.two_proxy:
# 使用第二层代理
proxy = self.two_proxy
conn = vless.connect(proxy['host'], proxy['port'])
# 重建请求(保留完整 URL因为是发给代理的
lines = original_request.split(b'\r\n')
# 更新请求头
new_lines = [lines[0]] # 保持原始请求行(包含完整 URL
has_host = False
for line in lines[1:]:
if line.lower().startswith(b'host:'):
new_lines.append(f'Host: {host}'.encode('utf-8'))
has_host = True
elif line.lower().startswith(b'proxy-'):
continue # 移除原有的代理头
else:
new_lines.append(line)
if not has_host:
new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
# 添加代理认证
if proxy['user'] and proxy['pass']:
import base64
auth = base64.b64encode(f"{proxy['user']}:{proxy['pass']}".encode()).decode()
new_lines.insert(1, f'Proxy-Authorization: Basic {auth}'.encode('utf-8'))
request = b'\r\n'.join(new_lines)
conn.send(request)
else:
# 直接连接目标
conn = vless.connect(host, port)
# 重建请求
lines = original_request.split(b'\r\n')
lines[0] = f'{method} {path} HTTP/1.1'.encode('utf-8')
# 更新 Host 头
new_lines = [lines[0]]
has_host = False
for line in lines[1:]:
if line.lower().startswith(b'host:'):
new_lines.append(f'Host: {host}'.encode('utf-8'))
has_host = True
elif line.lower().startswith(b'proxy-'):
continue
else:
new_lines.append(line)
if not has_host:
new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
request = b'\r\n'.join(new_lines)
conn.send(request)
# 读取响应并转发
self._relay_response(client, conn)