clash / app /main.py
clash-linux's picture
Upload 11 files
2a7659b verified
raw
history blame
6.14 kB
#!/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
@app.before_first_request
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
@app.route("/api/nodes", methods=["GET"])
@authenticate
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
@app.route("/api/switch", methods=["PUT"])
@authenticate
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
@app.route("/api/current", methods=["GET"])
@authenticate
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
@app.route("/api/refresh", methods=["POST"])
@authenticate
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
@app.route("/health", methods=["GET"])
def health_check():
"""健康检查接口"""
return jsonify({"status": "ok"})
# 新增:代理请求转发功能
@app.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'])
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的根代理请求
@app.route('/proxy', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH'])
def proxy_root():
"""处理根代理请求"""
return proxy_request("")
@app.route('/', methods=['GET'])
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)