whistle.pastekitlab 1.8.2 → 1.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +88 -24
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -86,6 +86,41 @@ function shouldIntercept(url) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// ==================== 资源类型过滤 ====================
|
|
90
|
+
function isResourceRequest(url, headers = {}) {
|
|
91
|
+
// 检查文件扩展名
|
|
92
|
+
const resourceExtensions = [
|
|
93
|
+
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.ico', // 图片
|
|
94
|
+
'.css', '.scss', '.less', // 样式
|
|
95
|
+
'.js', '.mjs', '.ts', // 脚本
|
|
96
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf', // 字体
|
|
97
|
+
'.mp3', '.mp4', '.avi', '.mov', '.wmv', '.flv', // 视频
|
|
98
|
+
'.pdf', '.zip', '.rar', '.7z', '.tar', '.gz' // 文档和压缩
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const urlLower = url.toLowerCase();
|
|
102
|
+
if (resourceExtensions.some(ext => urlLower.includes(ext))) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 检查 Content-Type(如果有)
|
|
107
|
+
const contentType = headers['content-type'] || '';
|
|
108
|
+
const resourceTypes = [
|
|
109
|
+
'image/',
|
|
110
|
+
'text/css',
|
|
111
|
+
'application/javascript',
|
|
112
|
+
'font/',
|
|
113
|
+
'video/',
|
|
114
|
+
'audio/'
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
if (resourceTypes.some(type => contentType.includes(type))) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
89
124
|
// ==================== WebSocket ====================
|
|
90
125
|
function startWebSocketServer() {
|
|
91
126
|
if (wss) return;
|
|
@@ -152,13 +187,13 @@ async function forwardRequest(req, res, url) {
|
|
|
152
187
|
reqBody += chunk;
|
|
153
188
|
}
|
|
154
189
|
|
|
155
|
-
// 通过 axios 转发请求(使用
|
|
190
|
+
// 通过 axios 转发请求(使用 stream)
|
|
156
191
|
const response = await axios({
|
|
157
192
|
method: req.method.toLowerCase(),
|
|
158
193
|
url: url,
|
|
159
194
|
headers: req.headers,
|
|
160
195
|
data: reqBody || undefined,
|
|
161
|
-
responseType: '
|
|
196
|
+
responseType: 'stream',
|
|
162
197
|
validateStatus: () => true
|
|
163
198
|
});
|
|
164
199
|
|
|
@@ -168,8 +203,9 @@ async function forwardRequest(req, res, url) {
|
|
|
168
203
|
res.setHeader(key, response.headers[key]);
|
|
169
204
|
});
|
|
170
205
|
|
|
171
|
-
//
|
|
172
|
-
|
|
206
|
+
// 直接 pipe 流到响应
|
|
207
|
+
response.data.pipe(res);
|
|
208
|
+
|
|
173
209
|
log(`直接转发: ${req.method} ${url} -> ${response.status}`);
|
|
174
210
|
|
|
175
211
|
} catch (error) {
|
|
@@ -189,6 +225,12 @@ module.exports = (server, options) => {
|
|
|
189
225
|
const url = req.fullUrl || req.url;
|
|
190
226
|
console.log('>>> [GLOBAL]', url);
|
|
191
227
|
|
|
228
|
+
// 过滤资源类请求(图片、CSS、JS等)
|
|
229
|
+
if (isResourceRequest(url, req.headers)) {
|
|
230
|
+
log(`跳过资源请求: ${url}`);
|
|
231
|
+
return forwardRequest(req, res, url);
|
|
232
|
+
}
|
|
233
|
+
|
|
192
234
|
// 域名过滤:如果不匹配则直接放行,不转发也不通知
|
|
193
235
|
if (!shouldIntercept(url)) {
|
|
194
236
|
log(`跳过未配置的域名: ${url}`);
|
|
@@ -217,15 +259,15 @@ module.exports = (server, options) => {
|
|
|
217
259
|
broadcast('REQUEST', requestData);
|
|
218
260
|
log(`REQ ${req.method} ${url}`);
|
|
219
261
|
|
|
220
|
-
// 通过 axios 转发请求(使用
|
|
262
|
+
// 通过 axios 转发请求(使用 stream 模式,支持大文件)
|
|
221
263
|
const startTime = Date.now();
|
|
222
264
|
const response = await axios({
|
|
223
265
|
method: req.method.toLowerCase(),
|
|
224
266
|
url: url,
|
|
225
267
|
headers: req.headers,
|
|
226
268
|
data: reqBody || undefined,
|
|
227
|
-
responseType: '
|
|
228
|
-
validateStatus: () => true
|
|
269
|
+
responseType: 'stream', // 使用流式传输,不占用大量内存
|
|
270
|
+
validateStatus: () => true
|
|
229
271
|
});
|
|
230
272
|
const duration = Date.now() - startTime;
|
|
231
273
|
|
|
@@ -235,25 +277,47 @@ module.exports = (server, options) => {
|
|
|
235
277
|
res.setHeader(key, response.headers[key]);
|
|
236
278
|
});
|
|
237
279
|
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
242
|
-
|
|
280
|
+
// 直接 pipe 流到响应(不会截断,支持任意大小)
|
|
281
|
+
response.data.pipe(res);
|
|
282
|
+
|
|
283
|
+
// 收集响应数据用于 WebSocket 通知(限制大小)
|
|
284
|
+
let responseDataBuffer = Buffer.alloc(0);
|
|
285
|
+
const originalPipe = response.data.pipe.bind(response.data);
|
|
286
|
+
|
|
287
|
+
response.data.on('data', (chunk) => {
|
|
288
|
+
// 只收集前 1MB 用于通知
|
|
289
|
+
if (responseDataBuffer.length < 1024 * 1024) {
|
|
290
|
+
const remaining = 1024 * 1024 - responseDataBuffer.length;
|
|
291
|
+
const toCollect = chunk.slice(0, remaining);
|
|
292
|
+
responseDataBuffer = Buffer.concat([responseDataBuffer, toCollect]);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
243
295
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
296
|
+
response.data.on('end', () => {
|
|
297
|
+
// 构建响应数据
|
|
298
|
+
const responseData = {
|
|
299
|
+
...requestData,
|
|
300
|
+
statusCode: response.status,
|
|
301
|
+
responseHeaders: {...response.headers},
|
|
302
|
+
responseBody: truncateString(bufferToString(responseDataBuffer)),
|
|
303
|
+
responseBodyBase64: responseDataBuffer.toString('base64'),
|
|
304
|
+
duration
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// 通知 Chrome 插件:响应完成
|
|
308
|
+
broadcast('RESPONSE', responseData);
|
|
309
|
+
log(`转发成功: ${req.method} ${url} -> ${response.status} (${duration}ms)`);
|
|
310
|
+
});
|
|
253
311
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
312
|
+
response.data.on('error', (error) => {
|
|
313
|
+
log(`流错误: ${error.message}`);
|
|
314
|
+
broadcast('ERROR', {
|
|
315
|
+
url,
|
|
316
|
+
method: req.method,
|
|
317
|
+
error: error.message,
|
|
318
|
+
timestamp: Date.now()
|
|
319
|
+
});
|
|
320
|
+
});
|
|
257
321
|
|
|
258
322
|
} catch (error) {
|
|
259
323
|
log(`转发失败: ${error.message}`);
|
package/package.json
CHANGED