BG5 commited on
Commit
97caefd
·
verified ·
1 Parent(s): 9373091

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +142 -23
app.py CHANGED
@@ -1,34 +1,153 @@
1
  import requests
2
- import importlib.util
3
  import sys
 
 
 
4
 
5
- def load_and_run_from_txt(txt_url: str):
6
- """同步方式下载txt并执行"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  try:
8
- # 下载txt文件
9
- response = requests.get(txt_url)
 
 
 
 
 
 
 
 
 
 
10
  response.raise_for_status()
11
 
12
- code_content = response.text
13
- print(f"下载成功,代码长度: {len(code_content)}")
14
 
15
- # 创建并执行模块
16
- spec = importlib.util.spec_from_loader("dynamic_api", loader=None)
17
- module = importlib.util.module_from_spec(spec)
18
-
19
- exec(code_content, module.__dict__)
20
- sys.modules["dynamic_api"] = module
21
-
22
- # 启动FastAPI
23
- if hasattr(module, 'app'):
24
- import uvicorn
25
- uvicorn.run(module.app, host="0.0.0.0", port=8000)
 
26
  else:
27
- print("未找到FastAPI应用")
 
28
 
29
- except Exception as e:
30
- print(f"执行失败: {e}")
 
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  if __name__ == "__main__":
33
- # 直接执行
34
- load_and_run_from_txt("https://bg5-pycode.static.hf.space/api.txt")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import requests
 
2
  import sys
3
+ import time
4
+ import subprocess
5
+ import os
6
 
7
+ # --- 配置 ---
8
+ # 要监测的远程代码URL
9
+ TXT_URL = "https://bg5-pycode.static.hf.space/api.txt"
10
+ # 将远程代码保存到本地的文件名
11
+ LOCAL_CODE_FILE = "dynamic_api_module.py"
12
+ # 检查更新的间隔时间(秒)
13
+ CHECK_INTERVAL = 30
14
+
15
+ # --- 全局变量,用于跟踪状态 ---
16
+ # 存储当前运行代码的HTTP ETag或内容哈希值
17
+ current_code_identifier = None
18
+ # 存储Uvicorn子进程对象
19
+ server_process = None
20
+
21
+ def check_for_updates():
22
+ """
23
+ 检查远程代码是否有更新。
24
+ 使用HTTP ETag头来高效检查,如果没有ETag则比较内容。
25
+ 返回一个元组: (是否有更新: bool, 新代码内容: str, 新标识符: str)
26
+ """
27
+ global current_code_identifier
28
+ print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 正在检查 {TXT_URL} ...")
29
+
30
  try:
31
+ headers = {}
32
+ # 如果我们有ETag,就使用If-None-Match头,让服务器判断内容是否改变
33
+ if current_code_identifier and current_code_identifier.startswith('"'):
34
+ headers['If-None-Match'] = current_code_identifier
35
+
36
+ response = requests.get(TXT_URL, headers=headers, timeout=10)
37
+
38
+ # 状态码 304 Not Modified 表示内容没有变化
39
+ if response.status_code == 304:
40
+ print("代码无变化 (ETag未改变)。")
41
+ return False, None, None
42
+
43
  response.raise_for_status()
44
 
45
+ new_code_content = response.text
 
46
 
47
+ # 优先使用ETag作为标识符,更高效
48
+ new_etag = response.headers.get('ETag')
49
+ if new_etag:
50
+ new_identifier = new_etag
51
+ else:
52
+ # 如果服务器不支持ETag,则计算内容的哈希值作为标识符
53
+ import hashlib
54
+ new_identifier = hashlib.sha256(new_code_content.encode('utf-8')).hexdigest()
55
+
56
+ if new_identifier != current_code_identifier:
57
+ print("检测到代码更新!")
58
+ return True, new_code_content, new_identifier
59
  else:
60
+ print("代码无变化 (内容哈希值未改变)。")
61
+ return False, None, None
62
 
63
+ except requests.exceptions.RequestException as e:
64
+ print(f"检查更新失败: {e}")
65
+ return False, None, None
66
 
67
+ def start_or_restart_server(code_content: str):
68
+ """
69
+ 停止当前服务器子进程(如果存在),然后根据新代码启动一个新进程。
70
+ """
71
+ global server_process
72
+
73
+ # 1. 停止当前正在运行的服务器
74
+ if server_process and server_process.poll() is None:
75
+ print("检测到正在运行的服务器,正在终止...")
76
+ server_process.terminate()
77
+ try:
78
+ # 等待进程完全终止
79
+ server_process.wait(timeout=5)
80
+ print("旧进程已成功终止。")
81
+ except subprocess.TimeoutExpired:
82
+ print("旧进程终止超时,强制结束。")
83
+ server_process.kill()
84
+
85
+ # 2. 将新代码写入本地文件
86
+ # 确保文件末尾有启动服务器的逻辑
87
+ # 注意:远程的api.txt需要包含名为 'app' 的FastAPI实例
88
+ boot_loader = """
89
+ # Injected by watcher script to run the server
90
  if __name__ == "__main__":
91
+ import uvicorn
92
+ # Make sure 'app' is defined in the code above
93
+ if 'app' in locals():
94
+ uvicorn.run(app, host="0.0.0.0", port=8000)
95
+ else:
96
+ print("Error: FastAPI instance named 'app' not found in the downloaded code.")
97
+ """
98
+ full_code = code_content + "\n" + boot_loader
99
+ with open(LOCAL_CODE_FILE, "w", encoding="utf-8") as f:
100
+ f.write(full_code)
101
+ print(f"新代码已写入 {LOCAL_CODE_FILE}")
102
+
103
+ # 3. 启动新服务器子进程
104
+ print("正在启动新的服务器进程...")
105
+ # 使用 sys.executable 确保使用与当前脚本相同的Python解释器
106
+ command = [sys.executable, LOCAL_CODE_FILE]
107
+ server_process = subprocess.Popen(command)
108
+ print(f"新服务器已启动,进程ID: {server_process.pid}")
109
+
110
+ def main_watcher():
111
+ """
112
+ 监视器的主循环。
113
+ """
114
+ global current_code_identifier
115
+
116
+ while True:
117
+ has_update, new_code, new_identifier = check_for_updates()
118
+
119
+ if has_update:
120
+ # 更新代码并重启服务器
121
+ start_or_restart_server(new_code)
122
+ current_code_identifier = new_identifier
123
+
124
+ elif server_process is None or server_process.poll() is not None:
125
+ # 处理服务器意外退出的情况
126
+ print("检测到服务器未运行或已崩溃,尝试启动...")
127
+ # 需要重新下载一次代码来启动
128
+ try:
129
+ response = requests.get(TXT_URL)
130
+ response.raise_for_status()
131
+ code_to_run = response.text
132
+ start_or_restart_server(code_to_run)
133
+ # 更新标识符
134
+ etag = response.headers.get('ETag')
135
+ if etag:
136
+ current_code_identifier = etag
137
+ else:
138
+ import hashlib
139
+ current_code_identifier = hashlib.sha256(code_to_run.encode('utf-8')).hexdigest()
140
+ except Exception as e:
141
+ print(f"无法下载代码以重启崩溃的服务器: {e}")
142
+
143
+ time.sleep(CHECK_INTERVAL)
144
+
145
+ if __name__ == "__main__":
146
+ try:
147
+ main_watcher()
148
+ except KeyboardInterrupt:
149
+ print("\n检测到手动中断,正在关闭服务器...")
150
+ if server_process and server_process.poll() is None:
151
+ server_process.terminate()
152
+ server_process.wait()
153
+ print("服务器已关闭。再见!")