i0110 commited on
Commit
bc312df
·
verified ·
1 Parent(s): 33ab2dd

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +218 -77
server.js CHANGED
@@ -59,10 +59,49 @@ class SpaceCache {
59
  if (!this.lastUpdate) return true;
60
  return (Date.now() - this.lastUpdate) > (expireMinutes * 60 * 1000);
61
  }
 
 
 
 
 
62
  }
63
 
64
  const spaceCache = new SpaceCache();
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  // 提供静态文件(前端文件)
67
  app.use(express.static(path.join(__dirname, 'public')));
68
 
@@ -133,93 +172,105 @@ const authenticateToken = (req, res, next) => {
133
  // 获取所有 spaces 列表(包括私有)
134
  app.get('/api/proxy/spaces', async (req, res) => {
135
  try {
136
- if (!spaceCache.isExpired()) {
137
- console.log('从缓存获取 Spaces 数据');
138
- // 从缓存返回的数据也需要根据登录状态和 SHOW_PRIVATE 过滤 private 字段
139
- const cachedSpaces = spaceCache.getAll().map(space => {
140
- const { token, ...safeSpace } = space; // 移除 token 字段
141
- return safeSpace;
142
- });
143
-
144
- // 检查是否登录
145
- const authHeader = req.headers['authorization'];
146
- const isAuthenticated = authHeader && authHeader.startsWith('Bearer ') && sessions.get(authHeader.split(' ')[1]);
147
-
148
- // 登录状态下返回所有实例,未登录时根据 SHOW_PRIVATE 决定是否返回 private 实例
149
- if (isAuthenticated) {
150
- console.log('用户已登录,返回所有实例');
151
- return res.json(cachedSpaces);
152
- } else if (SHOW_PRIVATE) {
153
- console.log('用户未登录,但 SHOW_PRIVATE 为 true,返回所有实例');
154
- return res.json(cachedSpaces);
155
  } else {
156
- console.log('用户未登录,SHOW_PRIVATE 为 false,过滤 private 实例');
157
- return res.json(cachedSpaces.filter(space => !space.private));
 
 
 
158
  }
 
 
159
  }
160
 
161
- const allSpaces = [];
162
- for (const username of usernames) {
163
- const token = userTokenMapping[username];
164
- if (!token) {
165
- console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
166
- }
167
-
168
- try {
169
- // 调用 HuggingFace API 获取 Spaces 列表
170
- const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
171
- const response = await axios.get(`https://huggingface.co/api/spaces?author=${username}`, { headers });
172
- const spaces = response.data;
173
- console.log(`获取到 ${spaces.length} 个 Spaces for ${username}`);
174
 
175
- for (const space of spaces) {
176
- try {
177
- // 获取 Space 详细信息
178
- const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${space.id}`, { headers });
179
- const spaceInfo = spaceInfoResponse.data;
180
- const spaceRuntime = spaceInfo.runtime || {};
181
-
182
- allSpaces.push({
183
- repo_id: spaceInfo.id,
184
- name: spaceInfo.cardData?.title || spaceInfo.id.split('/')[1],
185
- owner: spaceInfo.author,
186
- username: username,
187
- url: `https://${spaceInfo.author}-${spaceInfo.id.split('/')[1]}.hf.space`,
188
- status: spaceRuntime.stage || 'unknown',
189
- last_modified: spaceInfo.lastModified || 'unknown',
190
- created_at: spaceInfo.createdAt || 'unknown',
191
- sdk: spaceInfo.sdk || 'unknown',
192
- tags: spaceInfo.tags || [],
193
- private: spaceInfo.private || false,
194
- app_port: spaceInfo.cardData?.app_port || 'unknown'
195
- });
196
- } catch (error) {
197
- console.error(`处理 Space ${space.id} 失败:`, error.message);
 
 
 
198
  }
 
 
199
  }
200
- } catch (error) {
201
- console.error(`获取 Spaces 列表失败 for ${username}:`, error.message);
202
  }
203
- }
204
 
205
- allSpaces.sort((a, b) => a.name.localeCompare(b.name));
206
- spaceCache.updateAll(allSpaces);
207
- console.log(`总共获取到 ${allSpaces.length} 个 Spaces`);
208
 
209
- // 检查是否登录
210
- const authHeader = req.headers['authorization'];
211
- const isAuthenticated = authHeader && authHeader.startsWith('Bearer ') && sessions.get(authHeader.split(' ')[1]);
212
-
213
- // 登录状态下返回所有实例,未登录时根据 SHOW_PRIVATE 决定是否返回 private 实例
214
- if (isAuthenticated) {
215
- console.log('用户已登录,返回所有实例');
216
- res.json(allSpaces);
217
- } else if (SHOW_PRIVATE) {
218
- console.log('用户未登录,但 SHOW_PRIVATE 为 true,返回所有实例');
219
- res.json(allSpaces);
 
 
 
 
220
  } else {
221
- console.log('用户未登录,SHOW_PRIVATE false,过滤 private 实例');
222
- res.json(allSpaces.filter(space => !space.private));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
224
  } catch (error) {
225
  console.error(`代理获取 spaces 列表失败:`, error.message);
@@ -551,10 +602,29 @@ app.get('/api/proxy/live-metrics-stream', (req, res) => {
551
  // 生成唯一的客户端ID
552
  const clientId = crypto.randomBytes(8).toString('hex');
553
 
554
- // 获取查询参数中的实例列表
555
  const instancesParam = req.query.instances || '';
 
556
  const subscribedInstances = instancesParam.split(',').filter(id => id.trim() !== '');
557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  // 注册客户端
559
  metricsManager.registerClient(clientId, res, subscribedInstances);
560
 
@@ -598,6 +668,18 @@ app.post('/api/proxy/update-subscriptions', (req, res) => {
598
  res.json({ success: true, message: '订阅列表已更新' });
599
  });
600
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  // 处理其他请求,重定向到 index.html
602
  app.get('*', (req, res) => {
603
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
@@ -614,8 +696,67 @@ setInterval(() => {
614
  }
615
  }, 60 * 60 * 1000); // 每小时清理一次
616
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  app.listen(port, () => {
618
  console.log(`Server running on port ${port}`);
619
  console.log(`User configurations:`, usernames.map(user => `${user}: ${userTokenMapping[user] ? 'Token Configured' : 'No Token'}`).join(', ') || 'None');
620
  console.log(`Admin login enabled: Username=${ADMIN_USERNAME}, Password=${ADMIN_PASSWORD ? 'Configured' : 'Not Configured'}`);
 
621
  });
 
59
  if (!this.lastUpdate) return true;
60
  return (Date.now() - this.lastUpdate) > (expireMinutes * 60 * 1000);
61
  }
62
+
63
+ invalidate() {
64
+ this.lastUpdate = 0; // 强制使缓存失效
65
+ console.log('缓存被强制失效');
66
+ }
67
  }
68
 
69
  const spaceCache = new SpaceCache();
70
 
71
+ // 用于获取 Spaces 数据的函数,带有重试机制
72
+ async function fetchSpacesWithRetry(username, token, maxRetries = 5, retryDelay = 3000) {
73
+ let retries = 0;
74
+ while (retries < maxRetries) {
75
+ try {
76
+ const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
77
+ const response = await axios.get(`https://huggingface.co/api/spaces?author=${username}`, {
78
+ headers,
79
+ timeout: 10000 // 设置 10 秒超时
80
+ });
81
+ const spaces = response.data;
82
+ console.log(`获取到 ${spaces.length} 个 Spaces for ${username} (尝试 ${retries + 1}/${maxRetries})`);
83
+ return spaces;
84
+ } catch (error) {
85
+ retries++;
86
+ let errorDetail = error.message;
87
+ if (error.response) {
88
+ errorDetail += `, HTTP Status: ${error.response.status}`;
89
+ } else if (error.request) {
90
+ errorDetail += ', No response received (possible network issue)';
91
+ }
92
+ console.error(`获取 Spaces 列表失败 for ${username} (尝试 ${retries}/${maxRetries}): ${errorDetail}`);
93
+ if (retries < maxRetries) {
94
+ console.log(`等待 ${retryDelay/1000} 秒后重试...`);
95
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
96
+ } else {
97
+ console.error(`达到最大重试次数 (${maxRetries}),放弃重试 for ${username}`);
98
+ return [];
99
+ }
100
+ }
101
+ }
102
+ return [];
103
+ }
104
+
105
  // 提供静态文件(前端文件)
106
  app.use(express.static(path.join(__dirname, 'public')));
107
 
 
172
  // 获取所有 spaces 列表(包括私有)
173
  app.get('/api/proxy/spaces', async (req, res) => {
174
  try {
175
+ // 检查是否登录
176
+ let isAuthenticated = false;
177
+ const authHeader = req.headers['authorization'];
178
+ if (authHeader && authHeader.startsWith('Bearer ')) {
179
+ const token = authHeader.split(' ')[1];
180
+ const session = sessions.get(token);
181
+ if (session && session.expiresAt > Date.now()) {
182
+ isAuthenticated = true;
183
+ console.log(`用户已登录,Token: ${token.slice(0, 8)}...`);
 
 
 
 
 
 
 
 
 
 
184
  } else {
185
+ if (session) {
186
+ sessions.delete(token); // 删除过期的 token
187
+ console.log(`Token ${token.slice(0, 8)}... 已过期,拒绝访问`);
188
+ }
189
+ console.log('用户认证失败,无有效 Token');
190
  }
191
+ } else {
192
+ console.log('用户未提供认证令牌');
193
  }
194
 
195
+ // 如果缓存为空或已过期,强制重新获取数据
196
+ const cachedSpaces = spaceCache.getAll();
197
+ if (cachedSpaces.length === 0 || spaceCache.isExpired()) {
198
+ console.log(cachedSpaces.length === 0 ? '缓存为空,强制重新获取数据' : '缓存已过期,重新获取数据');
199
+ const allSpaces = [];
200
+ for (const username of usernames) {
201
+ const token = userTokenMapping[username];
202
+ if (!token) {
203
+ console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
204
+ }
 
 
 
205
 
206
+ try {
207
+ const spaces = await fetchSpacesWithRetry(username, token);
208
+ for (const space of spaces) {
209
+ try {
210
+ const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
211
+ const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${space.id}`, { headers });
212
+ const spaceInfo = spaceInfoResponse.data;
213
+ const spaceRuntime = spaceInfo.runtime || {};
214
+
215
+ allSpaces.push({
216
+ repo_id: spaceInfo.id,
217
+ name: spaceInfo.cardData?.title || spaceInfo.id.split('/')[1],
218
+ owner: spaceInfo.author,
219
+ username: username,
220
+ url: `https://${spaceInfo.author}-${spaceInfo.id.split('/')[1]}.hf.space`,
221
+ status: spaceRuntime.stage || 'unknown',
222
+ last_modified: spaceInfo.lastModified || 'unknown',
223
+ created_at: spaceInfo.createdAt || 'unknown',
224
+ sdk: spaceInfo.sdk || 'unknown',
225
+ tags: spaceInfo.tags || [],
226
+ private: spaceInfo.private || false,
227
+ app_port: spaceInfo.cardData?.app_port || 'unknown'
228
+ });
229
+ } catch (error) {
230
+ console.error(`处理 Space ${space.id} 失败:`, error.message);
231
+ }
232
  }
233
+ } catch (error) {
234
+ console.error(`获取 Spaces 列表失败 for ${username}:`, error.message);
235
  }
 
 
236
  }
 
237
 
238
+ allSpaces.sort((a, b) => a.name.localeCompare(b.name));
239
+ spaceCache.updateAll(allSpaces);
240
+ console.log(`总共获取到 ${allSpaces.length} 个 Spaces`);
241
 
242
+ const safeSpaces = allSpaces.map(space => {
243
+ const { token, ...safeSpace } = space;
244
+ return safeSpace;
245
+ });
246
+
247
+ if (isAuthenticated) {
248
+ console.log('用户已登录,返回所有实例(包括 private)');
249
+ res.json(safeSpaces);
250
+ } else if (SHOW_PRIVATE) {
251
+ console.log('用户未登录,但 SHOW_PRIVATE 为 true,返回所有实例');
252
+ res.json(safeSpaces);
253
+ } else {
254
+ console.log('用户未登录,SHOW_PRIVATE 为 false,过滤 private 实例');
255
+ res.json(safeSpaces.filter(space => !space.private));
256
+ }
257
  } else {
258
+ console.log('从缓存获取 Spaces 数据');
259
+ const safeSpaces = cachedSpaces.map(space => {
260
+ const { token, ...safeSpace } = space;
261
+ return safeSpace;
262
+ });
263
+
264
+ if (isAuthenticated) {
265
+ console.log('用户已登录,返回所有缓存实例(包括 private)');
266
+ return res.json(safeSpaces);
267
+ } else if (SHOW_PRIVATE) {
268
+ console.log('用户未登录,但 SHOW_PRIVATE 为 true,返回所有缓存实例');
269
+ return res.json(safeSpaces);
270
+ } else {
271
+ console.log('用户未登录,SHOW_PRIVATE 为 false,过滤 private 实例');
272
+ return res.json(safeSpaces.filter(space => !space.private));
273
+ }
274
  }
275
  } catch (error) {
276
  console.error(`代理获取 spaces 列表失败:`, error.message);
 
602
  // 生成唯一的客户端ID
603
  const clientId = crypto.randomBytes(8).toString('hex');
604
 
605
+ // 获取查询参数中的实例列表和 token
606
  const instancesParam = req.query.instances || '';
607
+ const token = req.query.token || '';
608
  const subscribedInstances = instancesParam.split(',').filter(id => id.trim() !== '');
609
 
610
+ // 检查登录状态
611
+ let isAuthenticated = false;
612
+ if (token) {
613
+ const session = sessions.get(token);
614
+ if (session && session.expiresAt > Date.now()) {
615
+ isAuthenticated = true;
616
+ console.log(`SSE 用户已登录,Token: ${token.slice(0, 8)}...`);
617
+ } else {
618
+ if (session) {
619
+ sessions.delete(token);
620
+ console.log(`SSE Token ${token.slice(0, 8)}... 已过期,拒绝访问`);
621
+ }
622
+ console.log('SSE 用户认证失败,无有效 Token');
623
+ }
624
+ } else {
625
+ console.log('SSE 用户未提供认证令牌');
626
+ }
627
+
628
  // 注册客户端
629
  metricsManager.registerClient(clientId, res, subscribedInstances);
630
 
 
668
  res.json({ success: true, message: '订阅列表已更新' });
669
  });
670
 
671
+ // 强制刷新缓存的接口
672
+ app.post('/api/proxy/refresh-spaces', async (req, res) => {
673
+ try {
674
+ console.log('前端请求强制刷新 Spaces 缓存');
675
+ spaceCache.invalidate(); // 使缓存失效
676
+ res.json({ message: '缓存已失效,数据将被重新获取' });
677
+ } catch (error) {
678
+ console.error('强制刷新缓存失败:', error.message);
679
+ res.status(500).json({ error: '强制刷新缓存失败', details: error.message });
680
+ }
681
+ });
682
+
683
  // 处理其他请求,重定向到 index.html
684
  app.get('*', (req, res) => {
685
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
 
696
  }
697
  }, 60 * 60 * 1000); // 每小时清理一次
698
 
699
+ // 定时刷新缓存任务
700
+ const REFRESH_INTERVAL = 5 * 60 * 1000; // 每 5 分钟检查一次
701
+ async function refreshSpacesCachePeriodically() {
702
+ console.log('启动定时刷新缓存任务...');
703
+ setInterval(async () => {
704
+ try {
705
+ const cachedSpaces = spaceCache.getAll();
706
+ if (spaceCache.isExpired() || cachedSpaces.length === 0) {
707
+ console.log('定时任务:缓存已过期或为空,重新获取 Spaces 数据');
708
+ const allSpaces = [];
709
+ for (const username of usernames) {
710
+ const token = userTokenMapping[username];
711
+ if (!token) {
712
+ console.warn(`用户 ${username} 没有配置 API Token,将尝试无认证访问公开数据`);
713
+ }
714
+ try {
715
+ const spaces = await fetchSpacesWithRetry(username, token);
716
+ for (const space of spaces) {
717
+ try {
718
+ const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
719
+ const spaceInfoResponse = await axios.get(`https://huggingface.co/api/spaces/${space.id}`, { headers });
720
+ const spaceInfo = spaceInfoResponse.data;
721
+ const spaceRuntime = spaceInfo.runtime || {};
722
+
723
+ allSpaces.push({
724
+ repo_id: spaceInfo.id,
725
+ name: spaceInfo.cardData?.title || spaceInfo.id.split('/')[1],
726
+ owner: spaceInfo.author,
727
+ username: username,
728
+ url: `https://${spaceInfo.author}-${spaceInfo.id.split('/')[1]}.hf.space`,
729
+ status: spaceRuntime.stage || 'unknown',
730
+ last_modified: spaceInfo.lastModified || 'unknown',
731
+ created_at: spaceInfo.createdAt || 'unknown',
732
+ sdk: spaceInfo.sdk || 'unknown',
733
+ tags: spaceInfo.tags || [],
734
+ private: spaceInfo.private || false,
735
+ app_port: spaceInfo.cardData?.app_port || 'unknown'
736
+ });
737
+ } catch (error) {
738
+ console.error(`处理 Space ${space.id} 失败:`, error.message);
739
+ }
740
+ }
741
+ } catch (error) {
742
+ console.error(`获取 Spaces 列表失败 for ${username}:`, error.message);
743
+ }
744
+ }
745
+ allSpaces.sort((a, b) => a.name.localeCompare(b.name));
746
+ spaceCache.updateAll(allSpaces);
747
+ console.log(`定时任务:总共获取到 ${allSpaces.length} 个 Spaces,缓存已更新`);
748
+ } else {
749
+ console.log('定时任务:缓存有效且不为空,无需更新');
750
+ }
751
+ } catch (error) {
752
+ console.error('定时任务:刷新缓存失败:', error.message);
753
+ }
754
+ }, REFRESH_INTERVAL);
755
+ }
756
+
757
  app.listen(port, () => {
758
  console.log(`Server running on port ${port}`);
759
  console.log(`User configurations:`, usernames.map(user => `${user}: ${userTokenMapping[user] ? 'Token Configured' : 'No Token'}`).join(', ') || 'None');
760
  console.log(`Admin login enabled: Username=${ADMIN_USERNAME}, Password=${ADMIN_PASSWORD ? 'Configured' : 'Not Configured'}`);
761
+ refreshSpacesCachePeriodically(); // 启动定时任务
762
  });