CatPtain commited on
Commit
0b2f2ef
·
verified ·
1 Parent(s): 546b5a7

Upload 11 files

Browse files
Files changed (11) hide show
  1. .dockerignore +49 -0
  2. .env.example +26 -0
  3. API_GUIDE.md +212 -0
  4. HF_SPACES_ENV_GUIDE.md +184 -0
  5. LICENSE +27 -0
  6. PRIVATE_SPACE_GUIDE.md +193 -0
  7. README.md +139 -32
  8. package.json +5 -4
  9. performance-config.js +0 -0
  10. server-simple.js +54 -0
  11. server.js +224 -26
.dockerignore ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker ignore file for HF Spaces
2
+ node_modules
3
+ npm-debug.log*
4
+ yarn-debug.log*
5
+ yarn-error.log*
6
+ .git
7
+ .gitignore
8
+ .dockerignore
9
+
10
+ # Development files
11
+ .env*
12
+ *.md
13
+ !README.md
14
+ test-*.html
15
+ test-*.md
16
+ test-screenshot.jpg
17
+
18
+ # Alternative deployment configs
19
+ railway.toml
20
+ render.yaml
21
+ vercel.json
22
+ .railwayignore
23
+ Dockerfile.hf
24
+ Dockerfile.simple
25
+ deploy-hf.sh
26
+ server-simple.js
27
+ performance-config.js
28
+
29
+ # Documentation
30
+ API_GUIDE.md
31
+ DEPLOYMENT_GUIDE.md
32
+ HF_SPACES_ENV_GUIDE.md
33
+ PRIVATE_SPACE_GUIDE.md
34
+ RAILWAY_ALTERNATIVE.md
35
+
36
+ # OS generated files
37
+ .DS_Store
38
+ .DS_Store?
39
+ ._*
40
+ .Spotlight-V100
41
+ .Trashes
42
+ ehthumbs.db
43
+ Thumbs.db
44
+
45
+ # IDE files
46
+ .vscode/
47
+ .idea/
48
+ *.swp
49
+ *.swo
.env.example ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment Variables Configuration for Hugging Face Spaces
2
+ # Copy these to your HF Spaces Settings → Variables
3
+
4
+ # API Key Configuration
5
+ API_KEYS=demo-key-123,production-key-456,client-key-789
6
+ REQUIRE_API_KEY=true
7
+
8
+ # Performance Settings (Optional)
9
+ NODE_ENV=production
10
+ PORT=7860
11
+
12
+ # Security Settings (Optional)
13
+ MAX_CONCURRENT_REQUESTS=3
14
+ CPU_THRESHOLD=95
15
+
16
+ # Instructions for Hugging Face Spaces:
17
+ # 1. Go to your Space Settings
18
+ # 2. Click on "Variables" tab
19
+ # 3. Add each variable above (one per line, without quotes)
20
+ # 4. Space will automatically restart with new configuration
21
+
22
+ # Example API Keys (Generate your own secure keys):
23
+ # - Use random strings of 20+ characters
24
+ # - Include letters, numbers, and symbols
25
+ # - Keep them secure and don't share publicly
26
+ # - Consider using UUID format: 550e8400-e29b-41d4-a716-446655440000
API_GUIDE.md ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # API Authentication & Usage Guide
2
+
3
+ ## 🔐 Setting Up API Keys in Hugging Face Spaces
4
+
5
+ ### Step 1: Generate Secure API Keys
6
+ ```bash
7
+ # Generate random API keys (example methods)
8
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
9
+ # Or use UUID format
10
+ node -e "console.log(require('crypto').randomUUID())"
11
+ ```
12
+
13
+ ### Step 2: Configure in HF Spaces
14
+ 1. Navigate to your Space: `https://huggingface.co/spaces/your-username/space-name`
15
+ 2. Click **"Settings"** tab
16
+ 3. Click **"Variables"** section
17
+ 4. Add these environment variables:
18
+
19
+ | Variable | Value | Description |
20
+ |----------|-------|-------------|
21
+ | `API_KEYS` | `key1,key2,key3` | Comma-separated API keys |
22
+ | `REQUIRE_API_KEY` | `true` | Enable authentication |
23
+
24
+ ### Step 3: Test Authentication
25
+ ```bash
26
+ # Test without API key (should fail)
27
+ curl https://your-space.hf.space/screenshot
28
+
29
+ # Test with API key (should work)
30
+ curl -H "X-API-Key: your-key-here" https://your-space.hf.space/screenshot
31
+ ```
32
+
33
+ ## 📊 Understanding Server Status Responses
34
+
35
+ ### Normal Operation
36
+ ```json
37
+ {
38
+ "status": "running",
39
+ "system": { "cpuUsage": "25%" },
40
+ "queue": { "activeRequests": 1, "queuedRequests": 0 }
41
+ }
42
+ ```
43
+
44
+ ### Server Busy (CPU > 95%)
45
+ ```json
46
+ {
47
+ "status": "busy",
48
+ "error": "Server is currently overloaded",
49
+ "cpuUsage": "96%",
50
+ "queueLength": 3
51
+ }
52
+ ```
53
+
54
+ ### Queue Full Response
55
+ ```json
56
+ {
57
+ "status": "busy",
58
+ "error": "Request queue timeout",
59
+ "queueLength": 10
60
+ }
61
+ ```
62
+
63
+ ## 🔄 Implementing Retry Logic
64
+
65
+ ### JavaScript Retry Pattern
66
+ ```javascript
67
+ async function screenshotWithRetry(url, options = {}, maxRetries = 3) {
68
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
69
+ try {
70
+ const response = await fetch('/screenshot', {
71
+ method: 'POST',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ 'X-API-Key': 'your-api-key'
75
+ },
76
+ body: JSON.stringify({ url, ...options })
77
+ });
78
+
79
+ if (response.ok) {
80
+ return await response.blob();
81
+ }
82
+
83
+ if (response.status === 503) {
84
+ const error = await response.json();
85
+ if (error.status === 'busy') {
86
+ console.log(`Attempt ${attempt}: Server busy, retrying...`);
87
+ await new Promise(resolve => setTimeout(resolve, 2000 * attempt));
88
+ continue;
89
+ }
90
+ }
91
+
92
+ // Other errors, don't retry
93
+ throw new Error(`API Error: ${response.status}`);
94
+
95
+ } catch (error) {
96
+ if (attempt === maxRetries) throw error;
97
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### Python Retry Pattern
104
+ ```python
105
+ import time
106
+ import requests
107
+ from typing import Optional
108
+
109
+ def screenshot_with_retry(url: str, api_key: str, max_retries: int = 3) -> Optional[bytes]:
110
+ for attempt in range(1, max_retries + 1):
111
+ try:
112
+ response = requests.post(
113
+ 'https://your-space.hf.space/screenshot',
114
+ headers={'X-API-Key': api_key, 'Content-Type': 'application/json'},
115
+ json={'url': url},
116
+ timeout=30
117
+ )
118
+
119
+ if response.status_code == 200:
120
+ return response.content
121
+
122
+ if response.status_code == 503:
123
+ error_data = response.json()
124
+ if error_data.get('status') == 'busy':
125
+ print(f"Attempt {attempt}: Server busy (CPU: {error_data.get('cpuUsage', 'N/A')})")
126
+ time.sleep(2 * attempt)
127
+ continue
128
+
129
+ response.raise_for_status()
130
+
131
+ except requests.RequestException as e:
132
+ if attempt == max_retries:
133
+ raise e
134
+ time.sleep(attempt)
135
+
136
+ return None
137
+ ```
138
+
139
+ ## 🚨 Rate Limiting & Best Practices
140
+
141
+ ### Rate Limit Headers
142
+ The API returns rate limiting information:
143
+ ```
144
+ X-RateLimit-Limit: 100
145
+ X-RateLimit-Remaining: 95
146
+ X-RateLimit-Reset: 1640995200
147
+ ```
148
+
149
+ ### Best Practices
150
+ 1. **Implement exponential backoff** for retries
151
+ 2. **Cache results** when possible
152
+ 3. **Use appropriate image quality** (70-80% usually sufficient)
153
+ 4. **Monitor your usage** via `/status` endpoint
154
+ 5. **Handle queue timeouts** gracefully
155
+
156
+ ### Production Usage Example
157
+ ```javascript
158
+ class ScreenshotAPI {
159
+ constructor(apiKey, baseUrl) {
160
+ this.apiKey = apiKey;
161
+ this.baseUrl = baseUrl;
162
+ this.requestCount = 0;
163
+ this.resetTime = 0;
164
+ }
165
+
166
+ async screenshot(url, options = {}) {
167
+ // Check rate limit
168
+ if (this.requestCount >= 95 && Date.now() < this.resetTime) {
169
+ throw new Error('Rate limit approached, waiting...');
170
+ }
171
+
172
+ const response = await this.makeRequest('/screenshot', {
173
+ method: 'POST',
174
+ body: JSON.stringify({ url, ...options })
175
+ });
176
+
177
+ // Update rate limit tracking
178
+ this.requestCount = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
179
+ this.resetTime = parseInt(response.headers.get('X-RateLimit-Reset') || '0') * 1000;
180
+
181
+ return response;
182
+ }
183
+
184
+ async makeRequest(endpoint, options = {}) {
185
+ return fetch(this.baseUrl + endpoint, {
186
+ ...options,
187
+ headers: {
188
+ 'Content-Type': 'application/json',
189
+ 'X-API-Key': this.apiKey,
190
+ ...options.headers
191
+ }
192
+ });
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## 🔧 Troubleshooting
198
+
199
+ ### Common Issues
200
+
201
+ | Error | Cause | Solution |
202
+ |-------|-------|----------|
203
+ | `401 Unauthorized` | Missing API key | Add `X-API-Key` header |
204
+ | `403 Forbidden` | Invalid API key | Check key spelling/validity |
205
+ | `503 Service Unavailable` | Server overloaded | Implement retry with delay |
206
+ | `429 Too Many Requests` | Rate limit exceeded | Wait for reset time |
207
+
208
+ ### Performance Optimization
209
+ - Use smaller dimensions for faster processing
210
+ - Lower quality settings for non-critical uses
211
+ - Batch requests with appropriate delays
212
+ - Monitor CPU usage via `/status`
HF_SPACES_ENV_GUIDE.md ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces 环境变量设置指南
2
+
3
+ ## 🔧 在 HF Spaces 中设置环境变量
4
+
5
+ ### 方法一:通过 Web 界面设置
6
+
7
+ 1. **进入 Space 设置**
8
+ - 访问你的 Space:`https://huggingface.co/spaces/your-username/your-space-name`
9
+ - 点击页面顶部的 **"Settings"** 标签
10
+
11
+ 2. **添加环境变量**
12
+ - 在设置页面中找到 **"Variables"** 或 **"Environment Variables"** 部分
13
+ - 点击 **"Add Variable"** 或 **"New Variable"**
14
+
15
+ 3. **配置认证相关变量**
16
+
17
+ | Variable Name | Value | Description |
18
+ |---------------|-------|-------------|
19
+ | `REQUIRE_API_KEY` | `true` | 启用 API 认证 |
20
+ | `API_KEYS` | `key1,key2,key3` | 自定义 API 密钥列表 |
21
+ | `HF_TOKEN` | `hf_your_token` | Hugging Face access token(可选) |
22
+
23
+ ### 方法二:通过 Space YAML 配置
24
+
25
+ 在你的 Space 根目录创建或编辑 `README.md` 文件,在顶部添加配置:
26
+
27
+ ```yaml
28
+ ---
29
+ title: Page Screenshot API
30
+ emoji: 📸
31
+ colorFrom: blue
32
+ colorTo: green
33
+ sdk: docker
34
+ pinned: false
35
+ license: mit
36
+ variables:
37
+ REQUIRE_API_KEY: "true"
38
+ API_KEYS: "demo-key-123,production-key-456"
39
+ NODE_ENV: "production"
40
+ ---
41
+ ```
42
+
43
+ ## 🔐 推荐的环境变量配置
44
+
45
+ ### 基础配置(公开 Space)
46
+ ```bash
47
+ # 不设置任何环境变量,或设置:
48
+ REQUIRE_API_KEY=false
49
+ ```
50
+
51
+ ### 受保护配置(带自定义 API Key)
52
+ ```bash
53
+ REQUIRE_API_KEY=true
54
+ API_KEYS=your-secret-key-1,your-secret-key-2,backup-key-3
55
+ ```
56
+
57
+ ### 企业配置(多种认证方式)
58
+ ```bash
59
+ REQUIRE_API_KEY=true
60
+ API_KEYS=client-key-001,client-key-002,admin-key-999
61
+ NODE_ENV=production
62
+ MAX_CONCURRENT_REQUESTS=5
63
+ CPU_THRESHOLD=90
64
+ ```
65
+
66
+ ## 🚨 重要安全注意事项
67
+
68
+ ### ✅ 安全的做法
69
+ - **永远不要在代码中硬编码 token**
70
+ - 使用 HF Spaces 的环境变量功能存储敏感信息
71
+ - 为不同的客户端生成不同的 API key
72
+ - 定期轮换 API keys
73
+
74
+ ### ❌ 避免的做法
75
+ - 不要在 GitHub 仓库中提交包含 token 的 `.env` 文件
76
+ - 不要在 README.md 的明文部分暴露真实的 API keys
77
+ - 不要使用简单易猜的 API keys
78
+
79
+ ## 🔄 环境变量的工作原理
80
+
81
+ 在你的 Space 中,这些环境变量会被自动注入到运行环境中:
82
+
83
+ ```javascript
84
+ // server.js 中的代码会自动读取这些环境变量
85
+ const API_KEYS = process.env.API_KEYS ?
86
+ process.env.API_KEYS.split(',').map(key => key.trim()) : [];
87
+ const REQUIRE_API_KEY = process.env.REQUIRE_API_KEY === 'true';
88
+ ```
89
+
90
+ ## 📊 不同认证模式的配置示例
91
+
92
+ ### 模式 1:完全开放(演示用)
93
+ ```bash
94
+ # 不设置任何环境变量
95
+ # 所有人都可以访问 API,有基础速率限制(30次/15分钟)
96
+ ```
97
+
98
+ ### 模式 2:混合模式(推荐)
99
+ ```bash
100
+ REQUIRE_API_KEY=false
101
+ API_KEYS=vip-key-001,premium-key-002
102
+ # 未认证用户:30次/15分钟
103
+ # 认证用户:100次/15分钟
104
+ # HF token 用户:100次/15分钟(自动支持)
105
+ ```
106
+
107
+ ### 模式 3:完全保护
108
+ ```bash
109
+ REQUIRE_API_KEY=true
110
+ API_KEYS=enterprise-key-001,client-key-002
111
+ # 只有持有有效 API key 或 HF token 的用户才能访问
112
+ ```
113
+
114
+ ## 🛠️ 实际部署步骤
115
+
116
+ ### 1. 设置环境变量
117
+ 1. 进入 Space Settings → Variables
118
+ 2. 添加必要的环境变量:
119
+ ```
120
+ REQUIRE_API_KEY: true
121
+ API_KEYS: your-generated-key-1,your-generated-key-2
122
+ ```
123
+
124
+ ### 2. 生成安全的 API Keys
125
+ ```bash
126
+ # 使用 Node.js 生成随机密钥
127
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
128
+
129
+ # 或生成 UUID 格式
130
+ node -e "console.log(require('crypto').randomUUID())"
131
+ ```
132
+
133
+ ### 3. 验证配置
134
+ 部署后访问你的 Space 的健康检查端点:
135
+ ```bash
136
+ curl https://your-space.hf.space/
137
+ ```
138
+
139
+ 响应应该显示认证状态:
140
+ ```json
141
+ {
142
+ "authentication": {
143
+ "required": true,
144
+ "supportedMethods": [
145
+ "X-API-Key: custom-api-key",
146
+ "Authorization: Bearer custom-api-key",
147
+ "Authorization: Bearer hf_token (for private Spaces)"
148
+ ]
149
+ }
150
+ }
151
+ ```
152
+
153
+ ## 🔄 动态配置更新
154
+
155
+ 环境变量更改后:
156
+ 1. Space 会自动重启
157
+ 2. 新的配置会立即生效
158
+ 3. 无需重新部署代码
159
+
160
+ ## 📈 监控和管理
161
+
162
+ ### 检查当前配置
163
+ ```bash
164
+ curl https://your-space.hf.space/status
165
+ ```
166
+
167
+ ### 测试认证
168
+ ```bash
169
+ # 测试自定义 API key
170
+ curl -H "X-API-Key: your-key" https://your-space.hf.space/
171
+
172
+ # 测试 HF token(如果 Space 是私有的)
173
+ curl -H "Authorization: Bearer hf_your_token" https://your-space.hf.space/
174
+ ```
175
+
176
+ ## 💡 最佳实践建议
177
+
178
+ 1. **开发阶段**:使用开放模式进行测试
179
+ 2. **生产部署**:启用认证保护
180
+ 3. **密钥管理**:定期轮换 API keys
181
+ 4. **访问控制**:为不同用户组分配不同的 keys
182
+ 5. **监控使用**:定期检查 API 使用情况
183
+
184
+ 通过以上配置,你可以完全通过 Hugging Face Spaces 的界面管理所有认证相关的环境变量,无需在代码中硬编码任何敏感信息!
LICENSE ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2024. All rights reserved.
4
+
5
+ NOTICE: This software and its documentation are proprietary and confidential
6
+ information. This software is provided under a proprietary license agreement.
7
+
8
+ RESTRICTIONS:
9
+ - You may NOT copy, modify, or distribute this software
10
+ - You may NOT reverse engineer or decompile this software
11
+ - You may NOT use this software for commercial purposes without a license
12
+ - You may NOT redistribute or sublicense this software
13
+
14
+ PERMITTED USES:
15
+ - Personal, non-commercial use only
16
+ - Educational purposes with proper attribution
17
+ - Evaluation for potential commercial licensing
18
+
19
+ COMMERCIAL LICENSING:
20
+ Commercial use requires a separate license agreement. Contact the developer
21
+ for commercial licensing terms and pricing.
22
+
23
+ DISCLAIMER:
24
+ This software is provided "AS IS" without warranty of any kind. The author
25
+ shall not be liable for any damages arising from the use of this software.
26
+
27
+ For licensing inquiries: [Contact Information]
PRIVATE_SPACE_GUIDE.md ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 私有 Hugging Face Spaces 访问指南
2
+
3
+ ## 🔐 私有 Space 认证说明
4
+
5
+ 当你的 Hugging Face Space 设置为 **private** 时,可以通过以下方式使用 access token 调用 API:
6
+
7
+ ### 1. 获取 Hugging Face Access Token
8
+
9
+ 1. 访问 [Hugging Face Settings](https://huggingface.co/settings/tokens)
10
+ 2. 点击 "New token" 创建新的访问令牌
11
+ 3. 选择适当的权限(通常选择 "Read" 权限即可)
12
+ 4. 复制生成的 token(格式为 `hf_xxxxxxxxxx`)
13
+
14
+ ### 2. 设置 Space 为私有
15
+
16
+ 1. 进入你的 Space 设置页面
17
+ 2. 在 "Visibility" 部分选择 **Private**
18
+ 3. 保存设置
19
+
20
+ ### 3. 使用 Access Token 调用 API
21
+
22
+ #### 方法一:Authorization Bearer Header
23
+ ```bash
24
+ curl -X POST https://your-username-space-name.hf.space/screenshot \
25
+ -H "Content-Type: application/json" \
26
+ -H "Authorization: Bearer hf_your_token_here" \
27
+ -d '{"url": "https://example.com", "width": 1280, "height": 720}' \
28
+ --output screenshot.jpg
29
+ ```
30
+
31
+ #### 方法二:JavaScript 调用
32
+ ```javascript
33
+ const response = await fetch('https://your-username-space-name.hf.space/screenshot', {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'Authorization': 'Bearer hf_your_token_here'
38
+ },
39
+ body: JSON.stringify({
40
+ url: 'https://example.com',
41
+ width: 1280,
42
+ height: 720,
43
+ quality: 80
44
+ })
45
+ });
46
+
47
+ if (response.ok) {
48
+ const imageBlob = await response.blob();
49
+ // 处理图片数据
50
+ } else {
51
+ const error = await response.json();
52
+ console.error('Error:', error);
53
+ }
54
+ ```
55
+
56
+ #### 方法三:Python 调用
57
+ ```python
58
+ import requests
59
+
60
+ url = "https://your-username-space-name.hf.space/screenshot"
61
+ headers = {
62
+ "Content-Type": "application/json",
63
+ "Authorization": "Bearer hf_your_token_here"
64
+ }
65
+ data = {
66
+ "url": "https://example.com",
67
+ "width": 1280,
68
+ "height": 720,
69
+ "quality": 80
70
+ }
71
+
72
+ response = requests.post(url, headers=headers, json=data)
73
+
74
+ if response.status_code == 200:
75
+ with open("screenshot.jpg", "wb") as f:
76
+ f.write(response.content)
77
+ print("Screenshot saved successfully")
78
+ else:
79
+ print("Error:", response.json())
80
+ ```
81
+
82
+ ## 🔄 认证方式优先级
83
+
84
+ 本 API 支持多种认证方式,按以下优先级处理:
85
+
86
+ 1. **Hugging Face Token** (`Authorization: Bearer hf_xxx`) - 最高优先级
87
+ 2. **自定义 Bearer Token** (`Authorization: Bearer xxx`)
88
+ 3. **API Key Header** (`X-API-Key: xxx`)
89
+
90
+ ## 🚨 Token 安全注意事项
91
+
92
+ ### ✅ 安全实践
93
+ - 定期轮换 access token
94
+ - 不要在客户端代码中硬编码 token
95
+ - 使用环境变量存储 token
96
+ - 为不同用途创建不同的 token
97
+
98
+ ### ❌ 避免的做法
99
+ - 不要在 GitHub 等公开仓库中提交 token
100
+ - 不要在浏览器控制台中暴露 token
101
+ - 不要与他人分享你的 personal access token
102
+
103
+ ## 📊 私有 Space 的优势
104
+
105
+ ### 🔒 访问控制
106
+ - 只有拥有 token 的用户才能访问
107
+ - 可以精确控制 API 使用权限
108
+ - 保护敏感的截图服务不被滥用
109
+
110
+ ### 📈 性能优势
111
+ - 更高的速率限制(100 次/15分钟 vs 30 次/15分钟)
112
+ - 优先级处理队列
113
+ - 更稳定的服务可用性
114
+
115
+ ### 💰 成本控制
116
+ - 避免不必要的 API 调用
117
+ - 防止恶意使用导致的资源消耗
118
+ - 更好的使用量监控
119
+
120
+ ## 🛠️ 环境变量配置
121
+
122
+ 如果你想同时支持自定义 API key 和 HF token,可以在 Space 设置中添加:
123
+
124
+ ```bash
125
+ # 启用认证(可选)
126
+ REQUIRE_API_KEY=true
127
+
128
+ # 自定义 API keys(可选)
129
+ API_KEYS=your-custom-key-1,your-custom-key-2,your-custom-key-3
130
+ ```
131
+
132
+ **注意:** 即使设置了 `REQUIRE_API_KEY=false`,HF access token 仍然可以正常使用。
133
+
134
+ ## 🔍 调试和监控
135
+
136
+ ### 检查认证状态
137
+ ```bash
138
+ curl https://your-username-space-name.hf.space/ \
139
+ -H "Authorization: Bearer hf_your_token_here"
140
+ ```
141
+
142
+ 响应会显示支持的认证方法:
143
+ ```json
144
+ {
145
+ "message": "Page Screenshot API - Hugging Face Spaces",
146
+ "version": "1.2.0",
147
+ "authentication": {
148
+ "required": true,
149
+ "supportedMethods": [
150
+ "X-API-Key: custom-api-key",
151
+ "Authorization: Bearer custom-api-key",
152
+ "Authorization: Bearer hf_token (for private Spaces)"
153
+ ]
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### 服务器状态监控
159
+ ```bash
160
+ curl https://your-username-space-name.hf.space/status \
161
+ -H "Authorization: Bearer hf_your_token_here"
162
+ ```
163
+
164
+ ## 🆘 故障排除
165
+
166
+ ### 401 Unauthorized
167
+ - 检查 token 是否正确
168
+ - 确认 token 以 `hf_` 开头
169
+ - 验证 token 长度至少 20 个字符
170
+
171
+ ### 403 Forbidden
172
+ - Token 格式不正确
173
+ - Token 可能已过期或被撤销
174
+ - 检查 Space 的访问权限设置
175
+
176
+ ### 示例错误响应
177
+ ```json
178
+ {
179
+ "error": "Invalid Hugging Face token format",
180
+ "message": "Hugging Face tokens should start with \"hf_\" and be at least 20 characters long",
181
+ "example": "Authorization: Bearer hf_abcdefghijklmnopqrstuvwxyz"
182
+ }
183
+ ```
184
+
185
+ ## 📝 最佳实践总结
186
+
187
+ 1. **部署时设置为私有:** 在生产环境中始终使用私有 Space
188
+ 2. **使用环境变量:** 通过环境变量管理敏感配置
189
+ 3. **实施监控:** 定期检查 API 使用情况和性能
190
+ 4. **Token 管理:** 定期轮换 access token,保持安全性
191
+ 5. **错误处理:** 在客户端代码中正确处理认证错误
192
+
193
+ 通过以上配置,你的私有 Hugging Face Space 将支持安全的 token 认证访问!
README.md CHANGED
@@ -8,49 +8,156 @@ pinned: false
8
  license: mit
9
  ---
10
 
11
- # Page Screenshot API
12
 
13
- A web service that captures screenshots of web pages using Puppeteer, optimized for Hugging Face Spaces.
14
 
15
- ## Features
16
- - Web page screenshot capture
17
- - Customizable dimensions (width/height)
18
- - Adjustable image quality
19
- - Rate limiting for API protection
20
- - CORS enabled for cross-origin requests
21
- - Interactive demo interface
22
 
23
- ## Live Demo
24
- Visit `/demo` to try the interactive screenshot tool!
 
25
 
26
- ## API Usage
27
 
28
- ### POST /screenshot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  ```json
30
  {
31
- "url": "https://example.com",
32
- "width": 1280,
33
- "height": 720,
34
- "quality": 75
35
  }
36
  ```
37
 
38
- **HF Spaces Limits:**
39
- - Width: 100-1600px
40
- - Height: 100-1200px
41
- - Timeout: 15 seconds
42
- - Rate limit: 30 requests/15min
43
 
44
- ### GET /
45
- Health check endpoint
 
 
 
 
 
46
 
47
- ### GET /demo
48
- Interactive demo interface
49
 
50
- ## Example Usage
51
  ```bash
52
- curl -X POST /screenshot \
53
- -H "Content-Type: application/json" \
54
- -d '{"url": "https://google.com", "width": 1280, "height": 720}' \
55
- --output screenshot.jpg
56
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  license: mit
9
  ---
10
 
11
+ # 📸 Page Screenshot API
12
 
13
+ Professional web page screenshot service built with Node.js and Puppeteer, optimized for Hugging Face Spaces.
14
 
15
+ ## 🚀 Live Demo
 
 
 
 
 
 
16
 
17
+ - **API Endpoint**: `https://your-username-page-shot.hf.space/screenshot`
18
+ - **Demo Interface**: `https://your-username-page-shot.hf.space/demo`
19
+ - **Health Check**: `https://your-username-page-shot.hf.space/`
20
 
21
+ ## 🔐 Access Control
22
 
23
+ This API uses **Hugging Face system-level authentication**:
24
+
25
+ ### Public Spaces
26
+ - Open access for all users
27
+ - Rate limited to 100 requests per 15 minutes
28
+ - Perfect for demos and testing
29
+
30
+ ### Private Spaces
31
+ - Requires valid Hugging Face access token
32
+ - Access controlled by HF platform automatically
33
+ - Higher security for production use
34
+
35
+ ## 📖 API Usage
36
+
37
+ ### Public Space Access
38
+ ```bash
39
+ curl -X POST https://your-space.hf.space/screenshot \
40
+ -H "Content-Type: application/json" \
41
+ -d '{"url": "https://example.com", "width": 1280, "height": 720}' \
42
+ --output screenshot.jpg
43
+ ```
44
+
45
+ ### Private Space Access
46
+ ```bash
47
+ curl -X POST https://your-space.hf.space/screenshot \
48
+ -H "Content-Type: application/json" \
49
+ -H "Authorization: Bearer hf_your_token_here" \
50
+ -d '{"url": "https://example.com", "width": 1280, "height": 720}' \
51
+ --output screenshot.jpg
52
+ ```
53
+
54
+ ### JavaScript Example
55
+ ```javascript
56
+ const response = await fetch('https://your-space.hf.space/screenshot', {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ // Add this line only for private spaces:
61
+ // 'Authorization': 'Bearer hf_your_token_here'
62
+ },
63
+ body: JSON.stringify({
64
+ url: 'https://example.com',
65
+ width: 1280,
66
+ height: 720,
67
+ quality: 80
68
+ })
69
+ });
70
+
71
+ const imageBlob = await response.blob();
72
+ ```
73
+
74
+ ## 📊 Parameters
75
+
76
+ | Parameter | Type | Default | Range | Description |
77
+ |-----------|------|---------|-------|-------------|
78
+ | `url` | string | **required** | - | Target webpage URL |
79
+ | `width` | number | 1280 | 100-1600 | Screenshot width in pixels |
80
+ | `height` | number | 720 | 100-1200 | Screenshot height in pixels |
81
+ | `quality` | number | 75 | 10-100 | JPEG quality percentage |
82
+
83
+ ## 🔄 Response Examples
84
+
85
+ ### Success Response
86
+ ```
87
+ HTTP/1.1 200 OK
88
+ Content-Type: image/jpeg
89
+ Content-Length: 245760
90
+
91
+ [Binary JPEG data]
92
+ ```
93
+
94
+ ### Error Response
95
+ ```json
96
+ {
97
+ "error": "Failed to capture screenshot",
98
+ "message": "Navigation timeout exceeded"
99
+ }
100
+ ```
101
+
102
+ ### Server Busy Response
103
  ```json
104
  {
105
+ "status": "busy",
106
+ "error": "Server is currently overloaded",
107
+ "cpuUsage": "96%",
108
+ "queueLength": 3
109
  }
110
  ```
111
 
112
+ ## 🛠️ Features
 
 
 
 
113
 
114
+ - **Queue Management** - Handles concurrent requests efficiently
115
+ - **CPU Monitoring** - Automatic load balancing
116
+ - ✅ **Rate Limiting** - Prevents abuse
117
+ - ✅ **HF Authentication** - Integrated with HuggingFace platform
118
+ - ✅ **Error Handling** - Comprehensive error responses
119
+ - ✅ **Demo Interface** - Built-in testing UI
120
+ - ✅ **Health Monitoring** - Status endpoint for monitoring
121
 
122
+ ## 📈 Monitoring
 
123
 
124
+ ### Health Check
125
  ```bash
126
+ curl https://your-space.hf.space/
127
+ ```
128
+
129
+ ### Server Status
130
+ ```bash
131
+ curl https://your-space.hf.space/status
132
+ ```
133
+
134
+ ## 🚨 Rate Limits
135
+
136
+ | Space Type | Requests | Window |
137
+ |------------|----------|--------|
138
+ | Public | 100 | 15 minutes |
139
+ | Private | 100 | 15 minutes |
140
+
141
+ ## 🔧 Deployment
142
+
143
+ This application is optimized for Hugging Face Spaces with:
144
+ - Docker-based deployment
145
+ - Automatic dependency management
146
+ - Resource-efficient Chrome configuration
147
+ - Built-in security features
148
+ - HuggingFace platform integration
149
+
150
+ ## 🔒 Security Notes
151
+
152
+ - **Private Spaces**: Access automatically controlled by HF platform
153
+ - **Public Spaces**: Open access with rate limiting
154
+ - **No API keys needed**: Authentication handled by HuggingFace
155
+ - **Secure by design**: Follows HF Spaces best practices
156
+
157
+ ## 📄 License
158
+
159
+ Proprietary - Commercial use requires license. See [LICENSE](LICENSE) for details.
160
+
161
+ ## 🤝 Support
162
+
163
+ For technical support or questions, please contact the administrator.
package.json CHANGED
@@ -1,16 +1,17 @@
1
  {
2
  "name": "page-screenshot-api",
3
- "version": "1.0.0",
4
- "description": "Web page screenshot API service",
5
  "main": "server.js",
6
  "scripts": {
7
  "start": "node server.js",
8
  "dev": "nodemon server.js",
9
  "build": "echo 'No build step required'"
10
  },
11
- "keywords": ["screenshot", "api", "puppeteer"],
12
  "author": "",
13
- "license": "MIT",
 
14
  "dependencies": {
15
  "express": "^4.18.2",
16
  "puppeteer": "^21.5.2",
 
1
  {
2
  "name": "page-screenshot-api",
3
+ "version": "1.3.0",
4
+ "description": "Professional web page screenshot API service for Hugging Face Spaces",
5
  "main": "server.js",
6
  "scripts": {
7
  "start": "node server.js",
8
  "dev": "nodemon server.js",
9
  "build": "echo 'No build step required'"
10
  },
11
+ "keywords": ["screenshot", "api", "puppeteer", "huggingface", "spaces"],
12
  "author": "",
13
+ "license": "UNLICENSED",
14
+ "private": true,
15
  "dependencies": {
16
  "express": "^4.18.2",
17
  "puppeteer": "^21.5.2",
performance-config.js ADDED
File without changes
server-simple.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const puppeteer = require('puppeteer');
3
+ const cors = require('cors');
4
+
5
+ const app = express();
6
+ const PORT = process.env.PORT || 7860;
7
+
8
+ app.use(cors());
9
+ app.use(express.json());
10
+
11
+ // 健康检查
12
+ app.get('/', (req, res) => {
13
+ res.json({ message: 'Page Screenshot API', status: 'running' });
14
+ });
15
+
16
+ // 截图端点
17
+ app.post('/screenshot', async (req, res) => {
18
+ const { url, width = 1280, height = 720, quality = 75 } = req.body;
19
+
20
+ if (!url) {
21
+ return res.status(400).json({ error: 'URL is required' });
22
+ }
23
+
24
+ let browser;
25
+ try {
26
+ browser = await puppeteer.launch({
27
+ headless: 'new',
28
+ executablePath: '/usr/bin/google-chrome-stable',
29
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
30
+ });
31
+
32
+ const page = await browser.newPage();
33
+ await page.setViewport({ width: parseInt(width), height: parseInt(height) });
34
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
35
+
36
+ const screenshot = await page.screenshot({
37
+ type: 'jpeg',
38
+ quality: parseInt(quality),
39
+ fullPage: false
40
+ });
41
+
42
+ res.set('Content-Type', 'image/jpeg');
43
+ res.send(screenshot);
44
+
45
+ } catch (error) {
46
+ res.status(500).json({ error: 'Screenshot failed', message: error.message });
47
+ } finally {
48
+ if (browser) await browser.close();
49
+ }
50
+ });
51
+
52
+ app.listen(PORT, () => {
53
+ console.log(`Screenshot API running on port ${PORT}`);
54
+ });
server.js CHANGED
@@ -3,44 +3,192 @@ const puppeteer = require('puppeteer');
3
  const cors = require('cors');
4
  const helmet = require('helmet');
5
  const rateLimit = require('express-rate-limit');
 
6
 
7
  const app = express();
8
  const PORT = process.env.PORT || 7860;
9
 
10
- // 中间件配置 - HF Spaces 优化
 
 
 
 
 
 
 
 
 
11
  app.use(helmet({
12
  contentSecurityPolicy: false
13
  }));
14
  app.use(cors());
15
  app.use(express.json({ limit: '10mb' }));
16
 
17
- // 速率限制
18
  const limiter = rateLimit({
19
  windowMs: 15 * 60 * 1000,
20
- max: 30,
21
  message: {
22
  error: 'Too many requests, please try again later.'
 
 
 
23
  }
24
  });
25
  app.use('/screenshot', limiter);
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  // 健康检查端点
28
  app.get('/', (req, res) => {
29
  res.json({
30
  message: 'Page Screenshot API - Hugging Face Spaces',
31
- version: '1.0.0',
32
  status: 'running',
33
  platform: 'HuggingFace Spaces',
 
 
 
 
34
  endpoints: {
35
  screenshot: 'POST /screenshot',
36
  demo: 'GET /demo',
37
- health: 'GET /'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
  });
40
  });
41
 
42
- // 截图API端点
43
- app.post('/screenshot', async (req, res) => {
44
  const { url, width = 1280, height = 720, quality = 75 } = req.body;
45
 
46
  if (!url) {
@@ -71,9 +219,8 @@ app.post('/screenshot', async (req, res) => {
71
 
72
  let browser;
73
  try {
74
- console.log('Launching browser...');
75
 
76
- // HF Spaces 优化配置 - 明确指定 Chrome 路径
77
  const browserOptions = {
78
  headless: 'new',
79
  executablePath: '/usr/bin/google-chrome-stable',
@@ -93,7 +240,6 @@ app.post('/screenshot', async (req, res) => {
93
  };
94
 
95
  browser = await puppeteer.launch(browserOptions);
96
-
97
  const page = await browser.newPage();
98
 
99
  await page.setViewport({
@@ -115,8 +261,6 @@ app.post('/screenshot', async (req, res) => {
115
  }
116
  });
117
 
118
- console.log(`Navigating to: ${url}`);
119
-
120
  await page.goto(url, {
121
  waitUntil: 'domcontentloaded',
122
  timeout: 15000
@@ -124,15 +268,13 @@ app.post('/screenshot', async (req, res) => {
124
 
125
  await page.waitForTimeout(1000);
126
 
127
- console.log('Taking screenshot...');
128
-
129
  const screenshot = await page.screenshot({
130
  type: 'jpeg',
131
  quality: Math.max(10, Math.min(100, parseInt(quality))),
132
  fullPage: false
133
  });
134
 
135
- console.log(`Screenshot taken: ${screenshot.length} bytes`);
136
 
137
  res.set({
138
  'Content-Type': 'image/jpeg',
@@ -163,7 +305,6 @@ app.post('/screenshot', async (req, res) => {
163
  if (browser) {
164
  try {
165
  await browser.close();
166
- console.log('Browser closed');
167
  } catch (closeError) {
168
  console.error('Error closing browser:', closeError.message);
169
  }
@@ -171,9 +312,9 @@ app.post('/screenshot', async (req, res) => {
171
  }
172
  });
173
 
174
- // HF Spaces 演示界面
175
  app.get('/demo', (req, res) => {
176
- res.send(`
177
  <!DOCTYPE html>
178
  <html>
179
  <head>
@@ -200,7 +341,7 @@ app.get('/demo', (req, res) => {
200
  background: linear-gradient(135deg, #007bff, #0056b3);
201
  color: white; border: none; padding: 14px 28px;
202
  border-radius: 6px; cursor: pointer; font-size: 16px; font-weight: 600;
203
- transition: transform 0.2s;
204
  }
205
  button:hover { transform: translateY(-1px); }
206
  button:disabled { background: #6c757d; cursor: not-allowed; transform: none; }
@@ -214,12 +355,23 @@ app.get('/demo', (req, res) => {
214
  padding: 8px 12px; margin: 5px; border-radius: 4px; cursor: pointer; font-size: 14px;
215
  }
216
  .example-btn:hover { background: #dee2e6; }
 
 
217
  </style>
218
  </head>
219
  <body>
220
  <div class="container">
221
- <h1>📸 Page Screenshot API</h1>
222
- <p>Enter a URL to capture a screenshot. Optimized for Hugging Face Spaces.</p>
 
 
 
 
 
 
 
 
 
223
 
224
  <div class="examples">
225
  <strong>Try these examples:</strong><br>
@@ -250,11 +402,27 @@ app.get('/demo', (req, res) => {
250
  </div>
251
 
252
  <button onclick="takeScreenshot()" id="captureBtn">Take Screenshot</button>
 
253
 
254
  <div id="result"></div>
255
  </div>
256
 
257
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  function setExample(url) {
259
  document.getElementById('url').value = url;
260
  }
@@ -279,7 +447,7 @@ app.get('/demo', (req, res) => {
279
  const response = await fetch('/screenshot', {
280
  method: 'POST',
281
  headers: {
282
- 'Content-Type': 'application/json',
283
  },
284
  body: JSON.stringify({ url, width, height, quality })
285
  });
@@ -288,6 +456,7 @@ app.get('/demo', (req, res) => {
288
  const blob = await response.blob();
289
  const imageUrl = URL.createObjectURL(blob);
290
  const size = (blob.size / 1024).toFixed(1);
 
291
  document.getElementById('result').innerHTML =
292
  '<div class="success"><h3>Screenshot Result:</h3>' +
293
  '<p>Size: ' + size + ' KB | Dimensions: ' + width + 'x' + height + '</p>' +
@@ -295,9 +464,19 @@ app.get('/demo', (req, res) => {
295
  '<a href="' + imageUrl + '" download="screenshot.jpg" style="background: #28a745; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Download Image</a></div>';
296
  } else {
297
  const error = await response.json();
298
- document.getElementById('result').innerHTML =
299
- '<div class="error"><strong>Error:</strong> ' + error.error +
300
- (error.suggestion ? '<br><strong>Suggestion:</strong> ' + error.suggestion : '') + '</div>';
 
 
 
 
 
 
 
 
 
 
301
  }
302
  } catch (error) {
303
  document.getElementById('result').innerHTML =
@@ -308,6 +487,12 @@ app.get('/demo', (req, res) => {
308
  }
309
  }
310
 
 
 
 
 
 
 
311
  // Enter key support
312
  document.getElementById('url').addEventListener('keypress', function(e) {
313
  if (e.key === 'Enter') {
@@ -317,7 +502,9 @@ app.get('/demo', (req, res) => {
317
  </script>
318
  </body>
319
  </html>
320
- `);
 
 
321
  });
322
 
323
  // 错误处理中间件
@@ -335,6 +522,17 @@ app.use((req, res) => {
335
  });
336
  });
337
 
 
 
 
 
 
 
 
 
 
338
  app.listen(PORT, '0.0.0.0', () => {
339
  console.log(`Screenshot API server running on port ${PORT} for Hugging Face Spaces`);
 
 
340
  });
 
3
  const cors = require('cors');
4
  const helmet = require('helmet');
5
  const rateLimit = require('express-rate-limit');
6
+ const os = require('os');
7
 
8
  const app = express();
9
  const PORT = process.env.PORT || 7860;
10
 
11
+ // 请求队列管理
12
+ const requestQueue = [];
13
+ let activeRequests = 0;
14
+ const MAX_CONCURRENT_REQUESTS = 3;
15
+
16
+ // CPU 监控
17
+ let lastCpuUsage = 0;
18
+ let cpuMonitorInterval;
19
+
20
+ // 中间件配置
21
  app.use(helmet({
22
  contentSecurityPolicy: false
23
  }));
24
  app.use(cors());
25
  app.use(express.json({ limit: '10mb' }));
26
 
27
+ // 简化的速率限制 - 所有用户统一限制
28
  const limiter = rateLimit({
29
  windowMs: 15 * 60 * 1000,
30
+ max: 100, // 统一的较高限制
31
  message: {
32
  error: 'Too many requests, please try again later.'
33
+ },
34
+ keyGenerator: (req) => {
35
+ return req.ip;
36
  }
37
  });
38
  app.use('/screenshot', limiter);
39
 
40
+ // CPU 监控功能
41
+ function startCpuMonitoring() {
42
+ const cpus = os.cpus();
43
+
44
+ cpuMonitorInterval = setInterval(() => {
45
+ const cpus = os.cpus();
46
+ let totalIdle = 0;
47
+ let totalTick = 0;
48
+
49
+ cpus.forEach(cpu => {
50
+ for (const type in cpu.times) {
51
+ totalTick += cpu.times[type];
52
+ }
53
+ totalIdle += cpu.times.idle;
54
+ });
55
+
56
+ const idle = totalIdle / cpus.length;
57
+ const total = totalTick / cpus.length;
58
+ const usage = 100 - ~~(100 * idle / total);
59
+
60
+ lastCpuUsage = usage;
61
+ }, 2000);
62
+ }
63
+
64
+ // 请求队列处理
65
+ function processQueue() {
66
+ if (requestQueue.length === 0 || activeRequests >= MAX_CONCURRENT_REQUESTS) {
67
+ return;
68
+ }
69
+
70
+ if (lastCpuUsage >= 95) {
71
+ return;
72
+ }
73
+
74
+ const { req, res, next } = requestQueue.shift();
75
+ activeRequests++;
76
+
77
+ const originalSend = res.send;
78
+ res.send = function(...args) {
79
+ activeRequests--;
80
+ setImmediate(processQueue);
81
+ return originalSend.apply(this, args);
82
+ };
83
+
84
+ next();
85
+ }
86
+
87
+ // 队列中间件
88
+ const queueMiddleware = (req, res, next) => {
89
+ if (lastCpuUsage >= 95) {
90
+ return res.status(503).json({
91
+ status: 'busy',
92
+ error: 'Server is currently overloaded',
93
+ message: 'CPU usage is too high. Please try again later.',
94
+ cpuUsage: `${lastCpuUsage}%`,
95
+ queueLength: requestQueue.length
96
+ });
97
+ }
98
+
99
+ if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
100
+ requestQueue.push({ req, res, next });
101
+
102
+ const timeout = setTimeout(() => {
103
+ const index = requestQueue.findIndex(item => item.res === res);
104
+ if (index !== -1) {
105
+ requestQueue.splice(index, 1);
106
+ res.status(503).json({
107
+ status: 'busy',
108
+ error: 'Request queue timeout',
109
+ message: 'Request was queued too long and timed out.',
110
+ queueLength: requestQueue.length
111
+ });
112
+ }
113
+ }, 30000);
114
+
115
+ req.on('close', () => {
116
+ clearTimeout(timeout);
117
+ const index = requestQueue.findIndex(item => item.res === res);
118
+ if (index !== -1) {
119
+ requestQueue.splice(index, 1);
120
+ }
121
+ });
122
+
123
+ return;
124
+ }
125
+
126
+ activeRequests++;
127
+
128
+ const originalSend = res.send;
129
+ res.send = function(...args) {
130
+ activeRequests--;
131
+ setImmediate(processQueue);
132
+ return originalSend.apply(this, args);
133
+ };
134
+
135
+ next();
136
+ };
137
+
138
+ // 启动 CPU 监控
139
+ startCpuMonitoring();
140
+
141
  // 健康检查端点
142
  app.get('/', (req, res) => {
143
  res.json({
144
  message: 'Page Screenshot API - Hugging Face Spaces',
145
+ version: '1.3.0',
146
  status: 'running',
147
  platform: 'HuggingFace Spaces',
148
+ authentication: {
149
+ type: 'HuggingFace System Level',
150
+ note: 'Authentication is handled by HuggingFace platform for private Spaces'
151
+ },
152
  endpoints: {
153
  screenshot: 'POST /screenshot',
154
  demo: 'GET /demo',
155
+ health: 'GET /',
156
+ status: 'GET /status'
157
+ },
158
+ license: 'Proprietary - Commercial use requires license'
159
+ });
160
+ });
161
+
162
+ // 服务器状态端点
163
+ app.get('/status', (req, res) => {
164
+ const memUsage = process.memoryUsage();
165
+
166
+ res.json({
167
+ status: 'running',
168
+ timestamp: new Date().toISOString(),
169
+ system: {
170
+ cpuUsage: `${lastCpuUsage}%`,
171
+ memoryUsage: {
172
+ rss: `${Math.round(memUsage.rss / 1024 / 1024)}MB`,
173
+ heapUsed: `${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`,
174
+ heapTotal: `${Math.round(memUsage.heapTotal / 1024 / 1024)}MB`
175
+ },
176
+ uptime: `${Math.floor(process.uptime())}s`
177
+ },
178
+ queue: {
179
+ activeRequests,
180
+ queuedRequests: requestQueue.length,
181
+ maxConcurrent: MAX_CONCURRENT_REQUESTS
182
+ },
183
+ authentication: {
184
+ type: 'HuggingFace System Level',
185
+ note: 'No application-level authentication required'
186
  }
187
  });
188
  });
189
 
190
+ // 截图API端点 - 移除所有认证检查
191
+ app.post('/screenshot', queueMiddleware, async (req, res) => {
192
  const { url, width = 1280, height = 720, quality = 75 } = req.body;
193
 
194
  if (!url) {
 
219
 
220
  let browser;
221
  try {
222
+ console.log(`Taking screenshot of: ${url}`);
223
 
 
224
  const browserOptions = {
225
  headless: 'new',
226
  executablePath: '/usr/bin/google-chrome-stable',
 
240
  };
241
 
242
  browser = await puppeteer.launch(browserOptions);
 
243
  const page = await browser.newPage();
244
 
245
  await page.setViewport({
 
261
  }
262
  });
263
 
 
 
264
  await page.goto(url, {
265
  waitUntil: 'domcontentloaded',
266
  timeout: 15000
 
268
 
269
  await page.waitForTimeout(1000);
270
 
 
 
271
  const screenshot = await page.screenshot({
272
  type: 'jpeg',
273
  quality: Math.max(10, Math.min(100, parseInt(quality))),
274
  fullPage: false
275
  });
276
 
277
+ console.log(`Screenshot completed: ${screenshot.length} bytes`);
278
 
279
  res.set({
280
  'Content-Type': 'image/jpeg',
 
305
  if (browser) {
306
  try {
307
  await browser.close();
 
308
  } catch (closeError) {
309
  console.error('Error closing browser:', closeError.message);
310
  }
 
312
  }
313
  });
314
 
315
+ // 简化的演示界面
316
  app.get('/demo', (req, res) => {
317
+ const demoHtml = `
318
  <!DOCTYPE html>
319
  <html>
320
  <head>
 
341
  background: linear-gradient(135deg, #007bff, #0056b3);
342
  color: white; border: none; padding: 14px 28px;
343
  border-radius: 6px; cursor: pointer; font-size: 16px; font-weight: 600;
344
+ transition: transform 0.2s; margin: 5px;
345
  }
346
  button:hover { transform: translateY(-1px); }
347
  button:disabled { background: #6c757d; cursor: not-allowed; transform: none; }
 
355
  padding: 8px 12px; margin: 5px; border-radius: 4px; cursor: pointer; font-size: 14px;
356
  }
357
  .example-btn:hover { background: #dee2e6; }
358
+ .status-info { background: #d1ecf1; padding: 15px; border-radius: 6px; margin-bottom: 20px; border-left: 4px solid #17a2b8; }
359
+ .hf-info { background: #d4edda; padding: 15px; border-radius: 6px; margin-bottom: 20px; border-left: 4px solid #28a745; }
360
  </style>
361
  </head>
362
  <body>
363
  <div class="container">
364
+ <h1>📸 Page Screenshot API Demo</h1>
365
+ <p>Professional screenshot service powered by Hugging Face Spaces.</p>
366
+
367
+ <div class="hf-info">
368
+ <strong>🔐 Access Control</strong><br>
369
+ This service uses Hugging Face system-level authentication. If this Space is private, access is automatically controlled by HF platform.
370
+ </div>
371
+
372
+ <div class="status-info">
373
+ <strong>📊 Server Status:</strong> <span id="serverStatus">Loading...</span>
374
+ </div>
375
 
376
  <div class="examples">
377
  <strong>Try these examples:</strong><br>
 
402
  </div>
403
 
404
  <button onclick="takeScreenshot()" id="captureBtn">Take Screenshot</button>
405
+ <button onclick="checkStatus()" id="statusBtn">Check Status</button>
406
 
407
  <div id="result"></div>
408
  </div>
409
 
410
  <script>
411
+ async function checkStatus() {
412
+ try {
413
+ const response = await fetch('/status');
414
+ const data = await response.json();
415
+ document.getElementById('serverStatus').innerHTML =
416
+ \`CPU: \${data.system.cpuUsage} | Queue: \${data.queue.queuedRequests} | Active: \${data.queue.activeRequests}\`;
417
+
418
+ document.getElementById('result').innerHTML =
419
+ '<h3>Server Status:</h3><pre>' + JSON.stringify(data, null, 2) + '</pre>';
420
+ } catch (error) {
421
+ document.getElementById('serverStatus').innerHTML = 'Error loading status';
422
+ document.getElementById('result').innerHTML = '<p style="color: red;">Error: ' + error.message + '</p>';
423
+ }
424
+ }
425
+
426
  function setExample(url) {
427
  document.getElementById('url').value = url;
428
  }
 
447
  const response = await fetch('/screenshot', {
448
  method: 'POST',
449
  headers: {
450
+ 'Content-Type': 'application/json'
451
  },
452
  body: JSON.stringify({ url, width, height, quality })
453
  });
 
456
  const blob = await response.blob();
457
  const imageUrl = URL.createObjectURL(blob);
458
  const size = (blob.size / 1024).toFixed(1);
459
+
460
  document.getElementById('result').innerHTML =
461
  '<div class="success"><h3>Screenshot Result:</h3>' +
462
  '<p>Size: ' + size + ' KB | Dimensions: ' + width + 'x' + height + '</p>' +
 
464
  '<a href="' + imageUrl + '" download="screenshot.jpg" style="background: #28a745; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Download Image</a></div>';
465
  } else {
466
  const error = await response.json();
467
+ let errorMsg = '<div class="error"><strong>Error:</strong> ' + error.error;
468
+
469
+ if (error.status === 'busy') {
470
+ errorMsg += '<br><strong>Status:</strong> Server is currently busy (CPU: ' + (error.cpuUsage || 'N/A') + ')';
471
+ errorMsg += '<br><strong>Queue Length:</strong> ' + (error.queueLength || 0);
472
+ }
473
+
474
+ if (error.suggestion) {
475
+ errorMsg += '<br><strong>Suggestion:</strong> ' + error.suggestion;
476
+ }
477
+
478
+ errorMsg += '</div>';
479
+ document.getElementById('result').innerHTML = errorMsg;
480
  }
481
  } catch (error) {
482
  document.getElementById('result').innerHTML =
 
487
  }
488
  }
489
 
490
+ // 页面加载时检查状态
491
+ checkStatus();
492
+
493
+ // 每30秒更新一次状态
494
+ setInterval(checkStatus, 30000);
495
+
496
  // Enter key support
497
  document.getElementById('url').addEventListener('keypress', function(e) {
498
  if (e.key === 'Enter') {
 
502
  </script>
503
  </body>
504
  </html>
505
+ `;
506
+
507
+ res.send(demoHtml);
508
  });
509
 
510
  // 错误处理中间件
 
522
  });
523
  });
524
 
525
+ // 优雅关闭
526
+ process.on('SIGTERM', () => {
527
+ console.log('SIGTERM received, shutting down gracefully...');
528
+ if (cpuMonitorInterval) {
529
+ clearInterval(cpuMonitorInterval);
530
+ }
531
+ process.exit(0);
532
+ });
533
+
534
  app.listen(PORT, '0.0.0.0', () => {
535
  console.log(`Screenshot API server running on port ${PORT} for Hugging Face Spaces`);
536
+ console.log('Authentication: Handled by HuggingFace platform (system-level)');
537
+ console.log('No application-level authentication required');
538
  });