mirror of
https://github.com/honmashironeko/ProxyCat.git
synced 2025-06-21 10:23:01 +00:00
2.0.4
This commit is contained in:
parent
d207a3bb8f
commit
086d37aa73
@ -1,5 +1,11 @@
|
|||||||
# ProxyCat 使用手册
|
# ProxyCat 使用手册
|
||||||
|
|
||||||
|
## 重要事项
|
||||||
|
|
||||||
|
- Python版本最好为Python 3.11
|
||||||
|
- Releases中为较为稳定的打包版本,不一定是最新
|
||||||
|
- API 接口所获取的代理地址必须为 IP:PORT 格式且只提供一条地址
|
||||||
|
|
||||||
## 源码使用及 Docker 部署
|
## 源码使用及 Docker 部署
|
||||||
|
|
||||||
### 源码手册
|
### 源码手册
|
||||||
@ -52,6 +58,7 @@ getip_url = 获取代理地址的 API 接口
|
|||||||
|
|
||||||
```
|
```
|
||||||
python ProxyCat.py
|
python ProxyCat.py
|
||||||
|
python app.py (Web控制管理-推荐方式)
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@ -90,6 +97,7 @@ docker logs proxycat
|
|||||||
# 0: 仅显示代理切换和错误信息
|
# 0: 仅显示代理切换和错误信息
|
||||||
# 1: 显示代理切换、倒计时和错误信息
|
# 1: 显示代理切换、倒计时和错误信息
|
||||||
# 2: 显示所有详细信息
|
# 2: 显示所有详细信息
|
||||||
|
# 仅终端管理时生效
|
||||||
display_level = 1
|
display_level = 1
|
||||||
|
|
||||||
# 本地服务器监听端口(默认为:1080)
|
# 本地服务器监听端口(默认为:1080)
|
||||||
@ -99,8 +107,8 @@ port = 1080
|
|||||||
# Web 管理页面端口(默认为:5000)
|
# Web 管理页面端口(默认为:5000)
|
||||||
web_port = 5000
|
web_port = 5000
|
||||||
|
|
||||||
# 代理地址轮换模式:cycle 表示循环使用,custom 表示使用自定义模式,load_balance 表示负载均衡(默认为:cycle)
|
# 代理地址轮换模式:cycle 表示循环使用,loadbalance 表示负载均衡(默认为:cycle)
|
||||||
# Proxy rotation mode: cycle means cyclic use, custom means custom mode, load_balance means load balancing (default:cycle)
|
# Proxy rotation mode: cycle means cyclic use, loadbalance means load balancing (default:cycle)
|
||||||
mode = cycle
|
mode = cycle
|
||||||
|
|
||||||
# 代理地址更换时间(秒),设置为 0 时每次请求都更换 IP(默认为:300)
|
# 代理地址更换时间(秒),设置为 0 时每次请求都更换 IP(默认为:300)
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
### 2025/03/23
|
||||||
|
|
||||||
|
- 修复负载均衡模式无法调用的BUG
|
||||||
|
- 修复socks5连接错误
|
||||||
|
- 修复http、socks5监听下的目标网站错误和代理地址失效情况一致导致无法正常触发代理切换
|
||||||
|
- 修改代理有效性校验,配置为可控检测,关闭后将不会进行有效性检查避免特殊情况一直切换
|
||||||
|
- 修复并发下导致大规模触发更换和提示的问题,锁定操作的原子性
|
||||||
|
- 修复大量细节逻辑、描述错误
|
||||||
|
- 当前代理切换触发条件为:时间间隔到期切换、代理失效自动切换、Web手动切换、API下首次请求自动获取
|
||||||
|
|
||||||
|
### 2025/03/17
|
||||||
|
|
||||||
|
- 修复目标站点本身错误时会触发代理切换的错误逻辑
|
||||||
|
- 修改连接关闭方式
|
||||||
|
- 优化监听服务器性能
|
||||||
|
- 修复多处错误BUG
|
||||||
|
|
||||||
### 2025/03/14
|
### 2025/03/14
|
||||||
|
|
||||||
- 修复'_last_used'报错问题,连接关闭方式修正
|
- 修复'_last_used'报错问题,连接关闭方式修正
|
||||||
|
32
ProxyCat.py
32
ProxyCat.py
@ -70,7 +70,7 @@ def update_status(server):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if server.mode == 'load_balance':
|
if server.mode == 'loadbalance':
|
||||||
if display_level >= 1:
|
if display_level >= 1:
|
||||||
print_proxy_info()
|
print_proxy_info()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@ -155,7 +155,7 @@ async def run_server(server):
|
|||||||
async def run_proxy_check(server):
|
async def run_proxy_check(server):
|
||||||
if server.config.get('check_proxies', 'False').lower() == 'true':
|
if server.config.get('check_proxies', 'False').lower() == 'true':
|
||||||
logging.info(get_message('proxy_check_start', server.language))
|
logging.info(get_message('proxy_check_start', server.language))
|
||||||
valid_proxies = await check_proxies(server.proxies)
|
valid_proxies = await check_proxies(server.proxies, server.test_url)
|
||||||
if valid_proxies:
|
if valid_proxies:
|
||||||
server.proxies = valid_proxies
|
server.proxies = valid_proxies
|
||||||
server.proxy_cycle = cycle(valid_proxies)
|
server.proxy_cycle = cycle(valid_proxies)
|
||||||
@ -187,6 +187,11 @@ class ProxyCat:
|
|||||||
if hasattr(socket, 'SO_KEEPALIVE'):
|
if hasattr(socket, 'SO_KEEPALIVE'):
|
||||||
socket.SO_KEEPALIVE = True
|
socket.SO_KEEPALIVE = True
|
||||||
|
|
||||||
|
if hasattr(socket, 'SO_REUSEADDR'):
|
||||||
|
socket.SO_REUSEADDR = True
|
||||||
|
if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':
|
||||||
|
socket.SO_REUSEPORT = True
|
||||||
|
|
||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
import resource
|
import resource
|
||||||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||||
@ -225,11 +230,17 @@ class ProxyCat:
|
|||||||
|
|
||||||
async def start_server(self):
|
async def start_server(self):
|
||||||
try:
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
if hasattr(socket, 'SO_REUSEPORT') and os.name != 'nt':
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||||
|
sock.bind((self.config.get('SERVER', 'host'), int(self.config.get('SERVER', 'port'))))
|
||||||
|
|
||||||
server = await asyncio.start_server(
|
server = await asyncio.start_server(
|
||||||
self.handle_client,
|
self.handle_client,
|
||||||
self.config.get('SERVER', 'host'),
|
sock=sock
|
||||||
self.config.get('SERVER', 'port')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info(get_message('server_running', self.language,
|
logging.info(get_message('server_running', self.language,
|
||||||
self.config.get('SERVER', 'host'),
|
self.config.get('SERVER', 'host'),
|
||||||
self.config.get('SERVER', 'port')))
|
self.config.get('SERVER', 'port')))
|
||||||
@ -396,6 +407,16 @@ class ProxyCat:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('data_transfer_error', self.language, e))
|
logging.error(get_message('data_transfer_error', self.language, e))
|
||||||
|
|
||||||
|
def monitor_resources(self):
|
||||||
|
import psutil
|
||||||
|
process = psutil.Process(os.getpid())
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
mem_info = process.memory_info()
|
||||||
|
logging.debug(f"Memory usage: {mem_info.rss / 1024 / 1024:.2f} MB, "
|
||||||
|
f"Connections: {len(self.tasks)}")
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setup_logging()
|
setup_logging()
|
||||||
parser = argparse.ArgumentParser(description=logos())
|
parser = argparse.ArgumentParser(description=logos())
|
||||||
@ -413,6 +434,9 @@ if __name__ == '__main__':
|
|||||||
status_thread = threading.Thread(target=update_status, args=(server,), daemon=True)
|
status_thread = threading.Thread(target=update_status, args=(server,), daemon=True)
|
||||||
status_thread.start()
|
status_thread.start()
|
||||||
|
|
||||||
|
cleanup_thread = threading.Thread(target=lambda: asyncio.run(server.cleanup_clients()), daemon=True)
|
||||||
|
cleanup_thread.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asyncio.run(run_server(server))
|
asyncio.run(run_server(server))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
87
app.py
87
app.py
@ -119,37 +119,26 @@ def get_status():
|
|||||||
else:
|
else:
|
||||||
current_proxy = get_message('no_proxy', server.language)
|
current_proxy = get_message('no_proxy', server.language)
|
||||||
|
|
||||||
|
|
||||||
|
time_left = server.time_until_next_switch()
|
||||||
|
if server.mode == 'loadbalance':
|
||||||
|
|
||||||
|
if time_left == float('inf'):
|
||||||
|
time_left = -1
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'current_proxy': current_proxy,
|
'current_proxy': current_proxy,
|
||||||
'mode': server.mode,
|
'mode': server.mode,
|
||||||
'port': int(server_config.get('port', '1080')),
|
'port': int(server_config.get('port', '1080')),
|
||||||
'interval': server.interval,
|
'interval': server.interval,
|
||||||
'time_left': server.time_until_next_switch(),
|
'time_left': time_left,
|
||||||
'total_proxies': len(server.proxies) if hasattr(server, 'proxies') else 0,
|
'total_proxies': len(server.proxies) if hasattr(server, 'proxies') else 0,
|
||||||
'use_getip': server.use_getip,
|
'use_getip': server.use_getip,
|
||||||
'getip_url': getattr(server, 'getip_url', '') if getattr(server, 'use_getip', False) else '',
|
'getip_url': getattr(server, 'getip_url', '') if getattr(server, 'use_getip', False) else '',
|
||||||
'auth_required': server.auth_required,
|
'auth_required': server.auth_required,
|
||||||
'display_level': int(config.get('DEFAULT', 'display_level', fallback='1')),
|
'display_level': int(server_config.get('display_level', '1')),
|
||||||
'service_status': 'running' if server.running else 'stopped',
|
'service_status': 'running' if server.running else 'stopped',
|
||||||
'config': {
|
'config': server_config
|
||||||
'port': server_config.get('port', ''),
|
|
||||||
'mode': server_config.get('mode', 'cycle'),
|
|
||||||
'interval': server_config.get('interval', ''),
|
|
||||||
'username': server_config.get('username', ''),
|
|
||||||
'password': server_config.get('password', ''),
|
|
||||||
'use_getip': server_config.get('use_getip', 'False'),
|
|
||||||
'getip_url': server_config.get('getip_url', ''),
|
|
||||||
'proxy_username': server_config.get('proxy_username', ''),
|
|
||||||
'proxy_password': server_config.get('proxy_password', ''),
|
|
||||||
'proxy_file': server_config.get('proxy_file', ''),
|
|
||||||
'check_proxies': server_config.get('check_proxies', 'False'),
|
|
||||||
'language': server_config.get('language', 'cn'),
|
|
||||||
'whitelist_file': server_config.get('whitelist_file', ''),
|
|
||||||
'blacklist_file': server_config.get('blacklist_file', ''),
|
|
||||||
'ip_auth_priority': server_config.get('ip_auth_priority', 'whitelist'),
|
|
||||||
'display_level': config.get('DEFAULT', 'display_level', fallback='1'),
|
|
||||||
'raw_content': config_content
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.route('/api/config', methods=['POST'])
|
@app.route('/api/config', methods=['POST'])
|
||||||
@ -158,6 +147,8 @@ def save_config():
|
|||||||
new_config = request.get_json()
|
new_config = request.get_json()
|
||||||
current_config = load_config('config/config.ini')
|
current_config = load_config('config/config.ini')
|
||||||
port_changed = str(new_config.get('port', '')) != str(current_config.get('port', ''))
|
port_changed = str(new_config.get('port', '')) != str(current_config.get('port', ''))
|
||||||
|
mode_changed = new_config.get('mode', '') != current_config.get('mode', '')
|
||||||
|
use_getip_changed = (new_config.get('use_getip', 'False').lower() == 'true') != (current_config.get('use_getip', 'False').lower() == 'true')
|
||||||
|
|
||||||
config_parser = ConfigParser()
|
config_parser = ConfigParser()
|
||||||
config_parser.read('config/config.ini', encoding='utf-8')
|
config_parser.read('config/config.ini', encoding='utf-8')
|
||||||
@ -172,12 +163,27 @@ def save_config():
|
|||||||
with open('config/config.ini', 'w', encoding='utf-8') as f:
|
with open('config/config.ini', 'w', encoding='utf-8') as f:
|
||||||
config_parser.write(f)
|
config_parser.write(f)
|
||||||
|
|
||||||
|
|
||||||
|
old_mode = server.mode
|
||||||
|
old_use_getip = server.use_getip
|
||||||
|
|
||||||
|
|
||||||
server.config = load_config('config/config.ini')
|
server.config = load_config('config/config.ini')
|
||||||
server._init_config_values(server.config)
|
server._init_config_values(server.config)
|
||||||
|
|
||||||
|
|
||||||
|
if mode_changed or use_getip_changed:
|
||||||
|
server._handle_mode_change()
|
||||||
|
|
||||||
|
|
||||||
|
if new_config.get('mode') == 'loadbalance':
|
||||||
|
server.last_switch_time = time.time()
|
||||||
|
server.last_switch_attempt = 0
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'port_changed': port_changed
|
'port_changed': port_changed,
|
||||||
|
'service_status': 'running' if server.running else 'stopped'
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -324,35 +330,8 @@ def clear_logs():
|
|||||||
@require_token
|
@require_token
|
||||||
def switch_proxy():
|
def switch_proxy():
|
||||||
try:
|
try:
|
||||||
if server.use_getip:
|
result = asyncio.run(server.switch_proxy())
|
||||||
from config.getip import newip
|
if result:
|
||||||
try:
|
|
||||||
old_proxy = server.current_proxy
|
|
||||||
new_proxy = newip()
|
|
||||||
server.current_proxy = new_proxy
|
|
||||||
server.last_switch_time = time.time()
|
|
||||||
server._log_proxy_switch(old_proxy, new_proxy)
|
|
||||||
return jsonify({
|
|
||||||
'status': 'success',
|
|
||||||
'current_proxy': server.current_proxy,
|
|
||||||
'message': get_message('switch_success', server.language)
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({
|
|
||||||
'status': 'error',
|
|
||||||
'message': get_message('get_proxy_failed', server.language, str(e))
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
if not server.proxies:
|
|
||||||
server.proxies = server._load_file_proxies()
|
|
||||||
if server.proxies:
|
|
||||||
server.proxy_cycle = cycle(server.proxies)
|
|
||||||
|
|
||||||
if server.proxy_cycle:
|
|
||||||
old_proxy = server.current_proxy
|
|
||||||
server.current_proxy = next(server.proxy_cycle)
|
|
||||||
server.last_switch_time = time.time()
|
|
||||||
server._log_proxy_switch(old_proxy, server.current_proxy)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
'current_proxy': server.current_proxy,
|
'current_proxy': server.current_proxy,
|
||||||
@ -361,7 +340,7 @@ def switch_proxy():
|
|||||||
else:
|
else:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': get_message('no_proxies_available', server.language)
|
'message': get_message('switch_failed', server.language, 'Proxy switch not needed or in cooldown')
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
@ -523,7 +502,7 @@ def check_version():
|
|||||||
original_level = httpx_logger.level
|
original_level = httpx_logger.level
|
||||||
httpx_logger.setLevel(logging.WARNING)
|
httpx_logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
CURRENT_VERSION = "ProxyCat-V2.0.2"
|
CURRENT_VERSION = "ProxyCat-V2.0.4"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
|
client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
|
||||||
@ -630,7 +609,7 @@ if __name__ == '__main__':
|
|||||||
web_port = int(config.get('web_port', '5000'))
|
web_port = int(config.get('web_port', '5000'))
|
||||||
web_url = f"http://127.0.0.1:{web_port}"
|
web_url = f"http://127.0.0.1:{web_port}"
|
||||||
if config.get('token'):
|
if config.get('token'):
|
||||||
web_url += f"?token={config.get('token')}"
|
web_url += f"/web?token={config.get('token')}"
|
||||||
|
|
||||||
logging.info(get_message('web_panel_url', server.language, web_url))
|
logging.info(get_message('web_panel_url', server.language, web_url))
|
||||||
logging.info(get_message('web_panel_notice', server.language))
|
logging.info(get_message('web_panel_notice', server.language))
|
||||||
|
@ -4,12 +4,12 @@ port = 1080
|
|||||||
web_port = 5000
|
web_port = 5000
|
||||||
mode = cycle
|
mode = cycle
|
||||||
interval = 300
|
interval = 300
|
||||||
use_getip = False
|
use_getip = false
|
||||||
getip_url = http://example.com/getip
|
getip_url = http://example.com/getip
|
||||||
proxy_username =
|
proxy_username =
|
||||||
proxy_password =
|
proxy_password =
|
||||||
proxy_file = ip.txt
|
proxy_file = ip.txt
|
||||||
check_proxies = True
|
check_proxies = true
|
||||||
test_url =
|
test_url =
|
||||||
language = cn
|
language = cn
|
||||||
whitelist_file = whitelist.txt
|
whitelist_file = whitelist.txt
|
||||||
@ -19,4 +19,5 @@ token = honmashironeko
|
|||||||
|
|
||||||
[Users]
|
[Users]
|
||||||
neko = 123456
|
neko = 123456
|
||||||
|
k = 123
|
||||||
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
socks5://127.0.0.1:7890
|
|
||||||
http://127.0.0.1:7890
|
http://127.0.0.1:7890
|
||||||
|
socks5://127.0.0.1:7890
|
@ -28,6 +28,9 @@ MESSAGES = {
|
|||||||
'proxy_switch': '切换代理: {} -> {}',
|
'proxy_switch': '切换代理: {} -> {}',
|
||||||
'proxy_consecutive_fails': '代理 {} 连续失败 {} 次,正在切换新代理',
|
'proxy_consecutive_fails': '代理 {} 连续失败 {} 次,正在切换新代理',
|
||||||
'proxy_invalid': '代理 {} 无效,立即切换代理',
|
'proxy_invalid': '代理 {} 无效,立即切换代理',
|
||||||
|
'proxy_failure': '代理检测失败: {}',
|
||||||
|
'proxy_failure_threshold': '代理无效,开始切换',
|
||||||
|
'proxy_check_error': '代理检查时发生错误: {}',
|
||||||
'connection_timeout': '连接超时',
|
'connection_timeout': '连接超时',
|
||||||
'data_transfer_timeout': '数据传输超时,正在重试...',
|
'data_transfer_timeout': '数据传输超时,正在重试...',
|
||||||
'connection_reset': '连接被重置',
|
'connection_reset': '连接被重置',
|
||||||
@ -49,7 +52,7 @@ MESSAGES = {
|
|||||||
'blog': '博客',
|
'blog': '博客',
|
||||||
'proxy_mode': '代理轮换模式',
|
'proxy_mode': '代理轮换模式',
|
||||||
'cycle': '循环',
|
'cycle': '循环',
|
||||||
'load_balance': '负载均衡',
|
'loadbalance': '负载均衡',
|
||||||
'single_round': '单轮',
|
'single_round': '单轮',
|
||||||
'proxy_interval': '代理更换时间',
|
'proxy_interval': '代理更换时间',
|
||||||
'default_auth': '默认账号密码',
|
'default_auth': '默认账号密码',
|
||||||
@ -105,7 +108,7 @@ MESSAGES = {
|
|||||||
'proxy_check_result': '代理检查完成,有效代理:{}个',
|
'proxy_check_result': '代理检查完成,有效代理:{}个',
|
||||||
'no_proxy': '无代理',
|
'no_proxy': '无代理',
|
||||||
'cycle_mode': '循环模式',
|
'cycle_mode': '循环模式',
|
||||||
'load_balance_mode': '负载均衡模式',
|
'loadbalance_mode': '负载均衡模式',
|
||||||
'proxy_check_start': '开始检查代理...',
|
'proxy_check_start': '开始检查代理...',
|
||||||
'proxy_check_complete': '代理检查完成',
|
'proxy_check_complete': '代理检查完成',
|
||||||
'proxy_save_success': '代理保存成功',
|
'proxy_save_success': '代理保存成功',
|
||||||
@ -181,6 +184,11 @@ MESSAGES = {
|
|||||||
'web_panel_url': '网页控制面板地址: {}',
|
'web_panel_url': '网页控制面板地址: {}',
|
||||||
'web_panel_notice': '请使用浏览器访问上述地址来管理代理服务器',
|
'web_panel_notice': '请使用浏览器访问上述地址来管理代理服务器',
|
||||||
'api_proxy_settings_title': 'API代理设置',
|
'api_proxy_settings_title': 'API代理设置',
|
||||||
|
'all_retries_failed': '所有重试均已失败,最后错误: {}',
|
||||||
|
'proxy_get_failed': '获取代理失败',
|
||||||
|
'proxy_get_error': '获取代理错误: {}',
|
||||||
|
'request_error': '请求错误: {}',
|
||||||
|
'proxy_switch_error': '代理切换错误: {}',
|
||||||
},
|
},
|
||||||
'en': {
|
'en': {
|
||||||
'getting_new_proxy': 'Getting new proxy IP',
|
'getting_new_proxy': 'Getting new proxy IP',
|
||||||
@ -193,6 +201,9 @@ MESSAGES = {
|
|||||||
'proxy_switch': 'Switch proxy: {} -> {}',
|
'proxy_switch': 'Switch proxy: {} -> {}',
|
||||||
'proxy_consecutive_fails': 'Proxy {} failed {} times consecutively, switching to new proxy',
|
'proxy_consecutive_fails': 'Proxy {} failed {} times consecutively, switching to new proxy',
|
||||||
'proxy_invalid': 'Proxy {} is invalid, switching proxy immediately',
|
'proxy_invalid': 'Proxy {} is invalid, switching proxy immediately',
|
||||||
|
'proxy_failure': 'Proxy check failed: {}',
|
||||||
|
'proxy_failure_threshold': 'Proxy invalid, switching now',
|
||||||
|
'proxy_check_error': 'Error occurred during proxy check: {}',
|
||||||
'connection_timeout': 'Connection timeout',
|
'connection_timeout': 'Connection timeout',
|
||||||
'data_transfer_timeout': 'Data transfer timeout, retrying...',
|
'data_transfer_timeout': 'Data transfer timeout, retrying...',
|
||||||
'connection_reset': 'Connection reset',
|
'connection_reset': 'Connection reset',
|
||||||
@ -214,7 +225,7 @@ MESSAGES = {
|
|||||||
'blog': 'Blog',
|
'blog': 'Blog',
|
||||||
'proxy_mode': 'Proxy Rotation Mode',
|
'proxy_mode': 'Proxy Rotation Mode',
|
||||||
'cycle': 'Cycle',
|
'cycle': 'Cycle',
|
||||||
'load_balance': 'Load Balance',
|
'loadbalance': 'Load Balance',
|
||||||
'single_round': 'Single Round',
|
'single_round': 'Single Round',
|
||||||
'proxy_interval': 'Proxy Change Interval',
|
'proxy_interval': 'Proxy Change Interval',
|
||||||
'default_auth': 'Default Username and Password',
|
'default_auth': 'Default Username and Password',
|
||||||
@ -248,37 +259,40 @@ MESSAGES = {
|
|||||||
'invalid_proxy': 'Current proxy is invalid: {}',
|
'invalid_proxy': 'Current proxy is invalid: {}',
|
||||||
'whitelist_error': 'Failed to add whitelist: {}',
|
'whitelist_error': 'Failed to add whitelist: {}',
|
||||||
'api_mode_notice': 'Currently in API mode, proxy address will be automatically obtained upon request',
|
'api_mode_notice': 'Currently in API mode, proxy address will be automatically obtained upon request',
|
||||||
|
'all_retries_failed': 'All retries failed, last error: {}',
|
||||||
|
'proxy_get_failed': 'Failed to get proxy',
|
||||||
|
'proxy_get_error': 'Error getting proxy: {}',
|
||||||
|
'proxy_switch_error': 'Error switching proxy: {}',
|
||||||
'server_running': 'Proxy server running at {}:{}',
|
'server_running': 'Proxy server running at {}:{}',
|
||||||
'server_start_error': 'Server startup error: {}',
|
'server_start_error': 'Server startup error: {}',
|
||||||
'server_shutting_down': 'Shutting down server...',
|
'server_shutting_down': 'Server shutting down...',
|
||||||
'client_process_error': 'Error processing client request: {}',
|
'client_process_error': 'Client processing error: {}',
|
||||||
'request_handling_error': 'Request handling error: {}',
|
'request_handling_error': 'Request handling error: {}',
|
||||||
'proxy_forward_error': 'Proxy forwarding error: {}',
|
'proxy_forward_error': 'Proxy forwarding error: {}',
|
||||||
'data_transfer_timeout': '{}data transfer timeout',
|
'data_transfer_timeout': '{}data transfer timeout',
|
||||||
'data_transfer_error': '{} data transfer error: {}',
|
|
||||||
'status_update_error': 'Status update error',
|
'status_update_error': 'Status update error',
|
||||||
'display_level_notice': 'Current display level: {}',
|
'display_level_notice': 'Current display level: {}',
|
||||||
'display_level_desc': '''Display level description:
|
'display_level_desc': '''Display level description:
|
||||||
0: Show only proxy switches and errors
|
0: Only show proxy switch and error messages
|
||||||
1: Show proxy switches, countdown and errors
|
1: Show proxy switch, countdown and error messages
|
||||||
2: Show all detailed information''',
|
2: Show all detailed information''',
|
||||||
'new_client_connect': 'New client connection - IP: {}, User: {}',
|
'new_client_connect': 'New client connection - IP: {}, User: {}',
|
||||||
'no_auth': 'No authentication',
|
'no_auth': 'No authentication',
|
||||||
'connection_error': 'Connection handling error: {}',
|
'connection_error': 'Connection handling error: {}',
|
||||||
'cleanup_error': 'IP cleanup error: {}',
|
'cleanup_error': 'Cleanup IP error: {}',
|
||||||
'port_changed': 'Port changed: {} -> {}, server restart required',
|
'port_changed': 'Port changed: {} -> {}, server restart required to take effect',
|
||||||
'config_updated': 'Server configuration updated',
|
'config_updated': 'Server configuration updated',
|
||||||
'load_proxy_file_error': 'Failed to load proxy file: {}',
|
'load_proxy_file_error': 'Failed to load proxy file: {}',
|
||||||
'proxy_check_result': 'Proxy check completed, valid proxies: {}',
|
'proxy_check_result': 'Proxy check completed, valid proxies: {}',
|
||||||
'no_proxy': 'No proxy',
|
'no_proxy': 'No proxy',
|
||||||
'cycle_mode': 'Cycle Mode',
|
'cycle_mode': 'Cycle mode',
|
||||||
'load_balance_mode': 'Load Balance Mode',
|
'loadbalance_mode': 'Load balance mode',
|
||||||
'proxy_check_start': 'Starting proxy check...',
|
'proxy_check_start': 'Starting proxy check...',
|
||||||
'proxy_check_complete': 'Proxy check completed',
|
'proxy_check_complete': 'Proxy check completed',
|
||||||
'proxy_save_success': 'Proxies saved successfully',
|
'proxy_save_success': 'Proxy saved successfully',
|
||||||
'proxy_save_failed': 'Failed to save proxies: {}',
|
'proxy_save_failed': 'Failed to save proxy: {}',
|
||||||
'ip_list_save_success': 'IP lists saved successfully',
|
'ip_list_save_success': 'IP list saved successfully',
|
||||||
'ip_list_save_failed': 'Failed to save IP lists: {}',
|
'ip_list_save_failed': 'Failed to save IP list: {}',
|
||||||
'switch_success': 'Proxy switched successfully',
|
'switch_success': 'Proxy switched successfully',
|
||||||
'switch_failed': 'Failed to switch proxy: {}',
|
'switch_failed': 'Failed to switch proxy: {}',
|
||||||
'service_start_success': 'Service started successfully',
|
'service_start_success': 'Service started successfully',
|
||||||
@ -305,20 +319,20 @@ MESSAGES = {
|
|||||||
'language_label': 'Language',
|
'language_label': 'Language',
|
||||||
'chinese': 'Chinese',
|
'chinese': 'Chinese',
|
||||||
'english': 'English',
|
'english': 'English',
|
||||||
'manual_switch_btn': 'Manual Switch',
|
'manual_switch_btn': 'Switch Manually',
|
||||||
'service_control_title': 'Service Control',
|
'service_control_title': 'Service Control',
|
||||||
'language_switch_success': '',
|
'language_switch_success': 'Language switched successfully',
|
||||||
'language_switch_failed': '',
|
'language_switch_failed': 'Failed to switch language',
|
||||||
'refresh_failed': 'Failed to refresh data: {}',
|
'refresh_failed': 'Failed to refresh data: {}',
|
||||||
'auth_username_label': 'Auth Username',
|
'auth_username_label': 'Authentication Username',
|
||||||
'auth_password_label': 'Auth Password',
|
'auth_password_label': 'Authentication Password',
|
||||||
'proxy_auth_username_label': 'Proxy Auth Username',
|
'proxy_auth_username_label': 'Proxy Authentication Username',
|
||||||
'proxy_auth_password_label': 'Proxy Auth Password',
|
'proxy_auth_password_label': 'Proxy Authentication Password',
|
||||||
'progress_bar_label': 'Switch Progress',
|
'progress_bar_label': 'Switch Progress',
|
||||||
'proxy_settings_title': 'Proxy Settings',
|
'proxy_settings_title': 'Proxy Settings',
|
||||||
'config_save_success': 'Configuration saved successfully',
|
'config_save_success': 'Configuration saved successfully',
|
||||||
'config_save_failed': 'Failed to save configuration: {}',
|
'config_save_failed': 'Failed to save configuration: {}',
|
||||||
'config_restart_required': 'Configuration changed, server restart required',
|
'config_restart_required': 'Configuration changed, server restart required to take effect',
|
||||||
'confirm_restart_service': 'Restart server now?',
|
'confirm_restart_service': 'Restart server now?',
|
||||||
'service_status': 'Service Status',
|
'service_status': 'Service Status',
|
||||||
'running': 'Running',
|
'running': 'Running',
|
||||||
@ -329,10 +343,10 @@ MESSAGES = {
|
|||||||
'service_stop_failed': 'Failed to stop service: {}',
|
'service_stop_failed': 'Failed to stop service: {}',
|
||||||
'service_restart_failed': 'Failed to restart service: {}',
|
'service_restart_failed': 'Failed to restart service: {}',
|
||||||
'invalid_token': 'Invalid access token',
|
'invalid_token': 'Invalid access token',
|
||||||
'config_file_changed': 'Configuration file change detected, reloading...',
|
'config_file_changed': 'Config file change detected, reloading...',
|
||||||
'proxy_file_changed': 'Proxy file changed, reloading...',
|
'proxy_file_changed': 'Proxy file changed, reloading...',
|
||||||
'test_target_label': 'Test Target URL',
|
'test_target_label': 'Test Target Address',
|
||||||
'invalid_test_target': 'Invalid test target URL',
|
'invalid_test_target': 'Invalid test target address',
|
||||||
'users_save_success': 'Users saved successfully',
|
'users_save_success': 'Users saved successfully',
|
||||||
'users_save_failed': 'Failed to save users: {}',
|
'users_save_failed': 'Failed to save users: {}',
|
||||||
'user_management_title': 'User Management',
|
'user_management_title': 'User Management',
|
||||||
@ -340,13 +354,13 @@ MESSAGES = {
|
|||||||
'password_column': 'Password',
|
'password_column': 'Password',
|
||||||
'actions_column': 'Actions',
|
'actions_column': 'Actions',
|
||||||
'add_user_btn': 'Add User',
|
'add_user_btn': 'Add User',
|
||||||
'enter_username': 'Enter username',
|
'enter_username': 'Please enter username',
|
||||||
'enter_password': 'Enter password',
|
'enter_password': 'Please enter password',
|
||||||
'confirm_delete_user': 'Are you sure you want to delete this user?',
|
'confirm_delete_user': 'Are you sure you want to delete this user?',
|
||||||
'no_logs_found': 'No matching logs found',
|
'no_logs_found': 'No matching logs found',
|
||||||
'clear_search': 'Clear Search',
|
'clear_search': 'Clear Search',
|
||||||
'web_panel_url': 'Web control panel URL: {}',
|
'web_panel_url': 'Web panel URL: {}',
|
||||||
'web_panel_notice': 'Please use a browser to visit the above URL to manage the proxy server',
|
'web_panel_notice': 'Please use a browser to access the above URL to manage the proxy server',
|
||||||
'api_proxy_settings_title': 'API Proxy Settings',
|
'api_proxy_settings_title': 'API Proxy Settings',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -376,7 +390,7 @@ def print_banner(config):
|
|||||||
banner_info = [
|
banner_info = [
|
||||||
(get_message('public_account', language), '樱花庄的本间白猫'),
|
(get_message('public_account', language), '樱花庄的本间白猫'),
|
||||||
(get_message('blog', language), 'https://y.shironekosan.cn'),
|
(get_message('blog', language), 'https://y.shironekosan.cn'),
|
||||||
(get_message('proxy_mode', language), get_message('cycle', language) if config.get('mode') == 'cycle' else get_message('load_balance', language) if config.get('mode') == 'load_balance' else get_message('single_round', language)),
|
(get_message('proxy_mode', language), get_message('cycle', language) if config.get('mode') == 'cycle' else get_message('loadbalance', language) if config.get('mode') == 'loadbalance' else get_message('single_round', language)),
|
||||||
(get_message('proxy_interval', language), f"{config.get('interval')}{get_message('seconds', language)}"),
|
(get_message('proxy_interval', language), f"{config.get('interval')}{get_message('seconds', language)}"),
|
||||||
(get_message('default_auth', language), auth_info),
|
(get_message('default_auth', language), auth_info),
|
||||||
(get_message('local_http', language), http_addr),
|
(get_message('local_http', language), http_addr),
|
||||||
@ -542,10 +556,9 @@ async def check_http_proxy(proxy, test_url=None):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def check_https_proxy(proxy, test_url=None):
|
|
||||||
return await check_http_proxy(proxy, test_url)
|
|
||||||
|
|
||||||
async def check_socks_proxy(proxy, test_url=None):
|
async def check_socks_proxy(proxy, test_url=None):
|
||||||
|
if test_url is None:
|
||||||
|
test_url = 'https://www.baidu.com'
|
||||||
protocol, auth, host, port = parse_proxy(proxy)
|
protocol, auth, host, port = parse_proxy(proxy)
|
||||||
if not all([host, port]):
|
if not all([host, port]):
|
||||||
return False
|
return False
|
||||||
@ -605,7 +618,7 @@ async def check_proxy(proxy, test_url=None):
|
|||||||
proxy_type = proxy.split('://')[0]
|
proxy_type = proxy.split('://')[0]
|
||||||
check_funcs = {
|
check_funcs = {
|
||||||
'http': check_http_proxy,
|
'http': check_http_proxy,
|
||||||
'https': check_https_proxy,
|
'https': check_http_proxy,
|
||||||
'socks5': check_socks_proxy
|
'socks5': check_socks_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,7 +650,7 @@ async def check_for_updates(language='cn'):
|
|||||||
match = re.search(r'<p>(ProxyCat-V\d+\.\d+\.\d+)</p>', content)
|
match = re.search(r'<p>(ProxyCat-V\d+\.\d+\.\d+)</p>', content)
|
||||||
if match:
|
if match:
|
||||||
latest_version = match.group(1)
|
latest_version = match.group(1)
|
||||||
CURRENT_VERSION = "ProxyCat-V2.0.2"
|
CURRENT_VERSION = "ProxyCat-V2.0.4"
|
||||||
if version.parse(latest_version.split('-V')[1]) > version.parse(CURRENT_VERSION.split('-V')[1]):
|
if version.parse(latest_version.split('-V')[1]) > version.parse(CURRENT_VERSION.split('-V')[1]):
|
||||||
print(f"{Fore.YELLOW}{get_message('new_version_found', language)} 当前版本: {CURRENT_VERSION}, 最新版本: {latest_version}{Style.RESET_ALL}")
|
print(f"{Fore.YELLOW}{get_message('new_version_found', language)} 当前版本: {CURRENT_VERSION}, 最新版本: {latest_version}{Style.RESET_ALL}")
|
||||||
print(f"{Fore.YELLOW}{get_message('visit_quark', language)}{Style.RESET_ALL}")
|
print(f"{Fore.YELLOW}{get_message('visit_quark', language)}{Style.RESET_ALL}")
|
||||||
|
@ -25,6 +25,7 @@ class AsyncProxyServer:
|
|||||||
self._init_config_values(config)
|
self._init_config_values(config)
|
||||||
self._init_server_state()
|
self._init_server_state()
|
||||||
self._init_connection_settings()
|
self._init_connection_settings()
|
||||||
|
self.proxy_failure_lock = asyncio.Lock()
|
||||||
|
|
||||||
def _init_config_values(self, config):
|
def _init_config_values(self, config):
|
||||||
self.port = int(config.get('port', '1080'))
|
self.port = int(config.get('port', '1080'))
|
||||||
@ -61,11 +62,11 @@ class AsyncProxyServer:
|
|||||||
self.switch_cooldown = 5
|
self.switch_cooldown = 5
|
||||||
self.proxy_check_cache = {}
|
self.proxy_check_cache = {}
|
||||||
self.last_check_time = {}
|
self.last_check_time = {}
|
||||||
self.proxy_check_ttl = 300
|
self.proxy_check_ttl = 60
|
||||||
self.check_cooldown = 10
|
self.check_cooldown = 10
|
||||||
self.max_fail_count = 3
|
|
||||||
self.proxy_fail_count = 0
|
|
||||||
self.connected_clients = set()
|
self.connected_clients = set()
|
||||||
|
self.last_proxy_failure_time = 0
|
||||||
|
self.proxy_failure_cooldown = 3
|
||||||
|
|
||||||
def _init_server_state(self):
|
def _init_server_state(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
@ -73,7 +74,6 @@ class AsyncProxyServer:
|
|||||||
self.server_instance = None
|
self.server_instance = None
|
||||||
self.tasks = set()
|
self.tasks = set()
|
||||||
self.last_switch_time = time.time()
|
self.last_switch_time = time.time()
|
||||||
self.proxy_failed = False
|
|
||||||
self.proxy_cycle = None
|
self.proxy_cycle = None
|
||||||
self.current_proxy = None
|
self.current_proxy = None
|
||||||
self.proxies = []
|
self.proxies = []
|
||||||
@ -89,36 +89,82 @@ class AsyncProxyServer:
|
|||||||
self.buffer_size = 8192
|
self.buffer_size = 8192
|
||||||
self.connection_timeout = 30
|
self.connection_timeout = 30
|
||||||
self.read_timeout = 60
|
self.read_timeout = 60
|
||||||
self.max_concurrent_requests = 50
|
self.max_concurrent_requests = 1000
|
||||||
self.request_semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
self.request_semaphore = asyncio.Semaphore(self.max_concurrent_requests)
|
||||||
self.connection_pool = {}
|
self.connection_pool = {}
|
||||||
self.max_pool_size = 100
|
self.max_pool_size = 500
|
||||||
|
self.client_pool = {}
|
||||||
|
self.client_pool_lock = asyncio.Lock()
|
||||||
|
self.proxy_pool = {}
|
||||||
|
self.active_connections = set()
|
||||||
|
|
||||||
def _update_config_values(self, new_config):
|
def _update_config_values(self, new_config):
|
||||||
self._init_config_values(new_config)
|
self._init_config_values(new_config)
|
||||||
self.last_switch_time = time.time()
|
self.last_switch_time = time.time()
|
||||||
|
|
||||||
|
self.last_switch_attempt = 0
|
||||||
|
|
||||||
def _handle_mode_change(self):
|
def _handle_mode_change(self):
|
||||||
|
|
||||||
|
self.last_switch_attempt = 0
|
||||||
|
|
||||||
if self.use_getip:
|
if self.use_getip:
|
||||||
self.proxies = []
|
self.proxies = []
|
||||||
self.proxy_cycle = None
|
self.proxy_cycle = None
|
||||||
self.current_proxy = None
|
self.current_proxy = None
|
||||||
logging.info(get_message('api_mode_notice', self.language))
|
logging.info(get_message('api_mode_notice', self.language))
|
||||||
else:
|
else:
|
||||||
|
logging.info(f"切换到{'负载均衡' if self.mode == 'loadbalance' else '循环模式'}模式,从 {self.proxy_file} 加载代理列表")
|
||||||
self.proxies = self._load_file_proxies()
|
self.proxies = self._load_file_proxies()
|
||||||
|
logging.info(f"加载到 {len(self.proxies)} 个代理")
|
||||||
|
|
||||||
if self.proxies:
|
if self.proxies:
|
||||||
self.proxy_cycle = cycle(self.proxies)
|
self.proxy_cycle = cycle(self.proxies)
|
||||||
self.current_proxy = next(self.proxy_cycle)
|
self.current_proxy = next(self.proxy_cycle)
|
||||||
if self.check_proxies:
|
logging.info(f"当前使用代理: {self.current_proxy}")
|
||||||
asyncio.run(self._check_proxies())
|
|
||||||
|
if self.check_proxies and self.mode != 'loadbalance':
|
||||||
|
try:
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
asyncio.create_task(self._check_proxies_wrapper())
|
||||||
|
else:
|
||||||
|
loop.run_until_complete(self._check_proxies())
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"检查代理时出错: {str(e)}")
|
||||||
|
else:
|
||||||
|
logging.error(f"从文件 {self.proxy_file} 加载代理失败,请检查文件是否存在且包含有效代理")
|
||||||
|
|
||||||
|
async def _check_proxies_wrapper(self):
|
||||||
|
"""包装 _check_proxies 方法,用于在已运行的事件循环中调用"""
|
||||||
|
await self._check_proxies()
|
||||||
|
|
||||||
def _reload_proxies(self):
|
def _reload_proxies(self):
|
||||||
|
|
||||||
|
self.last_switch_attempt = 0
|
||||||
|
|
||||||
|
logging.info(f"重新加载代理列表文件 {self.proxy_file}")
|
||||||
self.proxies = self._load_file_proxies()
|
self.proxies = self._load_file_proxies()
|
||||||
|
logging.info(f"加载到 {len(self.proxies)} 个代理")
|
||||||
|
|
||||||
if self.proxies:
|
if self.proxies:
|
||||||
self.proxy_cycle = cycle(self.proxies)
|
self.proxy_cycle = cycle(self.proxies)
|
||||||
self.current_proxy = next(self.proxy_cycle)
|
self.current_proxy = next(self.proxy_cycle)
|
||||||
|
logging.info(f"当前使用代理: {self.current_proxy}")
|
||||||
|
|
||||||
if self.check_proxies:
|
if self.check_proxies:
|
||||||
asyncio.run(self._check_proxies())
|
try:
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if loop.is_running():
|
||||||
|
asyncio.create_task(self._check_proxies_wrapper())
|
||||||
|
else:
|
||||||
|
loop.run_until_complete(self._check_proxies())
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"检查代理时出错: {str(e)}")
|
||||||
|
else:
|
||||||
|
logging.error(f"从文件 {self.proxy_file} 加载代理失败,请检查文件是否存在且包含有效代理")
|
||||||
|
|
||||||
async def _check_proxies(self):
|
async def _check_proxies(self):
|
||||||
from modules.modules import check_proxies
|
from modules.modules import check_proxies
|
||||||
@ -148,15 +194,44 @@ class AsyncProxyServer:
|
|||||||
self.running = True
|
self.running = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.server_instance = await asyncio.start_server(
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024 * 1024)
|
||||||
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||||
|
|
||||||
|
sock.bind(('0.0.0.0', self.port))
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
if hasattr(loop, 'set_default_executor'):
|
||||||
|
import concurrent.futures
|
||||||
|
executor = concurrent.futures.ThreadPoolExecutor(max_workers=max(32, os.cpu_count() * 4))
|
||||||
|
loop.set_default_executor(executor)
|
||||||
|
|
||||||
|
server = await asyncio.start_server(
|
||||||
self.handle_client,
|
self.handle_client,
|
||||||
'0.0.0.0',
|
sock=sock,
|
||||||
self.port
|
backlog=2048,
|
||||||
|
limit=32768,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.server_instance = server
|
||||||
logging.info(get_message('server_running', self.language, '0.0.0.0', self.port))
|
logging.info(get_message('server_running', self.language, '0.0.0.0', self.port))
|
||||||
|
|
||||||
async with self.server_instance:
|
self.tasks.add(asyncio.create_task(self.cleanup_clients()))
|
||||||
await self.server_instance.serve_forever()
|
self.tasks.add(asyncio.create_task(self._cleanup_pool()))
|
||||||
|
self.tasks.add(asyncio.create_task(self.cleanup_disconnected_ips()))
|
||||||
|
|
||||||
|
if hasattr(os, 'sched_setaffinity'):
|
||||||
|
try:
|
||||||
|
os.sched_setaffinity(0, range(os.cpu_count()))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async with server:
|
||||||
|
await server.serve_forever()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not self.stop_server:
|
if not self.stop_server:
|
||||||
logging.error(get_message('server_start_error', self.language, str(e)))
|
logging.error(get_message('server_start_error', self.language, str(e)))
|
||||||
@ -185,12 +260,32 @@ class AsyncProxyServer:
|
|||||||
try:
|
try:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
if self.interval != 0 and (self.switching_proxy or (current_time - self.last_switch_attempt < self.switch_cooldown)):
|
|
||||||
|
if self.mode == 'loadbalance' and self.proxies:
|
||||||
|
if not self.switching_proxy:
|
||||||
|
try:
|
||||||
|
self.switching_proxy = True
|
||||||
|
self.last_switch_attempt = current_time
|
||||||
|
|
||||||
|
if not self.use_getip:
|
||||||
|
|
||||||
|
if not self.proxy_cycle:
|
||||||
|
self.proxy_cycle = cycle(self.proxies)
|
||||||
|
self.current_proxy = next(self.proxy_cycle)
|
||||||
|
logging.info(f"负载均衡模式选择代理: {self.current_proxy}")
|
||||||
|
else:
|
||||||
|
|
||||||
|
await self.get_proxy()
|
||||||
|
finally:
|
||||||
|
self.switching_proxy = False
|
||||||
return self.current_proxy
|
return self.current_proxy
|
||||||
|
|
||||||
if (self.use_getip and (not self.current_proxy or
|
|
||||||
current_time - self.last_switch_time >= self.interval)) or \
|
if self.switching_proxy or (current_time - self.last_switch_attempt < self.switch_cooldown):
|
||||||
(not self.use_getip and self.interval == 0):
|
return self.current_proxy
|
||||||
|
|
||||||
|
if self.interval > 0 and current_time - self.last_switch_time >= self.interval or \
|
||||||
|
(self.use_getip and not self.current_proxy):
|
||||||
try:
|
try:
|
||||||
self.switching_proxy = True
|
self.switching_proxy = True
|
||||||
self.last_switch_attempt = current_time
|
self.last_switch_attempt = current_time
|
||||||
@ -221,7 +316,7 @@ class AsyncProxyServer:
|
|||||||
return valid_proxies[0]
|
return valid_proxies[0]
|
||||||
|
|
||||||
def time_until_next_switch(self):
|
def time_until_next_switch(self):
|
||||||
return float('inf') if self.mode == 'load_balance' else max(0, self.interval - (time.time() - self.last_switch_time))
|
return float('inf') if self.mode == 'loadbalance' else max(0, self.interval - (time.time() - self.last_switch_time))
|
||||||
|
|
||||||
def check_ip_auth(self, ip):
|
def check_ip_auth(self, ip):
|
||||||
try:
|
try:
|
||||||
@ -270,9 +365,25 @@ class AsyncProxyServer:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def _close_connection(self, writer):
|
||||||
|
try:
|
||||||
|
if writer and not writer.is_closing():
|
||||||
|
writer.write_eof()
|
||||||
|
await writer.drain()
|
||||||
|
writer.close()
|
||||||
|
try:
|
||||||
|
await writer.wait_closed()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def handle_client(self, reader, writer):
|
async def handle_client(self, reader, writer):
|
||||||
task = asyncio.current_task()
|
task = asyncio.current_task()
|
||||||
self.tasks.add(task)
|
self.tasks.add(task)
|
||||||
|
peername = writer.get_extra_info('peername')
|
||||||
|
if peername:
|
||||||
|
self.active_connections.add(peername)
|
||||||
try:
|
try:
|
||||||
peername = writer.get_extra_info('peername')
|
peername = writer.get_extra_info('peername')
|
||||||
if peername:
|
if peername:
|
||||||
@ -295,25 +406,37 @@ class AsyncProxyServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('client_handle_error', self.language, e))
|
logging.error(get_message('client_handle_error', self.language, e))
|
||||||
finally:
|
finally:
|
||||||
try:
|
if peername:
|
||||||
writer.close()
|
self.active_connections.discard(peername)
|
||||||
await writer.wait_closed()
|
await self._close_connection(writer)
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.tasks.remove(task)
|
self.tasks.remove(task)
|
||||||
|
|
||||||
async def _pipe(self, reader, writer):
|
async def _pipe(self, reader, writer):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
data = await reader.read(self.buffer_size)
|
data = await reader.read(self.buffer_size)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
|
try:
|
||||||
writer.write(data)
|
writer.write(data)
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
except (ConnectionError, ConnectionResetError):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
break
|
||||||
|
except (ConnectionError, ConnectionResetError):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
break
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('data_transfer_error', self.language, e))
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
await self._close_connection(writer)
|
||||||
|
|
||||||
def _split_proxy_auth(self, proxy_addr):
|
def _split_proxy_auth(self, proxy_addr):
|
||||||
match = re.match(r'((?P<username>.+?):(?P<password>.+?)@)?(?P<host>.+)', proxy_addr)
|
match = re.match(r'((?P<username>.+?):(?P<password>.+?)@)?(?P<host>.+)', proxy_addr)
|
||||||
@ -338,7 +461,12 @@ class AsyncProxyServer:
|
|||||||
else:
|
else:
|
||||||
proxy_url = f"{proxy_type}://{proxy_addr}"
|
proxy_url = f"{proxy_type}://{proxy_addr}"
|
||||||
|
|
||||||
client = httpx.AsyncClient(
|
import logging as httpx_logging
|
||||||
|
httpx_logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||||
|
httpx_logging.getLogger("hpack").setLevel(logging.WARNING)
|
||||||
|
httpx_logging.getLogger("h2").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
return httpx.AsyncClient(
|
||||||
proxies={"all://": proxy_url},
|
proxies={"all://": proxy_url},
|
||||||
limits=httpx.Limits(
|
limits=httpx.Limits(
|
||||||
max_keepalive_connections=100,
|
max_keepalive_connections=100,
|
||||||
@ -347,10 +475,9 @@ class AsyncProxyServer:
|
|||||||
),
|
),
|
||||||
timeout=30.0,
|
timeout=30.0,
|
||||||
http2=True,
|
http2=True,
|
||||||
verify=False
|
verify=False,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
client._last_used = time.time()
|
|
||||||
return client
|
|
||||||
|
|
||||||
async def _cleanup_connections(self):
|
async def _cleanup_connections(self):
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
@ -423,7 +550,7 @@ class AsyncProxyServer:
|
|||||||
|
|
||||||
dst_port = struct.unpack('!H', await reader.readexactly(2))[0]
|
dst_port = struct.unpack('!H', await reader.readexactly(2))[0]
|
||||||
|
|
||||||
max_retries = 3
|
max_retries = 1
|
||||||
retry_count = 0
|
retry_count = 0
|
||||||
last_error = None
|
last_error = None
|
||||||
|
|
||||||
@ -456,7 +583,6 @@ class AsyncProxyServer:
|
|||||||
self._pipe(remote_reader, writer)
|
self._pipe(remote_reader, writer)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.proxy_failed = False
|
|
||||||
return
|
return
|
||||||
|
|
||||||
except (asyncio.TimeoutError, ConnectionRefusedError, ConnectionResetError) as e:
|
except (asyncio.TimeoutError, ConnectionRefusedError, ConnectionResetError) as e:
|
||||||
@ -477,8 +603,8 @@ class AsyncProxyServer:
|
|||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if last_error:
|
#if last_error:
|
||||||
logging.error(get_message('all_retries_failed', self.language, str(last_error)))
|
#logging.error(get_message('all_retries_failed', self.language, str(last_error)))
|
||||||
writer.write(b'\x05\x01\x00\x01\x00\x00\x00\x00\x00\x00')
|
writer.write(b'\x05\x01\x00\x01\x00\x00\x00\x00\x00\x00')
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
@ -600,6 +726,8 @@ class AsyncProxyServer:
|
|||||||
async def _handle_client_impl(self, reader, writer, first_byte):
|
async def _handle_client_impl(self, reader, writer, first_byte):
|
||||||
try:
|
try:
|
||||||
peername = writer.get_extra_info('peername')
|
peername = writer.get_extra_info('peername')
|
||||||
|
client_info = f"{peername[0]}:{peername[1]}" if peername else "未知客户端"
|
||||||
|
|
||||||
if peername:
|
if peername:
|
||||||
client_ip = peername[0]
|
client_ip = peername[0]
|
||||||
if not self.check_ip_auth(client_ip):
|
if not self.check_ip_auth(client_ip):
|
||||||
@ -614,7 +742,7 @@ class AsyncProxyServer:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
method, path, _ = request_line.decode('utf-8', errors='ignore').split()
|
method, path, _ = request_line.decode('utf-8', errors='ignore').split()
|
||||||
except ValueError:
|
except (ValueError, UnicodeDecodeError) as e:
|
||||||
return
|
return
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
@ -651,10 +779,14 @@ class AsyncProxyServer:
|
|||||||
else:
|
else:
|
||||||
await self._handle_request(method, path, headers, reader, writer)
|
await self._handle_request(method, path, headers, reader, writer)
|
||||||
|
|
||||||
|
except (ConnectionError, ConnectionResetError, ConnectionAbortedError):
|
||||||
|
return
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('client_request_error', self.language, e))
|
if not isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError,
|
||||||
|
asyncio.CancelledError, asyncio.TimeoutError)):
|
||||||
|
logging.error(get_message('client_request_error', self.language, str(e)))
|
||||||
|
|
||||||
async def _handle_connect(self, path, reader, writer):
|
async def _handle_connect(self, path, reader, writer):
|
||||||
try:
|
try:
|
||||||
@ -665,6 +797,12 @@ class AsyncProxyServer:
|
|||||||
await writer.drain()
|
await writer.drain()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
max_retries = 1
|
||||||
|
retry_count = 0
|
||||||
|
last_error = None
|
||||||
|
|
||||||
|
while retry_count < max_retries:
|
||||||
|
try:
|
||||||
proxy = await self.get_next_proxy()
|
proxy = await self.get_next_proxy()
|
||||||
if not proxy:
|
if not proxy:
|
||||||
writer.write(b'HTTP/1.1 503 Service Unavailable\r\n\r\n')
|
writer.write(b'HTTP/1.1 503 Service Unavailable\r\n\r\n')
|
||||||
@ -692,6 +830,14 @@ class AsyncProxyServer:
|
|||||||
await remote_writer.drain()
|
await remote_writer.drain()
|
||||||
response = await remote_reader.readline()
|
response = await remote_reader.readline()
|
||||||
if not response.startswith(b'HTTP/1.1 200'):
|
if not response.startswith(b'HTTP/1.1 200'):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = f"Bad Gateway: {response.decode('utf-8', errors='ignore')}"
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
raise Exception("Bad Gateway")
|
raise Exception("Bad Gateway")
|
||||||
while (await remote_reader.readline()) != b'\r\n':
|
while (await remote_reader.readline()) != b'\r\n':
|
||||||
pass
|
pass
|
||||||
@ -702,6 +848,14 @@ class AsyncProxyServer:
|
|||||||
remote_writer.write(b'\x05\x01\x00\x03' + len(host).to_bytes(1, 'big') + host.encode() + port.to_bytes(2, 'big'))
|
remote_writer.write(b'\x05\x01\x00\x03' + len(host).to_bytes(1, 'big') + host.encode() + port.to_bytes(2, 'big'))
|
||||||
await remote_writer.drain()
|
await remote_writer.drain()
|
||||||
if (await remote_reader.read(10))[1] != 0:
|
if (await remote_reader.read(10))[1] != 0:
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = "SOCKS5 connection failed"
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
raise Exception("Bad Gateway")
|
raise Exception("Bad Gateway")
|
||||||
else:
|
else:
|
||||||
raise Exception("Unsupported proxy type")
|
raise Exception("Unsupported proxy type")
|
||||||
@ -713,22 +867,58 @@ class AsyncProxyServer:
|
|||||||
self._pipe(reader, remote_writer),
|
self._pipe(reader, remote_writer),
|
||||||
self._pipe(remote_reader, writer)
|
self._pipe(remote_reader, writer)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = "Connection Timeout"
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
logging.error(get_message('connect_timeout', self.language))
|
logging.error(get_message('connect_timeout', self.language))
|
||||||
writer.write(b'HTTP/1.1 504 Gateway Timeout\r\n\r\n')
|
writer.write(b'HTTP/1.1 504 Gateway Timeout\r\n\r\n')
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('proxy_invalid_switch', self.language))
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = str(e)
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
if not self.proxy_failed:
|
return
|
||||||
self.proxy_failed = True
|
|
||||||
await self.get_proxy()
|
except Exception as e:
|
||||||
else:
|
last_error = str(e)
|
||||||
self.proxy_failed = False
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||||
|
await writer.drain()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
#if last_error:
|
||||||
|
#logging.error(get_message('all_retries_failed', self.language, last_error))
|
||||||
|
|
||||||
async def _handle_request(self, method, path, headers, reader, writer):
|
async def _handle_request(self, method, path, headers, reader, writer):
|
||||||
async with self.request_semaphore:
|
async with self.request_semaphore:
|
||||||
|
max_retries = 1
|
||||||
|
retry_count = 0
|
||||||
|
last_error = None
|
||||||
|
|
||||||
|
while retry_count < max_retries:
|
||||||
try:
|
try:
|
||||||
proxy = await self.get_next_proxy()
|
proxy = await self.get_next_proxy()
|
||||||
if not proxy:
|
if not proxy:
|
||||||
@ -736,7 +926,8 @@ class AsyncProxyServer:
|
|||||||
await writer.drain()
|
await writer.drain()
|
||||||
return
|
return
|
||||||
|
|
||||||
key = f"{proxy}:{path}"
|
try:
|
||||||
|
client = await self._get_client(proxy)
|
||||||
|
|
||||||
proxy_headers = headers.copy()
|
proxy_headers = headers.copy()
|
||||||
proxy_type, proxy_addr = proxy.split('://')
|
proxy_type, proxy_addr = proxy.split('://')
|
||||||
@ -745,79 +936,228 @@ class AsyncProxyServer:
|
|||||||
auth_header = f'Basic {base64.b64encode(auth.encode()).decode()}'
|
auth_header = f'Basic {base64.b64encode(auth.encode()).decode()}'
|
||||||
proxy_headers['Proxy-Authorization'] = auth_header
|
proxy_headers['Proxy-Authorization'] = auth_header
|
||||||
|
|
||||||
if key in self.connection_pool:
|
try:
|
||||||
client = self.connection_pool[key]
|
|
||||||
else:
|
|
||||||
client = await self._create_client(proxy)
|
|
||||||
self.connection_pool[key] = client
|
|
||||||
|
|
||||||
async with client.stream(
|
async with client.stream(
|
||||||
method,
|
method,
|
||||||
path,
|
path,
|
||||||
headers=proxy_headers,
|
headers=proxy_headers,
|
||||||
content=reader,
|
content=reader,
|
||||||
|
timeout=30.0
|
||||||
) as response:
|
) as response:
|
||||||
writer.write(f'HTTP/1.1 {response.status_code} {response.reason_phrase}\r\n'.encode())
|
writer.write(f'HTTP/1.1 {response.status_code} {response.reason_phrase}\r\n'.encode())
|
||||||
|
|
||||||
for header_name, header_value in response.headers.items():
|
for header_name, header_value in response.headers.items():
|
||||||
if header_name.lower() != 'transfer-encoding':
|
if header_name.lower() not in ('transfer-encoding', 'connection'):
|
||||||
writer.write(f'{header_name}: {header_value}\r\n'.encode())
|
writer.write(f'{header_name}: {header_value}\r\n'.encode())
|
||||||
writer.write(b'\r\n')
|
writer.write(b'\r\n')
|
||||||
|
|
||||||
|
try:
|
||||||
async for chunk in response.aiter_bytes(chunk_size=self.buffer_size):
|
async for chunk in response.aiter_bytes(chunk_size=self.buffer_size):
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
try:
|
||||||
writer.write(chunk)
|
writer.write(chunk)
|
||||||
if len(chunk) >= self.buffer_size:
|
if len(chunk) >= self.buffer_size:
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
except (ConnectionError, ConnectionResetError, ConnectionAbortedError):
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
except (ConnectionError, ConnectionResetError, ConnectionAbortedError):
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
except httpx.RequestError:
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = "Request Error"
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('request_handling_error', self.language, str(e)))
|
if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = str(e)
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
return
|
||||||
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
finally:
|
return
|
||||||
await self._cleanup_connections()
|
|
||||||
|
|
||||||
|
except httpx.HTTPError:
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = "HTTP Error"
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = str(e)
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||||
|
await writer.drain()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if isinstance(e, (ConnectionError, ConnectionResetError, ConnectionAbortedError)):
|
||||||
|
|
||||||
|
await self.handle_proxy_failure()
|
||||||
|
last_error = str(e)
|
||||||
|
retry_count += 1
|
||||||
|
if retry_count < max_retries:
|
||||||
|
logging.warning(get_message('request_retry', self.language, max_retries - retry_count))
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
continue
|
||||||
|
return
|
||||||
|
if not isinstance(e, (asyncio.CancelledError,)):
|
||||||
|
logging.error(f"请求处理错误: {str(e)}")
|
||||||
|
try:
|
||||||
|
writer.write(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
|
||||||
|
await writer.drain()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
#if last_error:
|
||||||
|
logging.error(get_message('all_retries_failed', self.language, last_error))
|
||||||
|
|
||||||
|
async def _get_client(self, proxy):
|
||||||
|
async with self.client_pool_lock:
|
||||||
|
current_time = time.time()
|
||||||
|
if proxy in self.client_pool:
|
||||||
|
client, last_used = self.client_pool[proxy]
|
||||||
|
if current_time - last_used < 30 and not client.is_closed:
|
||||||
|
self.client_pool[proxy] = (client, current_time)
|
||||||
|
return client
|
||||||
|
else:
|
||||||
|
await client.aclose()
|
||||||
|
del self.client_pool[proxy]
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = await self._create_client(proxy)
|
||||||
|
if len(self.client_pool) >= self.max_pool_size:
|
||||||
|
oldest_proxy = min(self.client_pool, key=lambda x: self.client_pool[x][1])
|
||||||
|
old_client, _ = self.client_pool[oldest_proxy]
|
||||||
|
await old_client.aclose()
|
||||||
|
del self.client_pool[oldest_proxy]
|
||||||
|
|
||||||
|
self.client_pool[proxy] = (client, current_time)
|
||||||
|
return client
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"创建客户端失败: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
async def handle_proxy_failure(self):
|
async def handle_proxy_failure(self):
|
||||||
|
|
||||||
|
if not self.check_proxies:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self.last_proxy_failure_time < self.proxy_failure_cooldown:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if self.switching_proxy:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not self.proxy_failure_lock.locked():
|
||||||
|
async with self.proxy_failure_lock:
|
||||||
|
if (current_time - self.last_proxy_failure_time < self.proxy_failure_cooldown or
|
||||||
|
self.switching_proxy):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
self.last_proxy_failure_time = current_time
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_valid = await self.check_current_proxy()
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
#logging.warning(get_message('proxy_failure', self.language, self.current_proxy))
|
||||||
|
await self.switch_proxy()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(get_message('proxy_check_error', self.language, str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"代理失败处理出错: {str(e)}")
|
||||||
|
|
||||||
|
async def switch_proxy(self):
|
||||||
try:
|
try:
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
if self.switching_proxy or (current_time - self.last_switch_attempt < self.switch_cooldown):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.proxy_fail_count += 1
|
if current_time - self.last_switch_attempt < self.switch_cooldown:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if self.switching_proxy:
|
||||||
|
return False
|
||||||
|
|
||||||
if self.proxy_fail_count >= self.max_fail_count:
|
|
||||||
current_proxy = self.current_proxy if self.current_proxy else get_message('no_proxy', self.language)
|
|
||||||
logging.warning(get_message('proxy_consecutive_fails', self.language,
|
|
||||||
current_proxy, self.proxy_fail_count))
|
|
||||||
try:
|
|
||||||
self.switching_proxy = True
|
self.switching_proxy = True
|
||||||
self.last_switch_attempt = current_time
|
self.last_switch_attempt = current_time
|
||||||
old_proxy = current_proxy
|
old_proxy = self.current_proxy
|
||||||
|
|
||||||
|
temp_current_proxy = self.current_proxy
|
||||||
|
|
||||||
await self.get_proxy()
|
await self.get_proxy()
|
||||||
|
|
||||||
self.proxy_fail_count = 0
|
|
||||||
self.proxy_failed = False
|
|
||||||
|
|
||||||
finally:
|
if temp_current_proxy != self.current_proxy:
|
||||||
self.switching_proxy = False
|
self._log_proxy_switch(old_proxy, self.current_proxy)
|
||||||
else:
|
|
||||||
current_proxy = self.current_proxy if self.current_proxy else get_message('no_proxy', self.language)
|
self.last_proxy_failure_time = current_time
|
||||||
logging.warning(get_message('request_retry', self.language,
|
return True
|
||||||
self.max_fail_count - self.proxy_fail_count))
|
|
||||||
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_proxy = self.current_proxy if self.current_proxy else get_message('no_proxy', self.language)
|
logging.error(get_message('proxy_switch_error', self.language, str(e)))
|
||||||
logging.error(get_message('proxy_invalid', self.language, current_proxy))
|
return False
|
||||||
|
finally:
|
||||||
|
|
||||||
self.switching_proxy = False
|
self.switching_proxy = False
|
||||||
|
|
||||||
async def check_current_proxy(self):
|
async def check_current_proxy(self):
|
||||||
try:
|
try:
|
||||||
proxy = self.current_proxy
|
proxy = self.current_proxy
|
||||||
|
if not proxy:
|
||||||
|
return False
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
if not self.check_proxies:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
if proxy in self.last_check_time:
|
if proxy in self.last_check_time:
|
||||||
if current_time - self.last_check_time[proxy] < self.check_cooldown:
|
if current_time - self.last_check_time[proxy] < self.check_cooldown:
|
||||||
return self.proxy_check_cache.get(proxy, (current_time, True))[1]
|
return self.proxy_check_cache.get(proxy, (current_time, True))[1]
|
||||||
@ -828,21 +1168,25 @@ class AsyncProxyServer:
|
|||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
self.last_check_time[proxy] = current_time
|
self.last_check_time[proxy] = current_time
|
||||||
|
|
||||||
test_url = self.config.get('test_url', 'https://www.baidu.com')
|
test_url = self.config.get('test_url', 'https://www.baidu.com')
|
||||||
|
|
||||||
proxy_type = proxy.split('://')[0]
|
|
||||||
async with httpx.AsyncClient(
|
try:
|
||||||
proxies={f"{proxy_type}://": proxy},
|
from modules.modules import check_proxy
|
||||||
timeout=10,
|
|
||||||
verify=False
|
is_valid = await check_proxy(proxy, test_url)
|
||||||
) as client:
|
logging.warning(f"代理检查结果: {proxy} - {'有效' if is_valid else '无效'}")
|
||||||
response = await client.get(test_url)
|
except Exception as e:
|
||||||
is_valid = response.status_code == 200
|
logging.error(f"代理检测错误: {proxy} - {str(e)}")
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
|
||||||
self.proxy_check_cache[proxy] = (current_time, is_valid)
|
self.proxy_check_cache[proxy] = (current_time, is_valid)
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logging.error(f"代理检测异常: {str(e)}")
|
||||||
|
if 'proxy' in locals():
|
||||||
self.proxy_check_cache[proxy] = (current_time, False)
|
self.proxy_check_cache[proxy] = (current_time, False)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -860,39 +1204,40 @@ class AsyncProxyServer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize_proxies(self):
|
def initialize_proxies(self):
|
||||||
if self.mode == 'cycle':
|
|
||||||
if hasattr(self, 'proxies') and self.proxies:
|
if hasattr(self, 'proxies') and self.proxies:
|
||||||
self.proxy_cycle = cycle(self.proxies)
|
self.proxy_cycle = cycle(self.proxies)
|
||||||
elif self.use_getip:
|
return
|
||||||
pass
|
|
||||||
else:
|
if self.use_getip:
|
||||||
|
logging.info("API模式,将在请求时动态获取代理")
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.proxy_file, 'r') as f:
|
logging.info(f"从文件 {self.proxy_file} 加载代理列表")
|
||||||
self.proxies = [line.strip() for line in f if line.strip()]
|
self.proxies = self._load_file_proxies()
|
||||||
|
logging.info(f"加载到 {len(self.proxies)} 个代理")
|
||||||
|
|
||||||
if self.proxies:
|
if self.proxies:
|
||||||
self.proxy_cycle = cycle(self.proxies)
|
self.proxy_cycle = cycle(self.proxies)
|
||||||
|
self.current_proxy = next(self.proxy_cycle)
|
||||||
|
logging.info(f"初始代理: {self.current_proxy}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('load_proxy_file_error', self.language, str(e)))
|
logging.error(f"初始化代理列表失败: {str(e)}")
|
||||||
|
|
||||||
async def cleanup_disconnected_ips(self):
|
async def cleanup_disconnected_ips(self):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
active_ips = set()
|
active_ips = {addr[0] for addr in self.active_connections}
|
||||||
for client_info in self.get_active_connections():
|
|
||||||
active_ips.add(client_info[0])
|
|
||||||
|
|
||||||
self.connected_clients = active_ips
|
self.connected_clients = active_ips
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('cleanup_error', self.language, str(e)))
|
logging.error(get_message('cleanup_error', self.language, str(e)))
|
||||||
|
|
||||||
await asyncio.sleep(30)
|
await asyncio.sleep(30)
|
||||||
|
|
||||||
def is_docker():
|
def is_docker():
|
||||||
return os.path.exists('/.dockerenv')
|
return os.path.exists('/.dockerenv')
|
||||||
|
|
||||||
async def get_proxy_status(self):
|
async def get_proxy_status(self):
|
||||||
if self.mode == 'load_balance':
|
if self.mode == 'loadbalance':
|
||||||
return f"{get_message('current_proxy', self.language)}: {self.current_proxy}"
|
return f"{get_message('current_proxy', self.language)}: {self.current_proxy}"
|
||||||
else:
|
else:
|
||||||
time_left = self.time_until_next_switch()
|
time_left = self.time_until_next_switch()
|
||||||
@ -979,8 +1324,17 @@ class AsyncProxyServer:
|
|||||||
async def _cleanup_pool(self):
|
async def _cleanup_pool(self):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
def is_expired(conn):
|
||||||
|
return hasattr(conn, 'is_closed') and conn.is_closed
|
||||||
|
|
||||||
|
to_remove = []
|
||||||
for proxy, conn in list(self.proxy_pool.items()):
|
for proxy, conn in list(self.proxy_pool.items()):
|
||||||
if conn.is_closed:
|
if is_expired(conn):
|
||||||
|
to_remove.append(proxy)
|
||||||
|
|
||||||
|
for proxy in to_remove:
|
||||||
|
if proxy in self.proxy_pool:
|
||||||
del self.proxy_pool[proxy]
|
del self.proxy_pool[proxy]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'连接池清理错误: {e}')
|
logging.error(f'连接池清理错误: {e}')
|
||||||
@ -990,12 +1344,24 @@ class AsyncProxyServer:
|
|||||||
if old_proxy != new_proxy:
|
if old_proxy != new_proxy:
|
||||||
old_proxy = old_proxy if old_proxy else get_message('no_proxy', self.language)
|
old_proxy = old_proxy if old_proxy else get_message('no_proxy', self.language)
|
||||||
new_proxy = new_proxy if new_proxy else get_message('no_proxy', self.language)
|
new_proxy = new_proxy if new_proxy else get_message('no_proxy', self.language)
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if not hasattr(self, '_last_log_time') or \
|
||||||
|
not hasattr(self, '_last_log_content') or \
|
||||||
|
current_time - self._last_log_time > 1 or \
|
||||||
|
self._last_log_content != f"{old_proxy} -> {new_proxy}":
|
||||||
logging.info(get_message('proxy_switch', self.language, old_proxy, new_proxy))
|
logging.info(get_message('proxy_switch', self.language, old_proxy, new_proxy))
|
||||||
|
self._last_log_time = current_time
|
||||||
|
self._last_log_content = f"{old_proxy} -> {new_proxy}"
|
||||||
|
|
||||||
async def _validate_proxy(self, proxy):
|
async def _validate_proxy(self, proxy):
|
||||||
if not proxy:
|
if not proxy:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if not self.check_proxies:
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not validate_proxy(proxy):
|
if not validate_proxy(proxy):
|
||||||
logging.warning(get_message('proxy_invalid', self.language, proxy))
|
logging.warning(get_message('proxy_invalid', self.language, proxy))
|
||||||
@ -1027,6 +1393,7 @@ class AsyncProxyServer:
|
|||||||
async def get_proxy(self):
|
async def get_proxy(self):
|
||||||
try:
|
try:
|
||||||
old_proxy = self.current_proxy
|
old_proxy = self.current_proxy
|
||||||
|
temp_current_proxy = self.current_proxy
|
||||||
|
|
||||||
if not self.use_getip and self.proxies:
|
if not self.use_getip and self.proxies:
|
||||||
if not self.proxy_cycle:
|
if not self.proxy_cycle:
|
||||||
@ -1037,6 +1404,7 @@ class AsyncProxyServer:
|
|||||||
if await self._validate_proxy(new_proxy):
|
if await self._validate_proxy(new_proxy):
|
||||||
self.current_proxy = new_proxy
|
self.current_proxy = new_proxy
|
||||||
self.last_switch_time = time.time()
|
self.last_switch_time = time.time()
|
||||||
|
if temp_current_proxy != self.current_proxy:
|
||||||
self._log_proxy_switch(old_proxy, self.current_proxy)
|
self._log_proxy_switch(old_proxy, self.current_proxy)
|
||||||
return self.current_proxy
|
return self.current_proxy
|
||||||
|
|
||||||
@ -1047,8 +1415,10 @@ class AsyncProxyServer:
|
|||||||
try:
|
try:
|
||||||
new_proxy = await self._load_getip_proxy()
|
new_proxy = await self._load_getip_proxy()
|
||||||
if new_proxy and await self._validate_proxy(new_proxy):
|
if new_proxy and await self._validate_proxy(new_proxy):
|
||||||
|
|
||||||
self.current_proxy = new_proxy
|
self.current_proxy = new_proxy
|
||||||
self.last_switch_time = time.time()
|
self.last_switch_time = time.time()
|
||||||
|
if temp_current_proxy != self.current_proxy:
|
||||||
self._log_proxy_switch(old_proxy, self.current_proxy)
|
self._log_proxy_switch(old_proxy, self.current_proxy)
|
||||||
return self.current_proxy
|
return self.current_proxy
|
||||||
else:
|
else:
|
||||||
@ -1061,3 +1431,36 @@ class AsyncProxyServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(get_message('proxy_get_error', self.language, str(e)))
|
logging.error(get_message('proxy_get_error', self.language, str(e)))
|
||||||
return self.current_proxy
|
return self.current_proxy
|
||||||
|
|
||||||
|
async def cleanup_clients(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
async with self.client_pool_lock:
|
||||||
|
current_time = time.time()
|
||||||
|
expired_proxies = [
|
||||||
|
proxy for proxy, (_, last_used) in self.client_pool.items()
|
||||||
|
if current_time - last_used > 30
|
||||||
|
]
|
||||||
|
for proxy in expired_proxies:
|
||||||
|
client, _ = self.client_pool[proxy]
|
||||||
|
await client.aclose()
|
||||||
|
del self.client_pool[proxy]
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"清理客户端池错误: {str(e)}")
|
||||||
|
await asyncio.sleep(30)
|
||||||
|
|
||||||
|
def get_active_connections(self):
|
||||||
|
active = []
|
||||||
|
for task in self.tasks:
|
||||||
|
if not task.done():
|
||||||
|
try:
|
||||||
|
coro = task.get_coro()
|
||||||
|
if coro.__qualname__.startswith('AsyncProxyServer.handle_client'):
|
||||||
|
writer = coro.cr_frame.f_locals.get('writer')
|
||||||
|
if writer:
|
||||||
|
peername = writer.get_extra_info('peername')
|
||||||
|
if peername:
|
||||||
|
active.append(peername)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return active
|
||||||
|
@ -6,3 +6,5 @@ Requests==2.32.3
|
|||||||
tqdm>=4.65.0
|
tqdm>=4.65.0
|
||||||
flask>=2.0.1
|
flask>=2.0.1
|
||||||
werkzeug>=2.0.0
|
werkzeug>=2.0.0
|
||||||
|
asyncio>=3.4.3
|
||||||
|
configparser>=5.0.0
|
7
web/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
web/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
web/static/js/jquery.min.js
vendored
Normal file
2
web/static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1329,7 +1329,7 @@
|
|||||||
<label class="form-label" data-i18n="run_mode_label">运行模式</label>
|
<label class="form-label" data-i18n="run_mode_label">运行模式</label>
|
||||||
<select class="form-select" name="mode">
|
<select class="form-select" name="mode">
|
||||||
<option value="cycle" data-i18n="cycle_mode">循环模式</option>
|
<option value="cycle" data-i18n="cycle_mode">循环模式</option>
|
||||||
<option value="load_balance" data-i18n="load_balance_mode">负载均衡</option>
|
<option value="loadbalance" data-i18n="loadbalance_mode">负载均衡</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -1375,7 +1375,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" name="check_proxies" id="check-proxies">
|
<input class="form-check-input" type="checkbox" name="check_proxies" id="check-proxies">
|
||||||
<label class="form-check-label" for="check-proxies" data-i18n="enable_proxy_check_label">启用代理检测</label>
|
<label class="form-check-label" for="check-proxies" data-i18n="enable_proxy_check_label">代理有效性检测</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -1590,8 +1590,8 @@
|
|||||||
|
|
||||||
<div class="notification-container" id="notification-container"></div>
|
<div class="notification-container" id="notification-container"></div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="/static/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
<script src="/static/js/jquery.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
let lastLogId = 0;
|
let lastLogId = 0;
|
||||||
let searchTimeout = null;
|
let searchTimeout = null;
|
||||||
@ -1630,7 +1630,13 @@
|
|||||||
$.get(appendToken('/api/status'), function(data) {
|
$.get(appendToken('/api/status'), function(data) {
|
||||||
// 只在值发生变化时更新显示
|
// 只在值发生变化时更新显示
|
||||||
const currentProxy = $('#current-proxy').text();
|
const currentProxy = $('#current-proxy').text();
|
||||||
const newProxy = data.current_proxy || translations[currentLanguage].no_proxy;
|
let newProxy = data.current_proxy || translations[currentLanguage].no_proxy;
|
||||||
|
|
||||||
|
// 负载均衡模式特殊处理
|
||||||
|
if (data.mode === 'loadbalance') {
|
||||||
|
newProxy = translations[currentLanguage].loadbalance_mode || '负载均衡模式';
|
||||||
|
}
|
||||||
|
|
||||||
if (currentProxy !== newProxy) {
|
if (currentProxy !== newProxy) {
|
||||||
$('#current-proxy').text(newProxy);
|
$('#current-proxy').text(newProxy);
|
||||||
}
|
}
|
||||||
@ -1651,7 +1657,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新进度条和其他状态...
|
// 更新进度条和其他状态...
|
||||||
if (data.interval && data.time_left !== undefined) {
|
if (data.mode === 'loadbalance') {
|
||||||
|
// 负载均衡模式不显示倒计时
|
||||||
|
$('#next-switch').text(translations[currentLanguage].loadbalance_mode || '负载均衡模式');
|
||||||
|
$('.progress-bar').css('width', '100%');
|
||||||
|
} else if (data.interval && data.time_left !== undefined) {
|
||||||
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
||||||
$('#next-switch').text(`${timeLeft} ${translations[currentLanguage].seconds}`);
|
$('#next-switch').text(`${timeLeft} ${translations[currentLanguage].seconds}`);
|
||||||
|
|
||||||
@ -1673,13 +1683,20 @@
|
|||||||
|
|
||||||
function updateCountdown() {
|
function updateCountdown() {
|
||||||
$.get(appendToken('/api/status'), function(data) {
|
$.get(appendToken('/api/status'), function(data) {
|
||||||
if (data.time_left !== undefined && data.interval) {
|
if (data.time_left !== undefined) {
|
||||||
|
// 负载均衡模式特殊处理
|
||||||
|
if (data.time_left === -1 || data.mode === 'loadbalance') {
|
||||||
|
// 负载均衡模式不显示倒计时
|
||||||
|
$('.progress-bar').css('width', '100%');
|
||||||
|
$('#next-switch').text(translations[currentLanguage]['loadbalance_mode'] || '负载均衡模式');
|
||||||
|
} else if (data.interval) {
|
||||||
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
const timeLeft = Math.max(0, Math.ceil(data.time_left));
|
||||||
const progress = Math.min(100, ((data.interval - data.time_left) / data.interval) * 100);
|
const progress = Math.min(100, ((data.interval - data.time_left) / data.interval) * 100);
|
||||||
|
|
||||||
$('.progress-bar').css('width', progress + '%');
|
$('.progress-bar').css('width', progress + '%');
|
||||||
$('#next-switch').text(timeLeft + ' ' + translations[currentLanguage]['seconds']);
|
$('#next-switch').text(timeLeft + ' ' + translations[currentLanguage]['seconds']);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1688,31 +1705,34 @@
|
|||||||
if (data.config) {
|
if (data.config) {
|
||||||
const config = data.config;
|
const config = data.config;
|
||||||
|
|
||||||
// 更新表单值
|
// 更新表单值 - 始终使用配置文件中的值,避免空值
|
||||||
$('input[name="port"]').val(config.port);
|
$('input[name="port"]').val(config.port || '1080');
|
||||||
$('select[name="mode"]').val(config.mode);
|
$('select[name="mode"]').val(config.mode || 'cycle');
|
||||||
$('input[name="interval"]').val(config.interval);
|
$('input[name="interval"]').val(config.interval || '300');
|
||||||
$('input[name="username"]').val(config.username);
|
$('input[name="username"]').val(config.username || '');
|
||||||
$('input[name="password"]').val(config.password);
|
$('input[name="password"]').val(config.password || '');
|
||||||
// 修正checkbox的状态设置
|
// 修正checkbox的状态设置
|
||||||
$('input[name="use_getip"]').prop('checked', config.use_getip.toLowerCase() === 'true');
|
$('input[name="use_getip"]').prop('checked', (config.use_getip || '').toLowerCase() === 'true');
|
||||||
$('input[name="getip_url"]').val(config.getip_url);
|
$('input[name="getip_url"]').val(config.getip_url || '');
|
||||||
$('input[name="proxy_username"]').val(config.proxy_username);
|
$('input[name="proxy_username"]').val(config.proxy_username || '');
|
||||||
$('input[name="proxy_password"]').val(config.proxy_password);
|
$('input[name="proxy_password"]').val(config.proxy_password || '');
|
||||||
$('input[name="proxy_file"]').val(config.proxy_file);
|
$('input[name="proxy_file"]').val(config.proxy_file || 'ip.txt');
|
||||||
$('input[name="check_proxies"]').prop('checked', config.check_proxies.toLowerCase() === 'true');
|
$('input[name="check_proxies"]').prop('checked', (config.check_proxies || '').toLowerCase() === 'true');
|
||||||
$('select[name="language"]').val(config.language);
|
$('select[name="language"]').val(config.language || 'cn');
|
||||||
$('input[name="whitelist_file"]').val(config.whitelist_file);
|
$('input[name="whitelist_file"]').val(config.whitelist_file || '');
|
||||||
$('input[name="blacklist_file"]').val(config.blacklist_file);
|
$('input[name="blacklist_file"]').val(config.blacklist_file || '');
|
||||||
$('select[name="ip_auth_priority"]').val(config.ip_auth_priority);
|
$('select[name="ip_auth_priority"]').val(config.ip_auth_priority || 'whitelist');
|
||||||
$('select[name="display_level"]').val(config.display_level);
|
$('select[name="display_level"]').val(config.display_level || '1');
|
||||||
|
|
||||||
// 根据 use_getip 的状态更新 getip_url 输入框的禁用状态
|
// 根据 use_getip 的状态更新 getip_url 输入框的禁用状态
|
||||||
const useGetip = config.use_getip.toLowerCase() === 'true';
|
const useGetip = (config.use_getip || '').toLowerCase() === 'true';
|
||||||
$('input[name="getip_url"]').prop('disabled', !useGetip);
|
$('input[name="getip_url"]').prop('disabled', !useGetip);
|
||||||
|
|
||||||
// 更新其他相关UI元素
|
// 更新其他相关UI元素
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
|
||||||
|
// 更新地址显示
|
||||||
|
updateAddresses(config);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2146,7 +2166,7 @@
|
|||||||
'switch_interval': '切换间隔(秒)',
|
'switch_interval': '切换间隔(秒)',
|
||||||
'run_mode_text': '运行模式',
|
'run_mode_text': '运行模式',
|
||||||
'cycle_mode': '循环模式',
|
'cycle_mode': '循环模式',
|
||||||
'load_balance_mode': '负载均衡',
|
'loadbalance_mode': '负载均衡',
|
||||||
'language_text': '语言',
|
'language_text': '语言',
|
||||||
'auth_config': '认证配置',
|
'auth_config': '认证配置',
|
||||||
'username': '用户名',
|
'username': '用户名',
|
||||||
@ -2196,11 +2216,11 @@
|
|||||||
'switch_interval_label': '切换间隔(秒)',
|
'switch_interval_label': '切换间隔(秒)',
|
||||||
'run_mode_label': '运行模式',
|
'run_mode_label': '运行模式',
|
||||||
'cycle_mode': '循环模式',
|
'cycle_mode': '循环模式',
|
||||||
'load_balance_mode': '负载均衡',
|
'loadbalance_mode': '负载均衡',
|
||||||
'auth_config_title': '认证配置',
|
'auth_config_title': '认证配置',
|
||||||
'use_api_label': '使用API获取代理',
|
'use_api_label': '使用API获取代理',
|
||||||
'api_url_label': 'API地址',
|
'api_url_label': 'API地址',
|
||||||
'enable_proxy_check_label': '启用代理检测',
|
'enable_proxy_check_label': '代理有效性检测',
|
||||||
'proxy_list_label': '代理列表',
|
'proxy_list_label': '代理列表',
|
||||||
'save_proxy_btn': '保存代理',
|
'save_proxy_btn': '保存代理',
|
||||||
'check_proxy_btn': '检测代理',
|
'check_proxy_btn': '检测代理',
|
||||||
@ -2301,11 +2321,11 @@
|
|||||||
'loading': '加载中...',
|
'loading': '加载中...',
|
||||||
'manual_switch_btn': '手动切换',
|
'manual_switch_btn': '手动切换',
|
||||||
'cycle_mode': '循环模式',
|
'cycle_mode': '循环模式',
|
||||||
'load_balance_mode': '负载均衡',
|
'loadbalance_mode': '负载均衡',
|
||||||
'next_switch': '下次切换',
|
'next_switch': '下次切换',
|
||||||
'proxy_mode': {
|
'proxy_mode': {
|
||||||
'cycle': '循环模式',
|
'cycle': '循环模式',
|
||||||
'load_balance': '负载均衡'
|
'loadbalance': '负载均衡'
|
||||||
},
|
},
|
||||||
'service_action': {
|
'service_action': {
|
||||||
'start_success': '服务启动成功',
|
'start_success': '服务启动成功',
|
||||||
@ -2373,7 +2393,7 @@
|
|||||||
'switch_interval': 'Switch Interval(s)',
|
'switch_interval': 'Switch Interval(s)',
|
||||||
'run_mode_text': 'Run Mode',
|
'run_mode_text': 'Run Mode',
|
||||||
'cycle_mode': 'Cycle Mode',
|
'cycle_mode': 'Cycle Mode',
|
||||||
'load_balance_mode': 'Load Balance',
|
'loadbalance_mode': 'Load Balance',
|
||||||
'language_text': 'Language',
|
'language_text': 'Language',
|
||||||
'auth_config': 'Auth Config',
|
'auth_config': 'Auth Config',
|
||||||
'username': 'Username',
|
'username': 'Username',
|
||||||
@ -2424,11 +2444,11 @@
|
|||||||
'switch_interval_label': 'Switch Interval(s)',
|
'switch_interval_label': 'Switch Interval(s)',
|
||||||
'run_mode_label': 'Run Mode',
|
'run_mode_label': 'Run Mode',
|
||||||
'cycle_mode': 'Cycle Mode',
|
'cycle_mode': 'Cycle Mode',
|
||||||
'load_balance_mode': 'Load Balance',
|
'loadbalance_mode': 'Load Balance',
|
||||||
'auth_config_title': 'Authentication',
|
'auth_config_title': 'Authentication',
|
||||||
'use_api_label': 'Use API for Proxy',
|
'use_api_label': 'Use API for Proxy',
|
||||||
'api_url_label': 'API URL',
|
'api_url_label': 'API URL',
|
||||||
'enable_proxy_check_label': 'Enable Proxy Check',
|
'enable_proxy_check_label': 'Proxy Validity Check',
|
||||||
'proxy_list_label': 'Proxy List',
|
'proxy_list_label': 'Proxy List',
|
||||||
'save_proxy_btn': 'Save Proxies',
|
'save_proxy_btn': 'Save Proxies',
|
||||||
'check_proxy_btn': 'Check Proxies',
|
'check_proxy_btn': 'Check Proxies',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user