topbit 1.0.0 → 3.0.0
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/LICENSE +128 -0
- package/README.cn.md +1519 -0
- package/README.md +1483 -0
- package/bin/app.js +17 -0
- package/bin/loadinfo.sh +18 -0
- package/bin/new-ctl.js +234 -0
- package/bin/newapp.js +22 -0
- package/demo/allow.js +98 -0
- package/demo/cert/localhost-cert.pem +19 -0
- package/demo/cert/localhost-privkey.pem +28 -0
- package/demo/controller/api.js +15 -0
- package/demo/extends.js +5 -0
- package/demo/group-api.js +161 -0
- package/demo/group-api2.js +109 -0
- package/demo/http2.js +34 -0
- package/demo/http2_proxy_backend.js +45 -0
- package/demo/http2proxy.js +48 -0
- package/demo/http_proxy_backend.js +44 -0
- package/demo/httpproxy.js +47 -0
- package/demo/loader.js +27 -0
- package/demo/log.js +118 -0
- package/demo/memlimit.js +31 -0
- package/demo/min.js +7 -0
- package/demo/serv.js +15 -0
- package/images/middleware.jpg +0 -0
- package/images/topbit-middleware.png +0 -0
- package/images/topbit.png +0 -0
- package/package.json +42 -11
- package/src/_loadExtends.js +21 -0
- package/src/bodyparser.js +420 -0
- package/src/connfilter.js +125 -0
- package/src/context1.js +166 -0
- package/src/context2.js +182 -0
- package/src/ctxpool.js +39 -0
- package/src/ext.js +318 -0
- package/src/extends/Http2Pool.js +365 -0
- package/src/extends/__randstring.js +24 -0
- package/src/extends/cookie.js +44 -0
- package/src/extends/cors.js +334 -0
- package/src/extends/errorlog.js +252 -0
- package/src/extends/http2limit.js +126 -0
- package/src/extends/http2proxy.js +691 -0
- package/src/extends/jwt.js +217 -0
- package/src/extends/mixlogger.js +63 -0
- package/src/extends/paramcheck.js +266 -0
- package/src/extends/proxy.js +662 -0
- package/src/extends/realip.js +34 -0
- package/src/extends/referer.js +68 -0
- package/src/extends/resource.js +398 -0
- package/src/extends/session.js +174 -0
- package/src/extends/setfinal.js +50 -0
- package/src/extends/sni.js +48 -0
- package/src/extends/sse.js +293 -0
- package/src/extends/timing.js +111 -0
- package/src/extends/tofile.js +123 -0
- package/src/fastParseUrl.js +426 -0
- package/src/headerLimit.js +18 -0
- package/src/http1.js +336 -0
- package/src/http2.js +337 -0
- package/src/httpc.js +251 -0
- package/src/lib/npargv.js +354 -0
- package/src/lib/zipdata.js +45 -0
- package/src/loader/loader.js +999 -0
- package/src/logger.js +32 -0
- package/src/loggermsg.js +349 -0
- package/src/makeId.js +200 -0
- package/src/midcore.js +213 -0
- package/src/middleware1.js +103 -0
- package/src/middleware2.js +116 -0
- package/src/monitor.js +380 -0
- package/src/movefile.js +30 -0
- package/src/optionsCheck.js +54 -0
- package/src/randstring.js +23 -0
- package/src/router.js +682 -0
- package/src/sendmsg.js +27 -0
- package/src/strong.js +72 -0
- package/src/token/token.js +461 -0
- package/src/topbit.js +1293 -0
- package/src/versionCheck.js +31 -0
- package/test/test-bigctx.js +29 -0
- package/test/test-daemon-args.js +7 -0
- package/test/test-ext.js +81 -0
- package/test/test-find.js +69 -0
- package/test/test-route-sort.js +71 -0
- package/test/test-route.js +49 -0
- package/test/test-route2.js +51 -0
- package/test/test-run-args.js +7 -0
- package/test/test-url.js +52 -0
- package/main.js +0 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
module bodyparser
|
|
3
|
+
Copyright (C) 2019.08 BraveWang
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 有可能存在content-type,不存在filename,这种情况,其实是子multipart,需要再次解析。
|
|
8
|
+
* 但是此出不做处理,只做单层解析。
|
|
9
|
+
Content-type: multipart/form-data, boundary=AaB03x
|
|
10
|
+
--AaB03x
|
|
11
|
+
content-disposition: form-data; name="field1"
|
|
12
|
+
Joe Blow
|
|
13
|
+
--AaB03x
|
|
14
|
+
content-disposition: form-data; name="pics"
|
|
15
|
+
Content-type: multipart/mixed, boundary=BbC04y
|
|
16
|
+
--BbC04y
|
|
17
|
+
Content-disposition: attachment; filename="file1.txt"
|
|
18
|
+
Content-Type: text/plain
|
|
19
|
+
... contents of file1.txt ...
|
|
20
|
+
--BbC04y
|
|
21
|
+
Content-disposition: attachment; filename="file2.gif"
|
|
22
|
+
Content-type: image/gif
|
|
23
|
+
Content-Transfer-Encoding: binary
|
|
24
|
+
...contents of file2.gif...
|
|
25
|
+
--BbC04y--
|
|
26
|
+
--AaB03x--
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
//content-disposition,此格式定义了三个参数:inline、attachment、form-data
|
|
30
|
+
//attachment用于下载,form-data用于上传,多个参数用;分割,name和filename必须要有引号包含。
|
|
31
|
+
//一个非常操蛋的问题是,name和filename属性值都是可能包含;的,而且浏览器也不会对;进行转义。
|
|
32
|
+
//这种看起来不是很复杂的格式其实带来了很多问题,因为属性的值是未知的,在长期的实践中遇到过各种情况。
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const {fpqs} = require('./fastParseUrl.js')
|
|
37
|
+
|
|
38
|
+
class Bodyparser {
|
|
39
|
+
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
|
|
42
|
+
this.maxFiles = 15;
|
|
43
|
+
|
|
44
|
+
this.maxMultipartHeaders = 9;
|
|
45
|
+
|
|
46
|
+
//multipart 最大消息头绝对不可能超过此值。
|
|
47
|
+
//考虑到一些附属的消息头比如content-length、content-encoding等加上文件名最大长度。
|
|
48
|
+
//一般极端情况长度不会超过1000,超过此值,则几乎可以肯定是错误的数据或恶意请求。
|
|
49
|
+
this.maxHeaderSize = 1024;
|
|
50
|
+
|
|
51
|
+
this.maxFormLength = 0;
|
|
52
|
+
|
|
53
|
+
this.maxFormKey = 100;
|
|
54
|
+
|
|
55
|
+
if (typeof options === 'object') {
|
|
56
|
+
for (let k in options) {
|
|
57
|
+
switch (k) {
|
|
58
|
+
|
|
59
|
+
case 'maxFiles':
|
|
60
|
+
case 'maxFormLength':
|
|
61
|
+
case 'maxFormKey':
|
|
62
|
+
if (typeof options[k] === 'number' && options[k] > 0) {
|
|
63
|
+
this[k] = options[k];
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.pregUpload = /multipart.* boundary.*=/i;
|
|
73
|
+
this.formType = 'application/x-www-form-urlencoded';
|
|
74
|
+
|
|
75
|
+
this.methods = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
76
|
+
|
|
77
|
+
this.multiLength = 'multipart/form-data'.length;
|
|
78
|
+
|
|
79
|
+
this.formdataLength = 'form-data'.length;
|
|
80
|
+
this.formdataBorder = [' ', ';', '"', "'"];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
解析上传文件数据的函数,此函数解析的是整体的文件,
|
|
85
|
+
解析过程参照HTTP/1.1协议。
|
|
86
|
+
*/
|
|
87
|
+
parseUploadData(ctx) {
|
|
88
|
+
//let bdy = ctx.headers['content-type'].split('=')[1];
|
|
89
|
+
let ctype = ctx.headers['content-type'];
|
|
90
|
+
|
|
91
|
+
//multipart/form-data;boundary length is 28
|
|
92
|
+
let bdy = ctype.substring(ctype.indexOf('=', 28)+1);
|
|
93
|
+
|
|
94
|
+
if (!bdy) return false;
|
|
95
|
+
|
|
96
|
+
bdy = bdy.trim();
|
|
97
|
+
|
|
98
|
+
bdy = `--${bdy}`;
|
|
99
|
+
|
|
100
|
+
let bdy_crlf = `${bdy}\r\n`;
|
|
101
|
+
let crlf_bdy = `\r\n${bdy}`;
|
|
102
|
+
|
|
103
|
+
let file_end = 0;
|
|
104
|
+
let file_start = 0;
|
|
105
|
+
|
|
106
|
+
file_start = ctx.rawBody.indexOf(bdy_crlf);
|
|
107
|
+
if (file_start < 0) {
|
|
108
|
+
return ;
|
|
109
|
+
}
|
|
110
|
+
let bdycrlf_length = bdy_crlf.length;
|
|
111
|
+
file_start += bdycrlf_length;
|
|
112
|
+
|
|
113
|
+
let i=0; //保证不出现死循环或恶意数据产生大量无意义循环
|
|
114
|
+
|
|
115
|
+
while (i < this.maxFiles) {
|
|
116
|
+
file_end = ctx.rawBody.indexOf(crlf_bdy, file_start);
|
|
117
|
+
|
|
118
|
+
if (file_end <= 0) break;
|
|
119
|
+
|
|
120
|
+
this.parseSingleFile(ctx, file_start, file_end);
|
|
121
|
+
|
|
122
|
+
//\r\n--boundary\r\n
|
|
123
|
+
file_start = file_end + bdycrlf_length + 2;
|
|
124
|
+
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Content-Disposition: form-data; name="NAME"; filename="FILENAME"\r\n
|
|
132
|
+
* Content-Type: TYPE
|
|
133
|
+
*
|
|
134
|
+
* @param {object} ctx
|
|
135
|
+
* @param {number} start_ind
|
|
136
|
+
* @param {number} end_ind
|
|
137
|
+
*/
|
|
138
|
+
parseSingleFile(ctx, start_ind, end_ind) {
|
|
139
|
+
let header_end_ind = ctx.rawBody.indexOf('\r\n\r\n',start_ind);
|
|
140
|
+
let headerlength = header_end_ind - start_ind;
|
|
141
|
+
if (headerlength > this.maxHeaderSize) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
let header_data = ctx.rawBody.toString('utf8', start_ind, header_end_ind);
|
|
145
|
+
|
|
146
|
+
let data_start = header_end_ind+4;
|
|
147
|
+
//let data_end = end_ind;
|
|
148
|
+
let data_length = end_ind - 4 - header_end_ind;
|
|
149
|
+
|
|
150
|
+
//file data
|
|
151
|
+
//let headerlist = header_data.split('\r\n');
|
|
152
|
+
let headers = {};
|
|
153
|
+
let colon_index;
|
|
154
|
+
let crlf_indstart = 0;
|
|
155
|
+
//绝对不可能一两个字符就开始换行,第一行必须是content-disposition: xxx
|
|
156
|
+
let crlf_ind = header_data.indexOf('\r\n', 1);
|
|
157
|
+
let hcount = 0;
|
|
158
|
+
let hstr = '';
|
|
159
|
+
if (crlf_ind < 0) {
|
|
160
|
+
colon_index = header_data.indexOf(':');
|
|
161
|
+
colon_index > 0 && (
|
|
162
|
+
headers[ header_data.substring(0, colon_index).trim().toLowerCase() ] = header_data.substring(colon_index+1).trim()
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
while (crlf_ind > crlf_indstart && hcount < this.maxMultipartHeaders) {
|
|
166
|
+
hstr = header_data.substring(crlf_indstart, crlf_ind);
|
|
167
|
+
colon_index = hstr.indexOf(':');
|
|
168
|
+
hcount++;
|
|
169
|
+
|
|
170
|
+
colon_index > 0 && (
|
|
171
|
+
headers[ hstr.substring(0, colon_index).trim().toLowerCase() ] = hstr.substring(colon_index+1).trim()
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
crlf_indstart = crlf_ind;
|
|
175
|
+
if (crlf_ind < headerlength) {
|
|
176
|
+
crlf_ind = header_data.indexOf('\r\n', crlf_ind+3);
|
|
177
|
+
crlf_ind < 0 && (crlf_ind = headerlength);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/*
|
|
182
|
+
let colon_index = 0;
|
|
183
|
+
let hline = '';
|
|
184
|
+
for (let i = 0; i < headerlist.length && i < this.maxMultipartHeaders; i++) {
|
|
185
|
+
hline = headerlist[i];
|
|
186
|
+
colon_index = hline.indexOf(':');
|
|
187
|
+
if (colon_index < 0) continue;
|
|
188
|
+
headers[hline.substring(0, colon_index).trim().toLowerCase()] = hline.substring(colon_index+1).trim();
|
|
189
|
+
}*/
|
|
190
|
+
|
|
191
|
+
let cdps = headers['content-disposition'];
|
|
192
|
+
if (!cdps) return false;
|
|
193
|
+
|
|
194
|
+
let cdpobj = this.parseFormData(cdps);
|
|
195
|
+
if (!cdpobj) return false;
|
|
196
|
+
|
|
197
|
+
if (cdpobj.filename !== undefined) {
|
|
198
|
+
let file_post = {
|
|
199
|
+
filename: cdpobj.filename,
|
|
200
|
+
'content-type': headers['content-type'] || 'application/octet-stream',
|
|
201
|
+
start: data_start,
|
|
202
|
+
end: end_ind,
|
|
203
|
+
length: data_length,
|
|
204
|
+
headers: headers,
|
|
205
|
+
rawHeader: header_data
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let slash_index = file_post.filename.lastIndexOf('/');
|
|
209
|
+
if (slash_index >= 0) {
|
|
210
|
+
file_post.filename = file_post.filename.substring(slash_index+1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//content-type
|
|
214
|
+
file_post.type = file_post['content-type'];
|
|
215
|
+
let upload_name = cdpobj.name || 'file';
|
|
216
|
+
|
|
217
|
+
if (ctx.files[upload_name] === undefined) {
|
|
218
|
+
ctx.files[upload_name] = [ file_post ];
|
|
219
|
+
} else {
|
|
220
|
+
ctx.files[upload_name].push(file_post);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
} else {
|
|
224
|
+
//不支持子multipart格式
|
|
225
|
+
if (headers['content-type'] && headers['content-type'].indexOf('multipart/mixed') === 0) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (this.maxFormLength > 0 && data_length > this.maxFormLength) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let name = cdpobj.name;
|
|
234
|
+
|
|
235
|
+
if (name) {
|
|
236
|
+
let name_value = ctx.rawBody.toString('utf8', data_start, end_ind);
|
|
237
|
+
if (ctx.body[name] === undefined) {
|
|
238
|
+
ctx.body[name] = name_value;
|
|
239
|
+
|
|
240
|
+
} else if (Array.isArray(ctx.body[name])) {
|
|
241
|
+
ctx.body[name].push(name_value);
|
|
242
|
+
} else {
|
|
243
|
+
ctx.body[name] = [ctx.body[name], name_value];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
parseFormData(cdps) {
|
|
251
|
+
let rk = this.formdataLength
|
|
252
|
+
if (cdps.substring(0, rk) !== 'form-data') return false;
|
|
253
|
+
while (cdps[rk] === ';' || cdps[rk] === ' ') rk++
|
|
254
|
+
|
|
255
|
+
let cdpobj = {}
|
|
256
|
+
let cindex = 0
|
|
257
|
+
let statestr = cdps.substring(rk)
|
|
258
|
+
let cstart=0, i, k, q=''
|
|
259
|
+
let kname = ''
|
|
260
|
+
let eq_break = false
|
|
261
|
+
let real_index = 0
|
|
262
|
+
|
|
263
|
+
let state_length = statestr.length
|
|
264
|
+
|
|
265
|
+
while (cindex < state_length) {
|
|
266
|
+
//cindex = statestr.indexOf('=', cindex)
|
|
267
|
+
//if (cindex < 0) break
|
|
268
|
+
eq_break = false
|
|
269
|
+
real_index = 0
|
|
270
|
+
while (cindex < state_length) {
|
|
271
|
+
switch (statestr[cindex]) {
|
|
272
|
+
case ';':
|
|
273
|
+
cindex++
|
|
274
|
+
while(statestr[cindex] === ' ' && cindex < state_length) cindex++
|
|
275
|
+
cstart = cindex
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
//cstart是从一个非空字符开始的
|
|
279
|
+
case ' ':
|
|
280
|
+
;(real_index <= 0) && (real_index = cindex)
|
|
281
|
+
cindex++
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
case '=':
|
|
285
|
+
eq_break = true
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
default:
|
|
289
|
+
real_index > 0 && (real_index = 0)
|
|
290
|
+
cindex++
|
|
291
|
+
break
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (eq_break) break
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (cindex >= state_length) break
|
|
298
|
+
|
|
299
|
+
i = cindex + 1
|
|
300
|
+
while (statestr[i] === ' ' && i < state_length) i++
|
|
301
|
+
|
|
302
|
+
kname = statestr.substring(cstart, real_index || cindex)
|
|
303
|
+
//kname = statestr.substring(cstart, cindex).trimEnd()
|
|
304
|
+
if (!kname) {cindex=i;cstart=cindex;continue}
|
|
305
|
+
|
|
306
|
+
if (i >= state_length) {
|
|
307
|
+
//说明还是有=,但是后续没有赋值
|
|
308
|
+
cdpobj[kname] = ''
|
|
309
|
+
break
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
q = statestr[i]
|
|
313
|
+
|
|
314
|
+
if (q === ';') {
|
|
315
|
+
k = i
|
|
316
|
+
//name= 说明没有数据的值
|
|
317
|
+
cdpobj[kname] = ''
|
|
318
|
+
k++
|
|
319
|
+
} else if (q) {
|
|
320
|
+
if (q === '"' || q === "'") {
|
|
321
|
+
i++
|
|
322
|
+
} else {q = ''}
|
|
323
|
+
k = i
|
|
324
|
+
while (k < state_length) {
|
|
325
|
+
//有可能就是携带\\,引号应该被转义。
|
|
326
|
+
//if (statestr[k] === '\\') k+=2
|
|
327
|
+
|
|
328
|
+
if ( (q && statestr[k] === q)
|
|
329
|
+
|| (!q && (statestr[k] === ';' || statestr[k] === ' ')) )
|
|
330
|
+
{
|
|
331
|
+
cdpobj[kname] = statestr.substring(i, k)
|
|
332
|
+
k++
|
|
333
|
+
break
|
|
334
|
+
}
|
|
335
|
+
k++
|
|
336
|
+
}
|
|
337
|
+
//如果到了字符串末尾但是还没有设置值
|
|
338
|
+
if (!cdpobj[kname] && k === state_length) {
|
|
339
|
+
cdpobj[kname] = statestr.substring(i, k)
|
|
340
|
+
break
|
|
341
|
+
}
|
|
342
|
+
}/* else if (!q) {
|
|
343
|
+
break
|
|
344
|
+
} */
|
|
345
|
+
|
|
346
|
+
cindex = k
|
|
347
|
+
while (cindex < state_length
|
|
348
|
+
&& (statestr[cindex] === ' '
|
|
349
|
+
|| statestr[cindex] === ';'
|
|
350
|
+
|| statestr[cindex] === '"'
|
|
351
|
+
|| statestr[cindex] === "'"
|
|
352
|
+
)
|
|
353
|
+
) { cindex++ }
|
|
354
|
+
|
|
355
|
+
cstart = cindex
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return cdpobj
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
checkUploadHeader(typestr) {
|
|
362
|
+
if (typestr.indexOf('multipart/form-data') === 0
|
|
363
|
+
&& (typestr.indexOf('boundary=', this.multiLength) > 0
|
|
364
|
+
|| typestr.indexOf('boundary =', this.multiLength) > 0))
|
|
365
|
+
{
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
mid() {
|
|
373
|
+
let self = this;
|
|
374
|
+
let json_length = ('application/json').length;
|
|
375
|
+
let json_next = [' ', ';'];
|
|
376
|
+
|
|
377
|
+
return async (ctx, next) => {
|
|
378
|
+
let m1 = ctx.method[0]
|
|
379
|
+
|
|
380
|
+
if ((m1 === 'P' || m1 === 'D') && ctx.rawBody && (ctx.rawBody instanceof Buffer || typeof ctx.rawBody === 'string'))
|
|
381
|
+
{
|
|
382
|
+
if (ctx.headers['content-type'] === undefined) {
|
|
383
|
+
ctx.headers['content-type'] = '';
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let ctype = ctx.headers['content-type'];
|
|
387
|
+
|
|
388
|
+
if ( self.checkUploadHeader(ctype) ) {
|
|
389
|
+
|
|
390
|
+
ctx.isUpload = true;
|
|
391
|
+
self.parseUploadData(ctx, self.maxFiles);
|
|
392
|
+
|
|
393
|
+
} else if (ctype && ctype.indexOf(self.formType) >= 0) {
|
|
394
|
+
|
|
395
|
+
//autoDecode = true
|
|
396
|
+
fpqs(ctx.rawBody.toString('utf8'), ctx.body, true, self.maxFormKey);
|
|
397
|
+
|
|
398
|
+
} else if (ctype.indexOf('text/') === 0) {
|
|
399
|
+
ctx.body = ctx.rawBody.toString('utf8');
|
|
400
|
+
|
|
401
|
+
} else if (ctype === 'application/json'
|
|
402
|
+
|| (ctype.indexOf('application/json') === 0 && json_next.indexOf(ctype[json_length])>=0 ) )
|
|
403
|
+
{
|
|
404
|
+
//有可能会传递application/jsonb等其他json*的格式。
|
|
405
|
+
try {
|
|
406
|
+
ctx.body = JSON.parse( ctx.rawBody.toString('utf8') );
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return ctx.status(400).send('bad json data');
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
ctx.body = ctx.rawBody;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
await next(ctx);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
module.exports = Bodyparser;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
module connfilter
|
|
5
|
+
Copyright (C) 2019.08 BraveWang
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 请求过滤模块,此模块要挂载到connection事件上。
|
|
9
|
+
* @param {object} options 选项值参考:
|
|
10
|
+
* - unitTime {number}
|
|
11
|
+
* - maxConn {number}
|
|
12
|
+
* - deny {array}
|
|
13
|
+
* - allow {array}
|
|
14
|
+
* rundata是运行时数据,这个数据需要实时更新到负载监控,所以可以通过传递一个对象指向全局应用。
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
let connfilter = function (limit, rundata) {
|
|
19
|
+
|
|
20
|
+
if (! (this instanceof connfilter)) {
|
|
21
|
+
return new connfilter(limit, rundata);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let the = this;
|
|
25
|
+
|
|
26
|
+
this.iptable = new Map();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 请求过滤函数。
|
|
30
|
+
* @param {object} sock 当前请求的socket实例。
|
|
31
|
+
*/
|
|
32
|
+
this.callback = (sock) => {
|
|
33
|
+
rundata.conn++;
|
|
34
|
+
sock.on('close', (e) => {
|
|
35
|
+
rundata.conn--;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let remote_ip = sock.remoteAddress;
|
|
39
|
+
/**
|
|
40
|
+
* 注意,这需要你指明所运行的模式是IPv4,也就是要指明host为'0.0.0.0'或是其他,
|
|
41
|
+
* 否则会默认使用IPv6的地址,这时候,remoteAddress显示::ffff:127.0.0.1这样的字符串。
|
|
42
|
+
* */
|
|
43
|
+
if (limit.deny) {
|
|
44
|
+
if ( (limit.deny_type === 's' && limit.deny.has(remote_ip))
|
|
45
|
+
|| (limit.deny_type === 'f' && limit.deny(remote_ip)) )
|
|
46
|
+
{
|
|
47
|
+
sock.destroy();
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//检测是否超过最大连接数限制。
|
|
53
|
+
if (limit.maxConn > 0 && rundata.conn > limit.maxConn) {
|
|
54
|
+
sock.destroy();
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//如果开启了单元时间内单个IP最大访问次数限制则检测是否合法。
|
|
59
|
+
let ipcount;
|
|
60
|
+
|
|
61
|
+
if (limit.maxIPRequest > 0 &&
|
|
62
|
+
!( (limit.allow_type === 's' && limit.allow && limit.allow.has(remote_ip))
|
|
63
|
+
|| (limit.allow_type === 'f' && limit.allow(remote_ip)) ) )
|
|
64
|
+
{
|
|
65
|
+
let tm = Date.now();
|
|
66
|
+
|
|
67
|
+
if (the.iptable.has(remote_ip)) {
|
|
68
|
+
|
|
69
|
+
ipcount = this.iptable.get(remote_ip);
|
|
70
|
+
|
|
71
|
+
if (tm - ipcount.time > limit.unitTime) {
|
|
72
|
+
ipcount.count = 1;
|
|
73
|
+
ipcount.time = tm;
|
|
74
|
+
this.iptable.delete(remote_ip);
|
|
75
|
+
this.iptable.set(remote_ip, ipcount);
|
|
76
|
+
} else {
|
|
77
|
+
if (ipcount.count >= limit.maxIPRequest) {
|
|
78
|
+
sock.destroy();
|
|
79
|
+
return false;
|
|
80
|
+
} else {
|
|
81
|
+
ipcount.count++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
} else if (the.iptable.size >= limit.maxIPCache) {
|
|
86
|
+
/**
|
|
87
|
+
* 如果已经超过IP最大缓存数量限制则关闭连接,这种情况在极端情况下会出现。
|
|
88
|
+
* 不过最大缓存数量不能低于最大连接数。否则并发支持会受限制。
|
|
89
|
+
* */
|
|
90
|
+
sock.destroy();
|
|
91
|
+
return false;
|
|
92
|
+
|
|
93
|
+
} else {
|
|
94
|
+
the.iptable.set(remote_ip, {count: 1, time: tm});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
this.intervalId = null;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 限制IP请求次数的定时器。
|
|
105
|
+
* 这意味着会定期进行一次大清空。
|
|
106
|
+
*/
|
|
107
|
+
if (limit.maxIPRequest > 0) {
|
|
108
|
+
this.intervalId = setInterval(() => {
|
|
109
|
+
if (the.iptable.size >= limit.maxIPCache) {
|
|
110
|
+
the.iptable.clear();
|
|
111
|
+
} else {
|
|
112
|
+
let tm = Date.now();
|
|
113
|
+
|
|
114
|
+
for (let [k, v] of the.iptable) {
|
|
115
|
+
if ( (tm - v.time - 5000) < limit.unitTime) break;
|
|
116
|
+
the.iptable.delete(k);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}, limit.unitTime + 5000);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = connfilter;
|
package/src/context1.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ext = require('./ext.js')
|
|
4
|
+
const moveFile = require('./movefile.js')
|
|
5
|
+
|
|
6
|
+
class Context {
|
|
7
|
+
|
|
8
|
+
constructor () {
|
|
9
|
+
|
|
10
|
+
this.version = '1.1'
|
|
11
|
+
|
|
12
|
+
//主版本号
|
|
13
|
+
this.major = 1
|
|
14
|
+
|
|
15
|
+
this.maxBody = 0
|
|
16
|
+
|
|
17
|
+
this.method = ''
|
|
18
|
+
|
|
19
|
+
this.host = ''
|
|
20
|
+
|
|
21
|
+
this.protocol = ''
|
|
22
|
+
|
|
23
|
+
this.ip = ''
|
|
24
|
+
|
|
25
|
+
//实际的访问路径
|
|
26
|
+
this.path = ''
|
|
27
|
+
|
|
28
|
+
this.name = ''
|
|
29
|
+
|
|
30
|
+
this.headers = {}
|
|
31
|
+
|
|
32
|
+
//实际执行请求的路径
|
|
33
|
+
this.routepath = ''
|
|
34
|
+
|
|
35
|
+
this.param = {}
|
|
36
|
+
|
|
37
|
+
this.query = {}
|
|
38
|
+
|
|
39
|
+
this.body = {}
|
|
40
|
+
|
|
41
|
+
this.isUpload = false
|
|
42
|
+
|
|
43
|
+
this.group = ''
|
|
44
|
+
|
|
45
|
+
this.rawBody = ''
|
|
46
|
+
|
|
47
|
+
this.bodyLength = 0
|
|
48
|
+
|
|
49
|
+
this.files = {}
|
|
50
|
+
|
|
51
|
+
this.requestCall = null
|
|
52
|
+
|
|
53
|
+
this.ext = ext
|
|
54
|
+
|
|
55
|
+
this.port = 0
|
|
56
|
+
|
|
57
|
+
this.data = ''
|
|
58
|
+
this.dataEncoding = 'utf8'
|
|
59
|
+
|
|
60
|
+
this.req = null
|
|
61
|
+
this.res = null
|
|
62
|
+
|
|
63
|
+
this.box = {}
|
|
64
|
+
|
|
65
|
+
this.service = null
|
|
66
|
+
|
|
67
|
+
this.user = null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
json(data) {
|
|
71
|
+
return this.setHeader('content-type', 'application/json').to(data)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
text(data, encoding='utf-8') {
|
|
75
|
+
return this.setHeader('content-type', `text/plain;charset=${encoding}`).to(data)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
html(data, encoding='utf-8') {
|
|
79
|
+
return this.setHeader('content-type', `text/html;charset=${encoding}`).to(data)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
to(d) {
|
|
83
|
+
this.data = d
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getFile(name, ind = 0) {
|
|
87
|
+
if (ind < 0) {
|
|
88
|
+
return this.files[name] || []
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.files[name] === undefined) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (ind >= this.files[name].length) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this.files[name][ind]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setHeader(name, val) {
|
|
103
|
+
this.res.setHeader(name, val)
|
|
104
|
+
return this
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
sendHeader() {
|
|
108
|
+
!this.res
|
|
109
|
+
&& !this.res.headersSent
|
|
110
|
+
&& this.res.writeHead(this.res.statusCode)
|
|
111
|
+
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
status(stcode = null) {
|
|
116
|
+
if (stcode === null) {
|
|
117
|
+
return this.res.statusCode
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.res) {
|
|
121
|
+
this.res.statusCode = stcode
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return this
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
moveFile(upf, target) {
|
|
128
|
+
return moveFile(this, upf, target)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {(fs.ReadStream|string)} filename
|
|
133
|
+
* @param {object} options
|
|
134
|
+
* */
|
|
135
|
+
pipe(filename, options={}) {
|
|
136
|
+
return ext.pipe(filename, this.res, options)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
pipeJson(filename) {
|
|
140
|
+
return this.setHeader('content-type', 'application/json').pipe(filename)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pipeText(filename, encoding='utf-8') {
|
|
144
|
+
return this.setHeader('content-type', `text/plain;charset=${encoding}`).pipe(filename)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pipeHtml(filename, encoding='utf-8') {
|
|
148
|
+
return this.setHeader('content-type', `text/html;charset=${encoding}`).pipe(filename)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
removeHeader(name) {
|
|
152
|
+
this.res.removeHeader(name)
|
|
153
|
+
return this
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
write(data) {
|
|
157
|
+
this.res.write(data)
|
|
158
|
+
return this
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Context.prototype.oo = Context.prototype.to
|
|
164
|
+
Context.prototype.ok = Context.prototype.to
|
|
165
|
+
|
|
166
|
+
module.exports = Context
|