Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Simple Clash Relay - Flask 应用入口 | |
| """ | |
| import os | |
| import logging | |
| from flask import Flask, request, jsonify, Response | |
| from .clash_manager import ClashManager | |
| from .sub_manager import SubscriptionManager | |
| from .auth import authenticate | |
| import requests | |
| # 配置日志 | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # 从环境变量加载配置 | |
| SUB_URL = os.environ.get("SUB_URL") | |
| API_KEY = os.environ.get("API_KEY", "changeme") | |
| FLASK_PORT = int(os.environ.get("FLASK_PORT", 7860)) # 默认端口改为7860 | |
| CLASH_PROXY_PORT = int(os.environ.get("CLASH_PROXY_PORT", 7890)) | |
| CLASH_API_PORT = int(os.environ.get("CLASH_API_PORT", 9090)) | |
| # 初始化Flask应用 | |
| app = Flask(__name__) | |
| # 初始化管理器 | |
| clash_manager = None | |
| sub_manager = None | |
| def initialize(): | |
| """应用首次请求前的初始化""" | |
| global clash_manager, sub_manager | |
| logger.info("正在初始化应用...") | |
| # 初始化订阅管理器 | |
| sub_manager = SubscriptionManager( | |
| sub_url=SUB_URL, | |
| config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml") | |
| ) | |
| # 加载订阅并转换为Clash配置 | |
| try: | |
| sub_manager.load_and_convert_sub() | |
| logger.info("成功加载并转换订阅") | |
| except Exception as e: | |
| logger.error(f"加载订阅失败: {str(e)}") | |
| raise | |
| # 初始化Clash管理器 | |
| clash_manager = ClashManager( | |
| config_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "config.yaml"), | |
| clash_path=os.path.join(os.path.dirname(os.path.dirname(__file__)), "clash_core", "clash.meta-linux-amd64"), | |
| api_port=CLASH_API_PORT, | |
| proxy_port=CLASH_PROXY_PORT | |
| ) | |
| # 启动Clash Core | |
| try: | |
| clash_manager.start_clash() | |
| logger.info("成功启动Clash Core") | |
| except Exception as e: | |
| logger.error(f"启动Clash Core失败: {str(e)}") | |
| raise | |
| def get_nodes(): | |
| """获取可用节点列表""" | |
| try: | |
| nodes = clash_manager.get_nodes() | |
| return jsonify({"success": True, "nodes": nodes}) | |
| except Exception as e: | |
| logger.error(f"获取节点列表失败: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| def switch_node(): | |
| """切换到指定节点""" | |
| data = request.get_json() | |
| if not data or "node" not in data: | |
| return jsonify({"success": False, "error": "缺少'node'参数"}), 400 | |
| node_name = data["node"] | |
| try: | |
| clash_manager.switch_node(node_name) | |
| return jsonify({"success": True, "message": f"已切换到节点: {node_name}"}) | |
| except Exception as e: | |
| logger.error(f"切换到节点 {node_name} 失败: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| def get_current_node(): | |
| """获取当前使用的节点""" | |
| try: | |
| current_node = clash_manager.get_current_node() | |
| return jsonify({"success": True, "current_node": current_node}) | |
| except Exception as e: | |
| logger.error(f"获取当前节点失败: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| def refresh_subscription(): | |
| """刷新订阅并重新加载Clash配置""" | |
| try: | |
| sub_manager.load_and_convert_sub() | |
| clash_manager.restart_clash() | |
| return jsonify({"success": True, "message": "订阅已刷新,Clash已重启"}) | |
| except Exception as e: | |
| logger.error(f"刷新订阅失败: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| def health_check(): | |
| """健康检查接口""" | |
| return jsonify({"status": "ok"}) | |
| # 新增:代理请求转发功能 | |
| def proxy_request(path): | |
| """代理请求转发到Clash Core""" | |
| target_url = f"http://127.0.0.1:{CLASH_PROXY_PORT}/{path}" | |
| logger.debug(f"转发请求到: {target_url}") | |
| try: | |
| # 转发请求 | |
| resp = requests.request( | |
| method=request.method, | |
| url=target_url, | |
| headers={key: value for key, value in request.headers if key != 'Host'}, | |
| data=request.get_data(), | |
| cookies=request.cookies, | |
| allow_redirects=False, | |
| stream=True | |
| ) | |
| # 构建响应 | |
| excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] | |
| headers = [(name, value) for name, value in resp.raw.headers.items() | |
| if name.lower() not in excluded_headers] | |
| response = Response(resp.content, resp.status_code, headers) | |
| return response | |
| except Exception as e: | |
| logger.error(f"代理请求失败: {str(e)}") | |
| return jsonify({"success": False, "error": str(e)}), 500 | |
| # 新增:处理没有path的根代理请求 | |
| def proxy_root(): | |
| """处理根代理请求""" | |
| return proxy_request("") | |
| def index(): | |
| """首页 - 提供简单说明""" | |
| return """ | |
| <html> | |
| <head><title>Simple Clash Relay</title></head> | |
| <body> | |
| <h1>Simple Clash Relay</h1> | |
| <p>状态: 运行中</p> | |
| <p>API端点: /api/*</p> | |
| <p>代理端点: /proxy</p> | |
| <p>更多信息请查看文档。</p> | |
| </body> | |
| </html> | |
| """ | |
| if __name__ == "__main__": | |
| # 如果直接运行此文件,将初始化应用并启动Flask服务器 | |
| initialize() | |
| logger.info(f"启动Flask服务器,监听端口: {FLASK_PORT}") | |
| app.run(host="0.0.0.0", port=FLASK_PORT) |