答案
Whistle 支持通过脚本处理请求和响应,这为开发者提供了极大的灵活性,可以实现复杂的网络请求处理逻辑。
脚本处理基础
1. 脚本文件格式
Whistle 脚本使用 CommonJS 模块格式:
javascriptmodule.exports = function(req, res) { // 处理逻辑 };
2. 脚本类型
- reqScript:处理请求
- resScript:处理响应
- plugin:插件脚本
reqScript 使用
1. 基本用法
创建脚本文件:request-handler.js
javascriptmodule.exports = function(req, res) { console.log('Request URL:', req.url); console.log('Request Method:', req.method); console.log('Request Headers:', req.headers); };
配置规则:
shellwww.example.com reqScript://{request-handler.js}
2. 修改请求头
javascriptmodule.exports = function(req, res) { // 添加自定义请求头 req.headers['X-Custom-Header'] = 'Custom Value'; // 修改现有请求头 req.headers['User-Agent'] = 'Custom User Agent'; // 删除请求头 delete req.headers['Authorization']; };
3. 修改请求体
javascriptmodule.exports = function(req, res) { let body = ''; req.on('data', function(chunk) { body += chunk.toString(); }); req.on('end', function() { // 修改请求体 const modifiedBody = body.replace(/old/g, 'new'); // 重新发送修改后的请求 // 注意:这需要特殊处理 }); };
4. 请求重定向
javascriptmodule.exports = function(req, res) { // 修改请求 URL if (req.url.includes('/old-path')) { req.url = req.url.replace('/old-path', '/new-path'); } };
5. 条件处理
javascriptmodule.exports = function(req, res) { // 根据请求方法处理 if (req.method === 'POST') { req.headers['X-Request-Type'] = 'POST'; } // 根据请求路径处理 if (req.url.includes('/api/')) { req.headers['X-API-Request'] = 'true'; } // 根据请求头处理 if (req.headers['authorization']) { req.headers['X-Authenticated'] = 'true'; } };
resScript 使用
1. 基本用法
创建脚本文件:response-handler.js
javascriptmodule.exports = function(req, res) { console.log('Response Status:', res.statusCode); console.log('Response Headers:', res.headers); };
配置规则:
shellwww.example.com resScript://{response-handler.js}
2. 修改响应头
javascriptmodule.exports = function(req, res) { // 添加自定义响应头 res.setHeader('X-Custom-Header', 'Custom Value'); // 修改现有响应头 res.setHeader('Content-Type', 'application/json'); // 删除响应头 res.removeHeader('X-Powered-By'); };
3. 修改响应体
javascriptmodule.exports = function(req, res) { const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { let body = chunk.toString(); // 修改响应内容 body = body.replace(/old/g, 'new'); body = body.replace(/error/g, 'success'); // 重新结束响应 originalEnd.call(res, body, encoding); } else { originalEnd.call(res, chunk, encoding); } }; };
4. 响应数据转换
javascriptmodule.exports = function(req, res) { const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { const body = chunk.toString(); // JSON 数据转换 if (res.headers['content-type'] && res.headers['content-type'].includes('application/json')) { const jsonData = JSON.parse(body); jsonData.timestamp = Date.now(); jsonData.modified = true; originalEnd.call(res, JSON.stringify(jsonData), encoding); } else { originalEnd.call(res, chunk, encoding); } } else { originalEnd.call(res, chunk, encoding); } }; };
5. 响应缓存
javascriptconst cache = {}; module.exports = function(req, res) { const cacheKey = req.url; // 检查缓存 if (cache[cacheKey]) { console.log('Using cached response for:', cacheKey); res.end(cache[cacheKey]); return; } // 缓存响应 const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { cache[cacheKey] = chunk.toString(); originalEnd.call(res, chunk, encoding); } else { originalEnd.call(res, chunk, encoding); } }; };
高级脚本处理
1. 组合使用 reqScript 和 resScript
request-handler.js:
javascriptmodule.exports = function(req, res) { // 请求开始时间 req.startTime = Date.now(); req.headers['X-Request-Start'] = req.startTime; };
response-handler.js:
javascriptmodule.exports = function(req, res) { const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { // 计算请求耗时 const duration = Date.now() - (req.startTime || 0); res.setHeader('X-Request-Duration', duration); originalEnd.call(res, chunk, encoding); } else { originalEnd.call(res, chunk, encoding); } }; };
配置规则:
shellwww.example.com reqScript://{request-handler.js} resScript://{response-handler.js}
2. 使用外部模块
javascriptconst fs = require('fs'); const path = require('path'); module.exports = function(req, res) { // 读取外部文件 const filePath = path.join(__dirname, 'data.json'); const data = JSON.parse(fs.readFileSync(filePath, 'utf8')); // 使用数据修改响应 const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { const body = chunk.toString(); const modifiedBody = body.replace('{{data}}', JSON.stringify(data)); originalEnd.call(res, modifiedBody, encoding); } else { originalEnd.call(res, chunk, encoding); } }; };
3. 异步处理
javascriptconst https = require('https'); module.exports = function(req, res) { const originalEnd = res.end; res.end = function(chunk, encoding) { if (chunk) { // 异步处理 https.get('https://api.example.com/data', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { // 使用外部数据修改响应 const body = chunk.toString(); const modifiedBody = body.replace('{{external-data}}', data); originalEnd.call(res, modifiedBody, encoding); }); }).on('error', (err) => { console.error('Error fetching external data:', err); originalEnd.call(res, chunk, encoding); }); } else { originalEnd.call(res, chunk, encoding); } }; };
脚本调试
1. 使用 console.log
javascriptmodule.exports = function(req, res) { console.log('Request URL:', req.url); console.log('Request Method:', req.method); console.log('Request Headers:', JSON.stringify(req.headers, null, 2)); };
2. 错误处理
javascriptmodule.exports = function(req, res) { try { // 处理逻辑 const result = processData(req); res.end(JSON.stringify(result)); } catch (error) { console.error('Script error:', error); res.statusCode = 500; res.end(JSON.stringify({ error: error.message })); } };
最佳实践
1. 脚本组织
- 按功能模块组织脚本
- 使用有意义的文件名
- 添加注释说明脚本用途
2. 性能优化
- 避免在脚本中进行复杂计算
- 使用缓存减少重复操作
- 合理使用异步处理
3. 错误处理
- 添加适当的错误处理
- 记录错误日志
- 提供友好的错误信息
4. 安全考虑
- 验证输入数据
- 防止注入攻击
- 不暴露敏感信息