clash-linux commited on
Commit
7bba2af
·
verified ·
1 Parent(s): f4f961b

Upload 17 files

Browse files
Files changed (2) hide show
  1. app/main.py +88 -2
  2. requirements.txt +4 -1
app/main.py CHANGED
@@ -7,11 +7,16 @@ Simple Clash Relay - Flask 应用入口
7
 
8
  import os
9
  import logging
10
- from flask import Flask, request, jsonify, Response
 
 
 
 
 
 
11
  from .clash_manager import ClashManager
12
  from .sub_manager import SubscriptionManager
13
  from .auth import authenticate
14
- import requests
15
 
16
  # 配置日志
17
  logging.basicConfig(
@@ -421,6 +426,87 @@ def index():
421
  </html>
422
  """
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  if __name__ == "__main__":
425
  # 如果直接运行此文件,将初始化应用并启动Flask服务器
426
  initialize()
 
7
 
8
  import os
9
  import logging
10
+ import time
11
+ import signal
12
+ import sys
13
+ import json
14
+ import requests
15
+ from flask import Flask, request, jsonify, Response, send_from_directory
16
+ from werkzeug.exceptions import HTTPException
17
  from .clash_manager import ClashManager
18
  from .sub_manager import SubscriptionManager
19
  from .auth import authenticate
 
20
 
21
  # 配置日志
22
  logging.basicConfig(
 
426
  </html>
427
  """
428
 
429
+ # --- Clash API 反向代理 ---
430
+ @app.route('/ui/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
431
+ def proxy_clash_api(path):
432
+ """将 /ui/ 下的请求转发给内部的 Clash API (127.0.0.1:9090)"""
433
+ if not clash_manager:
434
+ return jsonify({"error": "Clash Manager 未初始化"}), 503
435
+
436
+ clash_api_base_url = f"http://127.0.0.1:{clash_manager.api_port}/"
437
+ target_url = clash_api_base_url + path
438
+
439
+ # 准备转发请求头,移除 Host 头,可能需要添加原始 Host 或 Forwarded 头
440
+ headers = {key: value for key, value in request.headers if key.lower() != 'host'}
441
+ # 确保传递原始的 X-Real-IP 或 X-Forwarded-For (如果存在)
442
+ # headers['X-Forwarded-For'] = request.remote_addr
443
+
444
+ # 处理 Websocket 连接 (Clash API 日志/流量推送可能使用)
445
+ if request.headers.get('Upgrade', '').lower() == 'websocket':
446
+ logger.info(f"检测到 WebSocket 请求: {path}")
447
+ # Flask 本身不直接支持代理 WebSocket,需要额外库或特殊处理
448
+ # 这里返回错误,表明此代理不支持 WebSocket
449
+ # 可以考虑使用 flask_sock 或其他库来支持
450
+ logger.warning("当前反向代理不支持 WebSocket 连接")
451
+ return jsonify({"error": "WebSocket proxying not supported"}), 501 # Not Implemented
452
+
453
+ try:
454
+ resp = requests.request(
455
+ method=request.method,
456
+ url=target_url,
457
+ headers=headers,
458
+ data=request.get_data(),
459
+ params=request.args,
460
+ stream=True, # 使用流式传输以处理大数据和流式API (如日志)
461
+ timeout=30 # 设置超时
462
+ )
463
+
464
+ # 准备转发响应头
465
+ response_headers = []
466
+ for name, value in resp.raw.headers.items():
467
+ # 避免转发某些头 (例如 'Transfer-Encoding': 'chunked' 可能导致问题)
468
+ if name.lower() not in ['content-encoding', 'content-length', 'transfer-encoding', 'connection']:
469
+ response_headers.append((name, value))
470
+
471
+ # 创建Flask响应对象
472
+ # 使用 resp.content 而不是迭代器,对于非流式响应更简单可靠
473
+ response = Response(resp.content, status=resp.status_code, headers=response_headers)
474
+ return response
475
+
476
+ except requests.exceptions.ConnectionError:
477
+ logger.error(f"无法连接到内部Clash API: {target_url}")
478
+ return jsonify({"error": "无法连接到内部 Clash API"}), 502 # Bad Gateway
479
+ except requests.exceptions.Timeout:
480
+ logger.error(f"连接到内部Clash API超时: {target_url}")
481
+ return jsonify({"error": "内部 Clash API 请求超时"}), 504 # Gateway Timeout
482
+ except Exception as e:
483
+ logger.error(f"代理到 Clash API 时出错: {str(e)}")
484
+ return jsonify({"error": f"代理请求失败: {str(e)}"}), 500
485
+
486
+ # --- 错误处理 ---
487
+ @app.errorhandler(HTTPException)
488
+ def handle_exception(e):
489
+ """返回JSON格式的HTTP错误"""
490
+ response = e.get_response()
491
+ response.data = json.dumps({
492
+ "code": e.code,
493
+ "name": e.name,
494
+ "description": e.description,
495
+ })
496
+ response.content_type = "application/json"
497
+ return response
498
+
499
+ @app.errorhandler(Exception)
500
+ def handle_generic_exception(e):
501
+ """处理未捕获的异常"""
502
+ logger.error(f"未捕获的异常: {str(e)}", exc_info=True)
503
+ # 对于非HTTP异常,返回500 Internal Server Error
504
+ return jsonify({
505
+ "code": 500,
506
+ "name": "Internal Server Error",
507
+ "description": f"发生内部错误: {str(e)}"
508
+ }), 500
509
+
510
  if __name__ == "__main__":
511
  # 如果直接运行此文件,将初始化应用并启动Flask服务器
512
  initialize()
requirements.txt CHANGED
@@ -4,4 +4,7 @@ requests==2.28.1
4
  Werkzeug==2.0.1
5
  PyYAML==6.0.1
6
  # pyyaml==6.0 # 已通过 apk 安装
7
- # 添加与 Flask==2.0.1 兼容的版本
 
 
 
 
4
  Werkzeug==2.0.1
5
  PyYAML==6.0.1
6
  # pyyaml==6.0 # 已通过 apk 安装
7
+ # 添加与 Flask==2.0.1 兼容的版本
8
+
9
+ # 确保 requests 库被包含,因为反向代理需要它
10
+ # (requests 已在上面,无需重复添加)