upcore-tcp 0.0.1

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/lib/server.js ADDED
@@ -0,0 +1,712 @@
1
+ const crypto = require('crypto');
2
+ const net = require('net');
3
+ const tls = require('tls');
4
+
5
+ const package = require('../package.json');
6
+ const version = package.version;
7
+
8
+ const routeContext = require('./routeContext');
9
+
10
+ const checkHeader = require('./checkHeader');
11
+
12
+ const headerDecode = require('./headerDecode');
13
+ const cookieDecode = require('./cookieDecode');
14
+ const queryDecode = require('./queryDecode');
15
+ const decodeFrame = require('./decodeFrame');
16
+ const encodeFrame = require('./encodeFrame');
17
+ const checkType = require('./checkType');
18
+ const parameterFile = require('./parameterFile');
19
+ const streamFile = require('./streamFile');
20
+ const payloadDecode = require('./payloadDecode');
21
+ const payloadType = require('./payloadType');
22
+
23
+ const httpHeader = require('./httpHeader');
24
+
25
+ const checkPath = require('./checkPath');
26
+
27
+ const parsePath = require('./parsePath');
28
+
29
+ module.exports = class{
30
+ CALLBACK_ID = 0
31
+ CALLBACK_TREE = {}
32
+ ROUTE_TREE = {}
33
+ TYPE_TREE = {}
34
+
35
+ constructor(options){
36
+ this.config = {
37
+ hostname:'0.0.0.0',
38
+ port:3000,
39
+ keepAlive:true,
40
+ keepAliveInitialDelay:5000,
41
+ noDelay:true,
42
+ timeout:30000,
43
+ dev:false,
44
+ traffic:()=>{},
45
+ };
46
+
47
+ // ==== Options Config ====
48
+ // allowHalfOpen
49
+ // highWaterMark
50
+ // keepAlive
51
+ // keepAliveInitialDelay
52
+ // noDelay
53
+ // pauseOnConnect
54
+ // blockList
55
+ // SNICallback
56
+
57
+ if(typeof options == 'object'){
58
+ for(let x in options){
59
+ if(options[x] !== undefined){
60
+ this.config[x] = options[x];
61
+ }
62
+ }
63
+ }
64
+
65
+ if(options.logger){
66
+ this.config.traffic = ({connection, method, host, search, pathname}) => {
67
+ const d = new Date()
68
+ const pad = n => String(n).padStart(2, '0')
69
+
70
+ const time =
71
+ `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
72
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
73
+
74
+ const C = {
75
+ reset: '\x1b[0m',
76
+ gray: '\x1b[90m',
77
+ green: '\x1b[32m',
78
+ cyan: '\x1b[36m',
79
+ yellow: '\x1b[33m',
80
+ red: '\x1b[31m',
81
+ magenta: '\x1b[35m',
82
+ }
83
+
84
+ const methodColor = (
85
+ method === 'get' ? C.green :
86
+ method === 'post' ? C.cyan :
87
+ method === 'put' ? C.yellow :
88
+ method === 'delete' ? C.red :
89
+ C.magenta
90
+ )
91
+
92
+ console.log(
93
+ `${C.gray}${time}${C.reset} ` +
94
+ `[${methodColor}${connection === 2 ? 'websocket' : method}${C.reset}] ` +
95
+ `${host}${C.reset} ` +
96
+ `${C.green}${pathname}${C.reset}` +
97
+ (search ? `${C.yellow}?${search}${C.reset}` : '')
98
+ )
99
+ }
100
+ }
101
+
102
+ let root = new routeContext(this, '*');
103
+ for(let x of root.setup){
104
+ this[x] = (...arr)=>{
105
+ return root[x](...arr);
106
+ }
107
+ }
108
+
109
+ this._core = this.config.use === 'https' ? tls : net;
110
+ }
111
+ listen(callback){
112
+ (this.server = this._core.createServer(this.config))
113
+ .on('connection', (socket)=>{
114
+ let focus = 1;
115
+ let payloadLength = 0;
116
+ let req;
117
+
118
+ try{
119
+ socket
120
+ .setTimeout(this.config.timeout)
121
+ .on('data', (chunk)=>{
122
+ if(focus == 1){
123
+ req = checkHeader(chunk);
124
+ if(req === false){
125
+ socket.destroy();
126
+ return false;
127
+ }
128
+
129
+ switch(req.connection){
130
+ case 2:{
131
+ socket.setTimeout(0);
132
+ focus = 3;
133
+ break;
134
+ }
135
+ default:{
136
+ socket.setKeepAlive(this.config.keepAlive, this.config.keepAliveInitialDelay);
137
+ }
138
+ }
139
+
140
+ if(req.success){
141
+ payloadLength = 0;
142
+
143
+ this.handle(req, socket);
144
+ }else{
145
+ payloadLength = req.payload[0]?.length || 0;
146
+ focus = 2;
147
+ }
148
+ }else if(focus == 2){
149
+ payloadLength += chunk.length;
150
+ req.payload.push(chunk);
151
+
152
+ if(req.length == payloadLength){
153
+ payloadLength = 0;
154
+ focus = 1;
155
+ req.success = true;
156
+
157
+ this.handle(req, socket);
158
+ }else if(req.length < payloadLength){
159
+ socket.destroy();
160
+ return false;
161
+ }
162
+ }else if(focus == 3){
163
+ if(req.frame){
164
+ req.frame.decode(chunk);
165
+ }
166
+ }
167
+ })
168
+ .on('close', ()=>{
169
+ if(req?.userOut) req.userOut();
170
+ })
171
+ .on('error', (err)=>{
172
+ if(!socket.destroyed){
173
+ socket.destroy();
174
+ }
175
+ })
176
+ .on('timeout', ()=>{
177
+ if(!socket.destroyed){
178
+ socket.destroy();
179
+ }
180
+ });
181
+ }catch(e){
182
+ console.log(e);
183
+ socket.destroy();
184
+ }
185
+ })
186
+ .on('error', (e)=>{
187
+ console.log(e);
188
+ });
189
+
190
+ const cb = typeof callback === 'function' ? () => callback(this.server.address()) : this.config.dev ? ()=>{
191
+ let {address, port} = this.server.address();
192
+ console.log(`HTTP/1.1 server(\x1b[32mr938-upcore\x1b[0m/${version}) listening on ${address}:\x1b[36m${port}\x1b[0m`);
193
+ } : null;
194
+
195
+ if(this.config.hostname !== undefined){
196
+ this.server.listen(this.config.port, this.config.hostname, cb);
197
+ }else{
198
+ this.server.listen(this.config.port, cb);
199
+ }
200
+ }
201
+ createTool(req, socket){
202
+ req.response = {
203
+ status:200,
204
+ header:[],
205
+ };
206
+
207
+ req.get = (key)=>{
208
+ headerDecode(req);
209
+
210
+ return req.header[key] ? req.header[key][req.header[key].length - 1] : undefined;
211
+ }
212
+ req.set = (key, value)=>{
213
+ req.response.header.push(`${key}: ${value}\r\n`);
214
+
215
+ return req;
216
+ };
217
+ req.code = (status)=>{
218
+ req.response.status = status;
219
+
220
+ return req;
221
+ }
222
+ req.http = (data)=>{
223
+ if(data !== undefined){
224
+ socket.write(httpHeader(req.response.status) + req.response.header.join('') + httpHeader.CONTENT_LENGTH + Buffer.byteLength(data) + httpHeader.CRLF2 + data);
225
+ }else if(req.connection === 2){
226
+ socket.write(httpHeader(req.response.status) + req.response.header.join('') + httpHeader.CONNECTION_UPGRADE + httpHeader.CRLF);
227
+ }else{
228
+ socket.write(httpHeader(req.response.status) + req.response.header.join('') + httpHeader.CRLF);
229
+ }
230
+ }
231
+ req.socket = (call)=>{
232
+ call(socket);
233
+ }
234
+ req.write = (data)=>{
235
+ socket.write(data);
236
+ }
237
+ req.error = (status)=>{
238
+ req.code(status);
239
+ req.http();
240
+ socket.destroy();
241
+ }
242
+ req.gcookie = (name) => {
243
+ if(!req.cookie){
244
+ let cookie = req.get('cookie');
245
+ req.cookie = cookieDecode(cookie);
246
+ }
247
+
248
+ return req.cookie[name];
249
+ }
250
+ req.scookie = (name, value, option) => {
251
+ let text = [name + '=' + encodeURI((value || '').toString())];
252
+ for(let x in option){
253
+ if(['domain', 'expires', 'httponly', 'max-age', 'partitioned', 'path', 'secure', 'sameSite'].includes(x)){
254
+ if(['string','number'].includes(typeof option[x])){
255
+ text.push(x + '=' + encodeURI(option[x].toString()));
256
+ }else{
257
+ text.push(x);
258
+ }
259
+ }
260
+ }
261
+ req.set('Set-Cookie', text.join('; '));
262
+
263
+ return req;
264
+ }
265
+ req.rcookie = (name, option)=>{
266
+ if(typeof option != 'object'){
267
+ option = {};
268
+ }
269
+ option['max-age'] = '-1';
270
+
271
+ req.scookie(name, '', option);
272
+
273
+ return req;
274
+ }
275
+ req.look = (keys)=>{
276
+ queryDecode(req);
277
+
278
+ for(let x of keys.split(/[\s]*,[\s]*/)){
279
+ if(req.query[x] === undefined){
280
+ return false;
281
+ }
282
+ }
283
+ return true;
284
+ }
285
+ req.u = (key)=>{
286
+ queryDecode(req);
287
+
288
+ if(req.query[key] !== undefined){
289
+ if(Array.isArray(req.query[key])){
290
+ return req.query[key][req.query[key].length - 1];
291
+ }else{
292
+ return req.query[key];
293
+ }
294
+ }
295
+ return false;
296
+ }
297
+
298
+ if(req.connection === 2){
299
+ req.frame = new decodeFrame((data)=>{
300
+ req.body = data;
301
+
302
+ if(data.type == 'utf8'){
303
+ if((data.payload[0] == '{' && data.payload[data.payload.length - 1] == '}') || (data.payload[0] == '[' && data.payload[data.payload.length - 1] == ']')){
304
+ try{
305
+ data.json = JSON.parse(data.payload);
306
+ }catch(e){
307
+ data.json = {};
308
+ }
309
+ }
310
+ req.upgrade('data');
311
+ }else if(data.type == 'binary'){
312
+ req.upgrade('data');
313
+ }else if(data.type == 'close'){
314
+ socket.destroy();
315
+ }else if(data.type == 'ping'){
316
+ req.upgrade('ping');
317
+ }else if(data.type == 'pong'){
318
+ req.upgrade('pong');
319
+ }else{
320
+ req.upgrade('error');
321
+ }
322
+ });
323
+
324
+ req.start = async(uid)=>{
325
+ let seckey = req.get('Sec-WebSocket-Key');
326
+ let protocol = req.get('Sec-WebSocket-Protocol');
327
+ let version = parseInt(req.get('Sec-WebSocket-Version'));
328
+
329
+ if(version !== 13){
330
+ req.error(400);
331
+ return false;
332
+ }
333
+
334
+ let checkUser = req.userHas(uid);
335
+ if(checkUser !== false){
336
+ checkUser.stop();
337
+ await checkUser.userOut();
338
+ }
339
+
340
+ let acceptKey = crypto.createHash('sha1')
341
+ .update(seckey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
342
+ .digest('base64');
343
+
344
+ req.uid = uid;
345
+ req.userIn();
346
+
347
+ req.code(101)
348
+ .set('Sec-WebSocket-Accept', acceptKey);
349
+
350
+ if(protocol){
351
+ req.set('Sec-WebSocket-Protocol', protocol);
352
+ }
353
+
354
+ req.http();
355
+ return true;
356
+ }
357
+ req.userEach = (callback)=>{
358
+ if(req._client?.users){
359
+ for(let x in req._client.users){
360
+ callback(req._client.users[x]);
361
+ }
362
+ }
363
+ };
364
+ req.userHas = (uid)=>{
365
+ return req._client?.users && req._client.users[uid] ? req._client.users[uid] : false;
366
+ };
367
+ req.userLength = ()=>{
368
+ return req._client.length;
369
+ };
370
+ req.userIn = ()=>{
371
+ req._client.users[req.uid] = req;
372
+ req._client.length++;
373
+ req.userIn = ()=>{};
374
+ };
375
+ req.userOut = async()=>{
376
+ if(req._client?.users && req._client.users[req.uid]){
377
+ delete req._client.users[req.uid];
378
+ req._client.length--;
379
+ await req.upgrade('close');
380
+ }
381
+ req.userOut = ()=>{};
382
+ };
383
+
384
+ req.stop = ()=>{
385
+ socket.destroy();
386
+ }
387
+ req.encode = (text, opcode)=>{
388
+ return encodeFrame(text, opcode);
389
+ }
390
+ req.pong = ()=>{
391
+ let encode = req.encode('pong', 10);
392
+ req.write(encode);
393
+ }
394
+ req.ping = ()=>{
395
+ let encode = req.encode('ping', 9);
396
+ req.write(encode);
397
+ }
398
+ req.send = (text, opcode = 1)=>{
399
+ let encode = req.encode(text, opcode);
400
+ req.write(encode);
401
+ }
402
+ req.sends = (text, opcode = 1)=>{
403
+ let encode = req.encode(text, opcode);
404
+ req.userEach((user)=>{
405
+ user.write(encode);
406
+ });
407
+ }
408
+ req.sendto = (uid, text, opcode = 1)=>{
409
+ let user = req.userHas(uid);
410
+ if(user !== false){
411
+ user.send(text, opcode);
412
+ return true;
413
+ }
414
+ return false;
415
+ }
416
+ req.sendc = (text, opcode = 1, call)=>{
417
+ let encode = req.encode(text, opcode);
418
+ req.userEach((user)=>{
419
+ if(call(user)){
420
+ user.write(encode);
421
+ }
422
+ });
423
+ }
424
+
425
+ req.json = (json)=>{
426
+ req.send(JSON.stringify(json), 1);
427
+ }
428
+ req.jsons = (json)=>{
429
+ req.sends(JSON.stringify(json), 1);
430
+ }
431
+ req.jsonto = (uid, json)=>{
432
+ let user = req.userHas(uid);
433
+ if(user !== false){
434
+ user.json(json);
435
+ return true;
436
+ }
437
+ return false;
438
+ }
439
+ req.jsonc = (json, call)=>{
440
+ req.sendc(JSON.stringify(json), 1, call);
441
+ }
442
+ }else{
443
+ const TYPE_TEST = req.type.toString();
444
+
445
+ req.test = ({type, data, core})=>{
446
+ let parameter = {};
447
+ if(
448
+ (type == 'json' && !TYPE_TEST.startsWith('application/json')) ||
449
+ (type == 'formdata' && !TYPE_TEST.startsWith('multipart/form-data')) ||
450
+ (type == 'wwwform' && !TYPE_TEST.startsWith('application/x-www-form-urlencoded'))
451
+ ){
452
+ req.code(415).set('Content-Length', 79).set('Content-Type', 'application/json').http('{"status":false,"err":"Request Content-Type does not match the expected type."}');
453
+ return false;
454
+ }
455
+
456
+ if(typeof core == 'object'){
457
+ for(let id in core){
458
+ if(req[id] !== core[id]){
459
+ req.code(400).set('Content-Length', 78).set('Content-Type', 'application/json').http('{"status":false,"err":"The request does not satisfy the required conditions."}');
460
+ return false;
461
+ }
462
+ }
463
+ }
464
+
465
+ if(typeof data == 'object'){
466
+ if(typeof req.body != 'object'){
467
+ req.code(415).set('Content-Length', 61).set('Content-Type', 'application/json').http('{"status":false,"err":"Request body must be a valid object."}');
468
+ return false;
469
+ }
470
+
471
+ for(let id in data){
472
+ let {t, r} = data[id];
473
+ if(req.body[id] !== undefined){
474
+ let value = type == 'json' ? req.body[id] : req.q(id);
475
+ let test = checkType(this.TYPE_TREE[t] || {}, value);
476
+ if(!test){
477
+ req.code(400).set('Content-Length', 54).set('Content-Type', 'application/json').http('{"status":false,"err":"Invalid format for parameter."}');
478
+ return false;
479
+ }
480
+
481
+ parameter[id] = value;
482
+ }else if(r === true){
483
+ req.code(400).set('Content-Length', 54).set('Content-Type', 'application/json').http('{"status":false,"err":"Missing or invalid parameter."}');
484
+ return false;
485
+ }
486
+ }
487
+ }
488
+
489
+ return parameter;
490
+ }
491
+ req.check = (keys)=>{
492
+ for(let x of keys.split(/[\s]*,[\s]*/)){
493
+ if(req.body[x] === undefined){
494
+ return false;
495
+ }
496
+ }
497
+ return true;
498
+ }
499
+ req.q = (key)=>{
500
+ if(req.body && req.body[key] !== undefined){
501
+ if(Array.isArray(req.body[key])){
502
+ return req.body[key][req.body[key].length - 1];
503
+ }else{
504
+ return req.body[key];
505
+ }
506
+ }
507
+
508
+ return false;
509
+ }
510
+ req.send = (text)=>{
511
+ req.response.header.push(httpHeader.CONTENT_TYPE_PLAIN);
512
+ req.http(text);
513
+ }
514
+ req.html = (text)=>{
515
+ req.response.header.push(httpHeader.CONTENT_TYPE_HTML);
516
+
517
+ req.http(text);
518
+ }
519
+ req.json = (data)=>{
520
+ try{
521
+ let text = JSON.stringify(data);
522
+
523
+ req.response.header.push(httpHeader.CONTENT_TYPE_JSON);
524
+
525
+ req.http(text);
526
+ }catch(e){
527
+ req.error(500);
528
+ }
529
+ }
530
+ req.redirect = (a, b)=>{
531
+ const typeA = typeof a;
532
+ const typeB = typeof b;
533
+ let status = false;
534
+ let location = false;
535
+
536
+ if(typeA == 'number' && typeB == 'string'){
537
+ status = a;
538
+ location = b;
539
+ }else if(typeA == 'string'){
540
+ status = 301;
541
+ location = a;
542
+ }
543
+
544
+ if(status === false){
545
+ req.error(500);
546
+ return false;
547
+ }
548
+
549
+ req.code(status)
550
+ req.set('Location', location)
551
+ req.http();
552
+ socket.destroy();
553
+ }
554
+ req.file = (op)=>{
555
+ if(typeof op == 'string'){
556
+ op = {file: op};
557
+ }
558
+
559
+ if(typeof op.error !== 'function'){
560
+ op.error = (code)=>{
561
+ req.error(code)
562
+ }
563
+ }
564
+ if(op.download){
565
+ req.set('Content-Disposition', `attachment; filename="${op.download || path.basename(op.file)}"`);
566
+ }
567
+
568
+ if(op.parameter){
569
+ parameterFile(req, op.file, op.parameter, op.error);
570
+ }else{
571
+ streamFile(req, op.file, op.error);
572
+ }
573
+ }
574
+ req.load = (a, b)=>{
575
+ let callback = ()=>{};
576
+ let type = payloadType(req);
577
+
578
+ if(typeof a == 'string' && typeof b == 'function'){
579
+ if(a !== type){
580
+ req.error(400);
581
+ return false;
582
+ }
583
+ callback = b;
584
+ }else if(Array.isArray(a) && typeof b == 'function'){
585
+ if(!a.includes(type)){
586
+ req.error(400);
587
+ return false;
588
+ }
589
+ callback = b;
590
+ }else if(typeof a == 'function'){
591
+ callback = a;
592
+ }else{
593
+ req.error(400);
594
+ return false;
595
+ }
596
+
597
+ let load = payloadDecode(req, type);
598
+
599
+ if(load !== false){
600
+ req.body = load;
601
+ callback();
602
+ }else{
603
+ req.error(400);
604
+ }
605
+ }
606
+ }
607
+ }
608
+ handle(req, socket){
609
+ const client = checkPath(this.ROUTE_TREE, req, socket);
610
+
611
+ if(client !== false){
612
+
613
+ this.createTool(req, socket);
614
+ let i = 0;
615
+ req._client = client;
616
+ req.next = async()=>{
617
+ await this.CALLBACK_TREE[client.core[i++]](req, client.core.length == i ? 'start' : 'next');
618
+ };
619
+
620
+ if(req.connection === 2){
621
+ let endCore = this.CALLBACK_TREE[client.core[client.core.length - 1]] || (()=>{});
622
+ req.upgrade = async(a)=>{
623
+ await endCore(req, a);
624
+ };
625
+ }
626
+
627
+ this.config.traffic(req);
628
+
629
+ req.next();
630
+ }else{
631
+ socket.destroy();
632
+ }
633
+ }
634
+
635
+ type(id, {type, length, format, list, mime}){
636
+ this.TYPE_TREE[id] = {type, length, format, list, mime};
637
+ return this;
638
+ }
639
+ types(obj){
640
+ for(let x in obj){
641
+ let {type, length, format, list, mime} = obj[x];
642
+
643
+ this.TYPE_TREE[x] = {type, length, format, list, mime};
644
+ }
645
+ return this;
646
+ }
647
+ set(domains, methods, paths, ...cb){
648
+ domains = Array.isArray(domains) ? domains : [domains];
649
+ methods = Array.isArray(methods) ? methods : [methods];
650
+ paths = Array.isArray(paths) ? paths : [paths];
651
+
652
+ let handler = {core:[]};
653
+
654
+ for(let x of cb){
655
+ if(typeof x === 'function'){
656
+ let id = `__cb_${++this.CALLBACK_ID}`;
657
+
658
+ this.CALLBACK_TREE[id] = x;
659
+ handler.core.push(id);
660
+
661
+ }else if(typeof x === 'string'){
662
+ handler.core.push(x);
663
+ }
664
+ }
665
+
666
+ for(const domain of domains){
667
+ this.ROUTE_TREE[domain] ??= {};
668
+
669
+ for(const method of methods){
670
+ this.ROUTE_TREE[domain][method] ??= {};
671
+
672
+ if(method == 'websocket'){
673
+ handler.users = {};
674
+ handler.length = 0;
675
+ }
676
+
677
+ for(const pathname of paths){
678
+ let node = this.ROUTE_TREE[domain][method];
679
+ const parts = parsePath(pathname);
680
+
681
+ for(const p of parts){
682
+ if(p.type === 'static'){
683
+ node.static ??= {};
684
+ node.static[p.value] ??= {};
685
+ node = node.static[p.value];
686
+ }else if(p.type === 'param'){
687
+ node.param ??= { id: p.name };
688
+ node = node.param;
689
+ }else if(p.type === 'wildcard'){
690
+ node.wildcard ??= { wildcard: true };
691
+ node = node.wildcard;
692
+ break;
693
+ }
694
+ }
695
+
696
+ node.handler = handler;
697
+ }
698
+ }
699
+ }
700
+
701
+ return this;
702
+ }
703
+ domain(domain){
704
+ return new routeContext(this, domain);
705
+ }
706
+ add(id, cb){
707
+ if(!this.CALLBACK_TREE[id]){
708
+ this.CALLBACK_TREE[id] = cb;
709
+ }
710
+ return this;
711
+ }
712
+ }