whistle.script 1.1.0 → 1.2.3
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/README.md +73 -11
- package/lib/auth.js +39 -0
- package/lib/index.js +17 -1
- package/lib/pipe.js +32 -0
- package/lib/resRulesServer.js +10 -0
- package/lib/rulesServer.js +11 -0
- package/lib/server.js +11 -14
- package/lib/sniCallback.js +24 -0
- package/lib/tunnelRulesServer.js +8 -0
- package/lib/tunnelServer.js +3 -0
- package/lib/util.js +126 -4
- package/lib/wsServer.js +3 -10
- package/package.json +6 -3
- package/rules.txt +2 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ whistle.script为[whistle](https://github.com/avwo/whistle)的一个扩展脚本
|
|
|
18
18
|
|
|
19
19
|
# 安装
|
|
20
20
|
|
|
21
|
-
1. 安装Node
|
|
21
|
+
1. 安装Node: [官网下载安装最新版本(LTS和Stable都可以)](https://nodejs.org/)
|
|
22
22
|
2. 安装最新版的[whistle](https://github.com/avwo/whistle)。
|
|
23
23
|
|
|
24
24
|
npm install -g whistle
|
|
@@ -28,9 +28,7 @@ whistle.script为[whistle](https://github.com/avwo/whistle)的一个扩展脚本
|
|
|
28
28
|
|
|
29
29
|
3. 安装script插件:
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
# Mac、Linux用户可能需要加sudo
|
|
33
|
-
sudo npm install -g whistle.script
|
|
31
|
+
w2 i whistle.script
|
|
34
32
|
|
|
35
33
|
# 使用
|
|
36
34
|
|
|
@@ -161,12 +159,12 @@ whistle.script为[whistle](https://github.com/avwo/whistle)的一个扩展脚本
|
|
|
161
159
|
// 正常断开 WebSocket 连接
|
|
162
160
|
socket.on('disconnect', (code, message, opts) => {
|
|
163
161
|
console.log(code, 'client disconnect');
|
|
164
|
-
svrSocket.disconnect(code,
|
|
162
|
+
svrSocket.disconnect(code, opts);
|
|
165
163
|
});
|
|
166
164
|
// 正常断开 WebSocket 连接
|
|
167
165
|
svrSocket.on('disconnect', (code, message, opts) => {
|
|
168
166
|
console.log(code, 'server disconnect');
|
|
169
|
-
socket.disconnect(code,
|
|
167
|
+
socket.disconnect(code, opts);
|
|
170
168
|
});
|
|
171
169
|
// 获取客户端解析后的帧数据
|
|
172
170
|
socket.on('message', (data, opts) => {
|
|
@@ -185,18 +183,82 @@ whistle.script为[whistle](https://github.com/avwo/whistle)的一个扩展脚本
|
|
|
185
183
|
whistle规则配置同上,访问[https://www.websocket.org/echo.html](https://www.websocket.org/echo.html),点击下面的connect按钮及send按钮,可以如下效果:[demo3](https://user-images.githubusercontent.com/11450939/126302243-26c8b4af-851c-4b00-87b9-3286e9e67251.gif)
|
|
186
184
|
3. 操作Tunnel请求
|
|
187
185
|
``` js
|
|
188
|
-
exports.handleTunnel = async (
|
|
189
|
-
const
|
|
190
|
-
|
|
186
|
+
exports.handleTunnel = async (socket, connect) => {
|
|
187
|
+
const svrSocket = await connect();
|
|
188
|
+
socket.pipe(svrSocket).pipe(socket);
|
|
191
189
|
};
|
|
192
190
|
```
|
|
193
191
|
whistle规则配置同上
|
|
194
|
-
# License
|
|
195
192
|
|
|
196
|
-
|
|
193
|
+
4. 鉴权
|
|
194
|
+
插件 `v1.2.0` 版本开始支持自定义鉴权方法(要求 Whistle 版本 >= `v2.7.16`):
|
|
195
|
+
``` js
|
|
196
|
+
exports.auth = async (req, options) => {
|
|
197
|
+
// 给请求添加自定义头,必须与 `x-whistle-` 开头
|
|
198
|
+
// 这样可以在插件的其他 hook 里面获取到该请求头(除了 http 请求的 reqRead 钩子)
|
|
199
|
+
req.setHeader('x-whistle-test', '1111111111');
|
|
200
|
+
// return false; // 直接返回 403
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
5. pipe
|
|
205
|
+
插件 `v1.2.1` 版本开始支持自定义 pipe 方法:
|
|
206
|
+
``` js
|
|
207
|
+
|
|
208
|
+
exports.handleReqRead = (req, res, options) => {
|
|
209
|
+
req.pipe(res);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
exports.handleReqWrite = (req, res, options) => {
|
|
213
|
+
req.pipe(res);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
exports.handleResRead = (req, res, options) => {
|
|
217
|
+
req.pipe(res);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
exports.handleResWrite = (req, res, options) => {
|
|
221
|
+
req.pipe(res);
|
|
222
|
+
};
|
|
197
223
|
|
|
224
|
+
exports.handleWsReqRead = (req, res, options) => {
|
|
225
|
+
req.pipe(res);
|
|
226
|
+
};
|
|
198
227
|
|
|
228
|
+
exports.handleWsReqWrite = (req, res, options) => {
|
|
229
|
+
req.pipe(res);
|
|
230
|
+
};
|
|
199
231
|
|
|
232
|
+
exports.handleWsResRead = (req, res, options) => {
|
|
233
|
+
req.pipe(res);
|
|
234
|
+
};
|
|
200
235
|
|
|
236
|
+
exports.handleWsResWrite = (req, res, options) => {
|
|
237
|
+
req.pipe(res);
|
|
238
|
+
};
|
|
201
239
|
|
|
240
|
+
exports.handleTunnelReqRead = (req, res, options) => {
|
|
241
|
+
req.pipe(res);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
exports.handleTunnelReqWrite = (req, res, options) => {
|
|
245
|
+
req.pipe(res);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
exports.handleTunnelResRead = (req, res, options) => {
|
|
249
|
+
req.pipe(res);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
exports.handleTunnelResWrite = (req, res, options) => {
|
|
253
|
+
req.pipe(res);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
# 如何引入第三方模块
|
|
259
|
+
使用绝对路径引入,如假设你的模块安装路径为 `/Users/test/node_modules/xxx`,则可以在脚本里面通过 `require('/Users/test/node_modules/xxx')` 引入。
|
|
260
|
+
|
|
261
|
+
# License
|
|
262
|
+
|
|
263
|
+
[MIT](https://github.com/whistle-plugins/whistle.script/blob/master/LICENSE)
|
|
202
264
|
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const scripts = require('./scripts');
|
|
2
|
+
const { getRemoteUrl, getContext, getFn, noop, request, AUTH_URL, STATS_URL, DATA_URL, isRemote } = require('./util');
|
|
3
|
+
|
|
4
|
+
module.exports = async (req, options) => {
|
|
5
|
+
if (!isRemote(req)) {
|
|
6
|
+
const ctx = getContext(req);
|
|
7
|
+
const { auth, verify } = scripts.getHandler(ctx);
|
|
8
|
+
const check = getFn(auth, verify);
|
|
9
|
+
return check && check(req, options);
|
|
10
|
+
}
|
|
11
|
+
const authUrl = getRemoteUrl(req, AUTH_URL);
|
|
12
|
+
if (authUrl) {
|
|
13
|
+
const { auth, headers } = await request(authUrl, req.headers);
|
|
14
|
+
if (auth === false) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (headers) {
|
|
18
|
+
Object.keys(headers).forEach((key) => {
|
|
19
|
+
req.set(key, headers[key]);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const statsUrl = getRemoteUrl(req, STATS_URL);
|
|
24
|
+
const dataUrl = getRemoteUrl(req, DATA_URL);
|
|
25
|
+
if (statsUrl) {
|
|
26
|
+
req.getReqSession((session) => {
|
|
27
|
+
if (session) {
|
|
28
|
+
request(statsUrl, req.headers, session).then(noop);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (dataUrl) {
|
|
33
|
+
req.getSession((session) => {
|
|
34
|
+
if (session) {
|
|
35
|
+
request(dataUrl, req.headers, session).then(noop);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
const handlePipe = require('./pipe');
|
|
2
|
+
|
|
3
|
+
exports.auth = require('./auth');
|
|
4
|
+
exports.sniCallback = require('./sniCallback');
|
|
2
5
|
exports.uiServer = require('./uiServer');
|
|
3
6
|
exports.rulesServer = require('./rulesServer');
|
|
4
7
|
exports.resRulesServer = require('./resRulesServer');
|
|
5
8
|
exports.tunnelRulesServer = require('./tunnelRulesServer');
|
|
6
9
|
exports.server = require('./server');
|
|
7
10
|
exports.tunnelServer = require('./tunnelServer');
|
|
11
|
+
|
|
12
|
+
exports.reqRead = handlePipe('reqRead');
|
|
13
|
+
exports.reqWrite = handlePipe('reqWrite');
|
|
14
|
+
exports.resRead = handlePipe('resRead');
|
|
15
|
+
exports.resWrite = handlePipe('resWrite');
|
|
16
|
+
exports.wsReqRead = handlePipe('wsReqRead');
|
|
17
|
+
exports.wsReqWrite = handlePipe('wsReqWrite');
|
|
18
|
+
exports.wsResRead = handlePipe('wsResRead');
|
|
19
|
+
exports.wsResWrite = handlePipe('wsResWrite');
|
|
20
|
+
exports.tunnelReqRead = handlePipe('tunnelReqRead');
|
|
21
|
+
exports.tunnelReqWrite = handlePipe('tunnelReqWrite');
|
|
22
|
+
exports.tunnelResRead = handlePipe('tunnelResRead');
|
|
23
|
+
exports.tunnelResWrite = handlePipe('tunnelResWrite');
|
package/lib/pipe.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const iconv = require('iconv-lite');
|
|
2
|
+
const scripts = require('./scripts');
|
|
3
|
+
const util = require('./util');
|
|
4
|
+
|
|
5
|
+
const getHandlerName = (name) => {
|
|
6
|
+
return `handle${name[0].toUpperCase()}${name.substring(1)}`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
module.exports = (name) => {
|
|
10
|
+
const eventName = name[0] === 'r' ? 'request' : 'connect';
|
|
11
|
+
return (server, options) => {
|
|
12
|
+
options = Object.assign({
|
|
13
|
+
getCharset: util.getCharset,
|
|
14
|
+
isText: util.isText,
|
|
15
|
+
iconv,
|
|
16
|
+
}, options);
|
|
17
|
+
server.on(eventName, async (req, res) => {
|
|
18
|
+
const oReq = req.originalReq;
|
|
19
|
+
oReq.ruleValue = oReq.pipeValue || oReq.ruleValue;
|
|
20
|
+
const handleRequest = scripts.getHandler({ req })[getHandlerName(name)];
|
|
21
|
+
if (!util.isFunction(handleRequest)) {
|
|
22
|
+
return req.pipe(res);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
await handleRequest(req, res, options);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
req.emit('error', err);
|
|
28
|
+
console.error(err); // eslint-disable-line
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
};
|
package/lib/resRulesServer.js
CHANGED
|
@@ -5,6 +5,16 @@ const scripts = require('./scripts');
|
|
|
5
5
|
module.exports = (server, options) => {
|
|
6
6
|
const app = new Koa();
|
|
7
7
|
app.use(async (ctx) => {
|
|
8
|
+
const { req } = ctx;
|
|
9
|
+
const rulesUrl = req.sessionStorage.get(util.RES_RULES_URL);
|
|
10
|
+
if (rulesUrl != null) {
|
|
11
|
+
req.sessionStorage.remove(util.RES_RULES_URL);
|
|
12
|
+
if (rulesUrl) {
|
|
13
|
+
const result = await util.request(rulesUrl, req.headers);
|
|
14
|
+
ctx.body = util.formateRules(result);
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
8
18
|
util.setupContext(ctx, options);
|
|
9
19
|
const { handleResponseRules } = scripts.getHandler(ctx);
|
|
10
20
|
if (util.isFunction(handleResponseRules)) {
|
package/lib/rulesServer.js
CHANGED
|
@@ -5,6 +5,17 @@ const scripts = require('./scripts');
|
|
|
5
5
|
module.exports = (server, options) => {
|
|
6
6
|
const app = new Koa();
|
|
7
7
|
app.use(async (ctx) => {
|
|
8
|
+
const { req } = ctx;
|
|
9
|
+
if (util.isRemote(req)) {
|
|
10
|
+
const rulesUrl = util.getRemoteUrl(req, util.REQ_RULES_URL);
|
|
11
|
+
const resRulesUrl = util.getRemoteUrl(req, util.RES_RULES_URL);
|
|
12
|
+
req.sessionStorage.set(util.RES_RULES_URL, resRulesUrl || '');
|
|
13
|
+
if (rulesUrl) {
|
|
14
|
+
const result = await util.request(rulesUrl, req.headers);
|
|
15
|
+
ctx.body = util.formateRules(result);
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
8
19
|
util.setupContext(ctx, options);
|
|
9
20
|
const {
|
|
10
21
|
handleWebSocketRules,
|
package/lib/server.js
CHANGED
|
@@ -5,20 +5,15 @@ const util = require('./util');
|
|
|
5
5
|
|
|
6
6
|
module.exports = (server, options) => {
|
|
7
7
|
server.on('request', async (req, res) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
method: req.method,
|
|
18
|
-
getStreamBuffer: util.getStreamBuffer,
|
|
19
|
-
getCharset: util.getCharset,
|
|
20
|
-
isText: util.isText,
|
|
21
|
-
};
|
|
8
|
+
if (util.isRemote(req)) {
|
|
9
|
+
return req.passThrough();
|
|
10
|
+
}
|
|
11
|
+
const ctx = util.getContext(req, res);
|
|
12
|
+
ctx.getStreamBuffer = util.getStreamBuffer;
|
|
13
|
+
ctx.getCharset = util.getCharset;
|
|
14
|
+
ctx.isText = util.isText;
|
|
15
|
+
ctx.iconv = iconv;
|
|
16
|
+
ctx.options = options;
|
|
22
17
|
const { handleRequest } = scripts.getHandler(ctx);
|
|
23
18
|
if (!util.isFunction(handleRequest)) {
|
|
24
19
|
return req.passThrough();
|
|
@@ -29,6 +24,8 @@ module.exports = (server, options) => {
|
|
|
29
24
|
await handleRequest(ctx, req.request);
|
|
30
25
|
} catch (err) {
|
|
31
26
|
clearup();
|
|
27
|
+
req.emit('error', err);
|
|
28
|
+
console.error(err); // eslint-disable-line
|
|
32
29
|
}
|
|
33
30
|
});
|
|
34
31
|
setupWsServer(server, options);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const scripts = require('./scripts');
|
|
2
|
+
const { getRemoteUrl, getContext, getFn, request, SNI_URL, isSni } = require('./util');
|
|
3
|
+
|
|
4
|
+
module.exports = async (req, options) => {
|
|
5
|
+
if (!isSni(req)) {
|
|
6
|
+
const oReq = req.originalReq;
|
|
7
|
+
oReq.ruleValue = oReq.sniValue || oReq.ruleValue;
|
|
8
|
+
const ctx = getContext(req);
|
|
9
|
+
const { sniCallback, SNICallback } = scripts.getHandler(ctx);
|
|
10
|
+
const sniCb = getFn(sniCallback, SNICallback);
|
|
11
|
+
return sniCb && sniCb(req, options);
|
|
12
|
+
}
|
|
13
|
+
const sniUrl = getRemoteUrl(req, SNI_URL);
|
|
14
|
+
if (sniUrl) {
|
|
15
|
+
const result = await request(sniUrl, req.headers);
|
|
16
|
+
if (result === false || result.cert === false) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (result === true || result.cert === true) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
};
|
package/lib/tunnelRulesServer.js
CHANGED
|
@@ -5,6 +5,14 @@ const scripts = require('./scripts');
|
|
|
5
5
|
module.exports = (server, options) => {
|
|
6
6
|
const app = new Koa();
|
|
7
7
|
app.use(async (ctx) => {
|
|
8
|
+
const { req } = ctx;
|
|
9
|
+
if (util.isRemote(req)) {
|
|
10
|
+
const rulesUrl = util.getRemoteUrl(req, util.REQ_RULES_URL);
|
|
11
|
+
if (rulesUrl) {
|
|
12
|
+
ctx.body = await util.request(rulesUrl, req.headers);
|
|
13
|
+
}
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
8
16
|
util.setupContext(ctx, options);
|
|
9
17
|
const { handleTunnelRules } = scripts.getHandler(ctx);
|
|
10
18
|
if (util.isFunction(handleTunnelRules)) {
|
package/lib/tunnelServer.js
CHANGED
|
@@ -4,6 +4,9 @@ const scripts = require('./scripts');
|
|
|
4
4
|
|
|
5
5
|
module.exports = (server, options) => {
|
|
6
6
|
server.on('connect', async (req, socket) => {
|
|
7
|
+
if (util.isRemote(req)) {
|
|
8
|
+
return req.passThrough();
|
|
9
|
+
}
|
|
7
10
|
const { url, headers } = req;
|
|
8
11
|
socket.headers = headers;
|
|
9
12
|
socket.options = options;
|
package/lib/util.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
const zlib = require('zlib');
|
|
2
2
|
const { EventEmitter } = require('events');
|
|
3
|
+
const { parse: parseUrl } = require('url');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const https = require('https');
|
|
3
6
|
const dataSource = require('./dataSource');
|
|
4
7
|
|
|
5
|
-
exports.
|
|
8
|
+
exports.AUTH_URL = 'x-whistle-.script-auth-url';
|
|
9
|
+
exports.SNI_URL = 'x-whistle-.script-sni-url';
|
|
10
|
+
exports.REQ_RULES_URL = 'x-whistle-.script-req-rules-url';
|
|
11
|
+
exports.RES_RULES_URL = 'x-whistle-.script-res-rules-url';
|
|
12
|
+
exports.STATS_URL = 'x-whistle-.script-stats-url';
|
|
13
|
+
exports.DATA_URL = 'x-whistle-.script-data-url';
|
|
14
|
+
exports.noop = () => {};
|
|
15
|
+
|
|
16
|
+
const POLICY = 'x-whistle-.script-policy';
|
|
17
|
+
const isFunction = fn => typeof fn === 'function';
|
|
18
|
+
const URL_RE = /^https?:(?:\/\/|%3A%2F%2F)[\w.-]/;
|
|
19
|
+
|
|
20
|
+
exports.isFunction = isFunction;
|
|
6
21
|
exports.noop = () => {};
|
|
7
22
|
const getCharset = (headers) => {
|
|
8
23
|
if (/charset=([^\s]+)/.test(headers['content-type'])) {
|
|
@@ -62,15 +77,23 @@ exports.setupContext = (ctx, options) => {
|
|
|
62
77
|
ctx.fullUrl = ctx.req.originalReq.url;
|
|
63
78
|
};
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
|
|
80
|
+
const formateRules = (ctx) => {
|
|
81
|
+
if (ctx.rules || ctx.values) {
|
|
82
|
+
return {
|
|
68
83
|
rules: Array.isArray(ctx.rules) ? ctx.rules.join('\n') : `${ctx.rules}`,
|
|
69
84
|
values: ctx.values,
|
|
70
85
|
};
|
|
71
86
|
}
|
|
72
87
|
};
|
|
73
88
|
|
|
89
|
+
exports.formateRules = formateRules;
|
|
90
|
+
|
|
91
|
+
exports.responseRules = (ctx) => {
|
|
92
|
+
if (!ctx.body) {
|
|
93
|
+
ctx.body = formateRules(ctx);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
74
97
|
exports.getDataSource = () => {
|
|
75
98
|
const ds = new EventEmitter();
|
|
76
99
|
const handleData = (type, args) => {
|
|
@@ -85,3 +108,102 @@ exports.getDataSource = () => {
|
|
|
85
108
|
},
|
|
86
109
|
};
|
|
87
110
|
};
|
|
111
|
+
|
|
112
|
+
exports.getContext = (req, res) => {
|
|
113
|
+
const fullUrl = req.originalReq.url;
|
|
114
|
+
return {
|
|
115
|
+
req,
|
|
116
|
+
res,
|
|
117
|
+
fullUrl,
|
|
118
|
+
url: fullUrl,
|
|
119
|
+
headers: req.headers,
|
|
120
|
+
method: req.method,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
exports.getFn = (f1, f2) => {
|
|
125
|
+
if (isFunction(f1)) {
|
|
126
|
+
return f1;
|
|
127
|
+
} if (isFunction(f2)) {
|
|
128
|
+
return f2;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
const request = (url, headers, data) => {
|
|
134
|
+
if (!url) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const options = parseUrl(url);
|
|
138
|
+
options.headers = Object.assign({}, headers);
|
|
139
|
+
delete options.headers.host;
|
|
140
|
+
if (data) {
|
|
141
|
+
data = Buffer.from(JSON.stringify(data));
|
|
142
|
+
options.method = 'POST';
|
|
143
|
+
}
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const httpModule = options.protocol === 'https:' ? https : http;
|
|
146
|
+
options.rejectUnauthorized = true;
|
|
147
|
+
const client = httpModule.request(options, (res) => {
|
|
148
|
+
res.on('error', handleError); // eslint-disable-line
|
|
149
|
+
let body;
|
|
150
|
+
res.on('data', (chunk) => {
|
|
151
|
+
body = body ? Buffer.concat([body, chunk]) : chunk;
|
|
152
|
+
});
|
|
153
|
+
res.on('end', () => {
|
|
154
|
+
clearTimeout(timer); // eslint-disable-line
|
|
155
|
+
if (body) {
|
|
156
|
+
try {
|
|
157
|
+
resolve(JSON.parse(body.toString()) || '');
|
|
158
|
+
} catch (e) {}
|
|
159
|
+
}
|
|
160
|
+
resolve('');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
const handleError = (err) => {
|
|
164
|
+
clearTimeout(timer); // eslint-disable-line
|
|
165
|
+
client.destroy();
|
|
166
|
+
reject(err);
|
|
167
|
+
};
|
|
168
|
+
const timer = setTimeout(() => handleError(new Error('Timeout')), 12000);
|
|
169
|
+
client.on('error', handleError);
|
|
170
|
+
client.end(data);
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
exports.request = async (url, headers, data) => {
|
|
175
|
+
try {
|
|
176
|
+
return await request(url, headers, data);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (!data) {
|
|
179
|
+
return request(url, headers, data);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const hasPolicy = ({ headers }, name) => {
|
|
185
|
+
const policy = headers[POLICY];
|
|
186
|
+
if (typeof policy === 'string') {
|
|
187
|
+
return policy.toLowerCase().indexOf(name) !== -1;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const isRemote = (req) => {
|
|
192
|
+
return hasPolicy(req, 'remote');
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
exports.isRemote = isRemote;
|
|
196
|
+
|
|
197
|
+
exports.isSni = (req) => {
|
|
198
|
+
return hasPolicy(req, 'sni');
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
exports.getRemoteUrl = (req, name) => {
|
|
202
|
+
let url = req.headers[name];
|
|
203
|
+
if (typeof url === 'string') {
|
|
204
|
+
url = decodeURIComponent(url);
|
|
205
|
+
if (URL_RE.test(url)) {
|
|
206
|
+
return url;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
package/lib/wsServer.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
|
-
const
|
|
2
|
+
const iconv = require('iconv-lite');
|
|
3
|
+
const { getDataSource, getFn } = require('./util');
|
|
3
4
|
const scripts = require('./scripts');
|
|
4
5
|
|
|
5
|
-
const { getDataSource } = util;
|
|
6
|
-
|
|
7
|
-
const getFn = (f1, f2) => {
|
|
8
|
-
if (util.isFunction(f1)) {
|
|
9
|
-
return f1;
|
|
10
|
-
} if (util.isFunction(f2)) {
|
|
11
|
-
return f2;
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
6
|
|
|
15
7
|
module.exports = (server, options) => {
|
|
16
8
|
const { getReceiver, getSender } = options.wsParser;
|
|
@@ -63,6 +55,7 @@ module.exports = (server, options) => {
|
|
|
63
55
|
req.on('error', clearup);
|
|
64
56
|
socket.dataSource = dataSource;
|
|
65
57
|
socket.headers = headers;
|
|
58
|
+
socket.iconv = iconv;
|
|
66
59
|
socket.url = socket.fullUrl = oReq.url; // eslint-disable-line
|
|
67
60
|
const handleUpgrade = (res) => {
|
|
68
61
|
if (replied) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whistle.script",
|
|
3
3
|
"description": "The plugin for the extension script for whistle",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.3",
|
|
5
5
|
"author": "avenwu <avenwu@vip.qq.com>",
|
|
6
6
|
"contributors": [],
|
|
7
7
|
"license": "MIT",
|
|
@@ -15,14 +15,17 @@
|
|
|
15
15
|
"ssi"
|
|
16
16
|
],
|
|
17
17
|
"registry": "https://github.com/whistle-plugins/whistle.script",
|
|
18
|
+
"whistleConfig": {
|
|
19
|
+
"pluginVars": true
|
|
20
|
+
},
|
|
18
21
|
"repository": {
|
|
19
22
|
"type": "git",
|
|
20
23
|
"url": "https://github.com/whistle-plugins/whistle.script.git"
|
|
21
24
|
},
|
|
22
25
|
"scripts": {
|
|
23
26
|
"dev": "webpack --config ./src/webpack.config.js -w",
|
|
24
|
-
"lint": "eslint ./lib index.js",
|
|
25
|
-
"lintfix": "eslint --fix ./lib index.js"
|
|
27
|
+
"lint": "eslint ./lib test index.js",
|
|
28
|
+
"lintfix": "eslint --fix ./lib test index.js"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"babel-core": "^6.7.6",
|
package/rules.txt
ADDED