total5 0.0.1 → 0.0.2

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/routing.js ADDED
@@ -0,0 +1,1028 @@
1
+ // The MIT License
2
+ // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
3
+
4
+ const NEWLINE = '\r\n';
5
+
6
+ const PROXY_KEEPALIVE = new F.Http.Agent({ keepAlive: true, timeout: 60000 });
7
+ const PROXY_KEEPALIVEHTTPS = new F.Https.Agent({ keepAlive: true, timeout: 60000 });
8
+ const PROXY_OPTIONS = { end: true };
9
+
10
+ function parseSizeTimeout(route, value) {
11
+
12
+ var number = +value.match(/\d+/)[0];
13
+ var type = value.match(/[a-z]+/i);
14
+
15
+ if (type)
16
+ type = type[0].toLowerCase();
17
+ else
18
+ type = '';
19
+
20
+ switch (type) {
21
+ case 's':
22
+ if (route.timeout < number)
23
+ route.timeout = number;
24
+ break;
25
+ case 'm':
26
+ number = number * 60;
27
+ if (route.timeout < number)
28
+ route.timeout = number;
29
+ break;
30
+ case 'h':
31
+ number = number * 60 * 60;
32
+ if (route.timeout < number)
33
+ route.timeout = number;
34
+ break;
35
+ case 'b':
36
+ if (route.size < number)
37
+ route.size = number;
38
+ break;
39
+ case 'kb':
40
+ number = number / 1024;
41
+ if (route.size < number)
42
+ route.size = number;
43
+ break;
44
+ case 'gb':
45
+ number = (number * 1024 * 1024) * 1000;
46
+ if (route.size < number)
47
+ route.size = number;
48
+ break;
49
+ case 'mb':
50
+ default:
51
+ number = number * 1024 * 1024;
52
+ if (route.size < number)
53
+ route.size = number;
54
+ break;
55
+ }
56
+ }
57
+
58
+ // Total.js routing
59
+ function Route(url, action, size) {
60
+
61
+ if (typeof(action) === 'number') {
62
+ size = action;
63
+ action = null;
64
+ }
65
+
66
+ // Apply a default API endpoint
67
+ url = url.replace(/\?/g, F.config.$api).replace(/\/{2,}/g, '/');
68
+
69
+ var t = this;
70
+
71
+ if (url[0] === '#') {
72
+ // internal routing
73
+ t.url = [url.substring(1)];
74
+ t.action = action;
75
+ t.fallback = true;
76
+ F.routes.fallback[t.url] = t;
77
+ return;
78
+ }
79
+
80
+ t.timeout = 0;
81
+ t.size = 0;
82
+ t.flags = {};
83
+
84
+ var index = url.indexOf(' ');
85
+
86
+ t.method = url.substring(0, index).toUpperCase();
87
+ url = url.substring(index + 1).trim();
88
+
89
+ index = url.indexOf(' ');
90
+
91
+ if (index === -1)
92
+ index = url.length;
93
+
94
+ t.url2 = url.substring(0, index);
95
+
96
+ if (t.url2[0] === '@') {
97
+ // @TODO: missing WAPI implementation
98
+ t.skip = true;
99
+ console.log('This "{0}" kind of routes are not supported yet'.format(t.url2));
100
+ return;
101
+ }
102
+
103
+ t.url = exports.split(t.url2, true);
104
+ url = url.substring(index + 1);
105
+ t.id = t.method + '/' + t.url.join('/');
106
+
107
+ t.auth = 0;
108
+ t.action = action;
109
+
110
+ switch (t.method[0]) {
111
+ case '+':
112
+ t.auth = 1;
113
+ t.method = t.method.substring(1);
114
+ break;
115
+ case '-':
116
+ t.auth = 2;
117
+ t.method = t.method.substring(1);
118
+ break;
119
+ }
120
+
121
+ if (t.method === 'FILE') {
122
+
123
+ let types = t.url[t.url.length - 1];
124
+
125
+ // fixed filename
126
+ if (t.url2.indexOf('*') === -1) {
127
+ t.fixed = true;
128
+ } else if (types === '*') {
129
+ t.url[t.url.length - 1] = '*';
130
+ } else if (types === '*.*') {
131
+ t.url.splice(t.url.length - 1, 1);
132
+ } else {
133
+ t.url.splice(t.url.length - 1, 1);
134
+ index = types.lastIndexOf('.');
135
+ types = types.substring(index + 1).split('|');
136
+ t.ext = {};
137
+ for (let type of types)
138
+ t.ext[type] = 1;
139
+ }
140
+
141
+ } else {
142
+ t.params = [];
143
+ index = 0;
144
+ for (let path of t.url) {
145
+ if (path[0] === '{') {
146
+ let tmp = path.split(':').trim();
147
+ t.params.push({ name: tmp[0].replace(/\{|\}/g, ''), type: tmp[1] || 'string', index: index });
148
+ }
149
+ index++;
150
+ }
151
+ t.size = size || 0;
152
+ }
153
+
154
+ t.middleware = [];
155
+
156
+ index = t.url.indexOf('*');
157
+ t.wildcard = index != -1;
158
+
159
+ if (index != -1)
160
+ t.url.splice(index, 1);
161
+
162
+ if (!t.url.length)
163
+ t.url[0] = '/';
164
+
165
+ url = url.replace(/<\d+[mb|gb|kb|b|m|s|h]+/gi, function(text) {
166
+ parseSizeTimeout(t, text.substring(1));
167
+ return '';
168
+ });
169
+
170
+ t.priority = 100;
171
+ t.type = t.method === 'WEBSOCKET' || t.method === 'SOCKET' ? 'websocket' : t.method === 'FILE' ? 'file' : 'route';
172
+ t.partial = t.method === 'PATCH';
173
+
174
+ var endpoint = '';
175
+ var isapi = false;
176
+
177
+ if (t.method === 'API') {
178
+ t.method = 'POST';
179
+ isapi = true;
180
+ t.id = t.id.replace(/^(\+|-)/, '');
181
+ url = url.replace(/(\*|\+|-|%)?[a-z0-9-_/{}]+/i, function(text) {
182
+ let tmp = text.trim();
183
+ let c = tmp[0];
184
+ endpoint = c === '%' || c === '+' || c === '*' || c === '-' ? tmp.substring(1) : tmp;
185
+ t.partial = tmp[0] === '%';
186
+ return text;
187
+ });
188
+ }
189
+
190
+ // Parse flags
191
+ url = url.replace(/(@|#)+[a-z0-9]+/gi, function(text) {
192
+ let tmp = text.trim();
193
+ if (tmp[0] === '#') {
194
+ t.middleware.push(tmp.substring(1));
195
+ } else {
196
+ t.flags[tmp.substring(1)] = 1;
197
+ t.priority--;
198
+ }
199
+ return '';
200
+ }).trim();
201
+
202
+ // Parse actions
203
+ index = url.indexOf('-->');
204
+
205
+ if (index !== -1) {
206
+ t.actions = [];
207
+ url = url.substring(index + 3).replace(/(\+|-|%)?[a-z0-9-_/]+(\s\(response\))?/gi, function(text) {
208
+ t.actions.push(text.trim());
209
+ return '';
210
+ }).trim();
211
+ } else {
212
+ if (isapi)
213
+ t.actions = [url.replace(/\+|-|%|#/g, '').trim()];
214
+ else
215
+ t.actions = url.substring(0, index + 3).replace(/\s{2,}/g, ' ').split(/\s|,/);
216
+ }
217
+
218
+ var parent = null;
219
+
220
+ if (endpoint) {
221
+
222
+ let params = [];
223
+ let arr = endpoint.split('/');
224
+
225
+ for (let i = 1; i < arr.length; i++) {
226
+ let param = arr[i].replace(/\{|\}/g, '').trim();
227
+ params.push({ index: i, name: param });
228
+ }
229
+
230
+ parent = F.routes.routes.findItem('id', t.id);
231
+
232
+ var apiroute = { auth: t.auth, params: params, actions: t.actions.join(','), action: action, timeout: t.timeout };
233
+
234
+ t.apiendpoint = arr[0];
235
+
236
+ if (parent) {
237
+ parent.api[arr[0]] = apiroute;
238
+ t.skip = true;
239
+ t.parent = parent;
240
+ } else {
241
+ if (!t.api)
242
+ t.api = {};
243
+ t.api[arr[0]] = apiroute;
244
+ }
245
+
246
+ delete t.actions;
247
+
248
+ // Reset auth
249
+ t.auth = 0;
250
+
251
+ } else
252
+ t.actions = t.actions.join(',');
253
+
254
+ // Max. payload size
255
+ if (!t.size) {
256
+ switch (t.type) {
257
+ case 'websocket':
258
+ t.size = F.config.$wsmaxsize * 1024;
259
+ t.connections = [];
260
+ break;
261
+ case 'route':
262
+ t.size = F.config.$httpmaxsize * 1024;
263
+ break;
264
+ }
265
+ }
266
+
267
+ if (parent && parent.size < t.size)
268
+ parent.size = t.size;
269
+
270
+ if (typeof(t.action) === 'string') {
271
+ t.view = t.action;
272
+ t.action = null;
273
+ }
274
+
275
+ if (!t.view && !t.action && t.method !== 'FILE' && t.method !== 'SOCKET')
276
+ t.view = t.url[0] && t.url[0] !== '/' ? t.url[0] : 'index';
277
+
278
+ if (t.wildcard)
279
+ t.priority -= 50;
280
+
281
+ }
282
+
283
+ Route.prototype.compare = function(ctrl) {
284
+ var url = this.url;
285
+ for (var i = 0; i < url.length; i++) {
286
+ let path = url[i];
287
+ if (path[0] !== '{' && path !== ctrl.split2[i])
288
+ return false;
289
+ }
290
+ return true;
291
+ };
292
+
293
+ Route.prototype.remove = function() {
294
+
295
+ var self = this;
296
+ var index = -1;
297
+
298
+ switch (self.type) {
299
+ case 'websocket':
300
+ index = F.routes.websockets.indexOf(self);
301
+ if (index !== -1)
302
+ F.routes.websockets.splice(index);
303
+ for (let conn of self.connections)
304
+ conn.destroy();
305
+ break;
306
+ case 'file':
307
+ index = F.routes.files.indexOf(self);
308
+ if (index !== -1)
309
+ F.routes.files.splice(index);
310
+ break;
311
+ default:
312
+ if (self.apiendpoint) {
313
+ if (self.parent) {
314
+ delete self.parent.api[self.apiendpoint];
315
+ if (Object.keys(self.parent.api).length == 0) {
316
+ index = F.routes.routes.indexOf(self.parent);
317
+ if (index !== -1)
318
+ F.routes.routes.splice(index);
319
+ }
320
+ } else {
321
+ delete self.api[self.apiendpoint];
322
+ if (Object.keys(self.api).length == 0) {
323
+ index = F.routes.routes.indexOf(self);
324
+ if (index !== -1)
325
+ F.routes.routes.splice(index);
326
+ }
327
+ }
328
+ } else {
329
+ index = F.routes.routes.indexOf(self);
330
+ if (index !== -1)
331
+ F.routes.routes.splice(index, 1);
332
+ }
333
+ break;
334
+ }
335
+
336
+ exports.sort();
337
+ };
338
+
339
+ exports.route = function(url, action, size) {
340
+ var route = new Route(url, action, size);
341
+ if (route && !route.skip) {
342
+ switch (route.type) {
343
+ case 'websocket':
344
+ F.routes.websockets.push(route);
345
+ break;
346
+ case 'file':
347
+ F.routes.files.push(route);
348
+ break;
349
+ default:
350
+ F.routes.routes.push(route);
351
+ break;
352
+ }
353
+ F.routes.timeout && clearTimeout(F.routes.timeout);
354
+ F.routes.timeout = setTimeout(exports.sort, 100);
355
+ }
356
+ return route;
357
+ };
358
+
359
+ exports.cors = function(url) {
360
+ F.config.$cors = url;
361
+ };
362
+
363
+ exports.sort = function() {
364
+
365
+ var cache = {};
366
+ var tmp;
367
+ var key;
368
+
369
+ F.routes.routes.quicksort('priority_desc');
370
+ F.routes.files.quicksort('priority_desc');
371
+ F.routes.websockets.quicksort('priority_desc');
372
+
373
+ for (let route of F.routes.routes) {
374
+
375
+ key = route.method;
376
+ tmp = cache[key];
377
+
378
+ if (!tmp)
379
+ tmp = cache[key] = {};
380
+
381
+ var u = [];
382
+ for (let path of route.url)
383
+ u.push(path.replace(/\{.*?\}/g, '{}'));
384
+
385
+ if (route.wildcard)
386
+ u.push('*');
387
+
388
+ var k = u.join('/').replace(/\/\//g, '/');
389
+
390
+ if (k.indexOf('{') !== -1)
391
+ k = 'D';
392
+
393
+ if (tmp[k])
394
+ tmp[k].push(route);
395
+ else
396
+ tmp[k] = [route];
397
+ }
398
+
399
+ F.routes.routescache = cache;
400
+ cache = {};
401
+
402
+ for (let route of F.routes.websockets) {
403
+
404
+ tmp = cache;
405
+
406
+ var u = [];
407
+ for (let path of route.url)
408
+ u.push(path.replace(/\{.*?\}/g, '{}'));
409
+
410
+ if (route.wildcard)
411
+ u.push('*');
412
+
413
+ var k = u.join('/').replace(/\/\//g, '/');
414
+
415
+ if (k.indexOf('{') !== -1)
416
+ k = 'D';
417
+
418
+ if (tmp[k])
419
+ tmp[k].push(route);
420
+ else
421
+ tmp[k] = [route];
422
+ }
423
+
424
+ F.routes.websocketscache = cache;
425
+ cache = {};
426
+
427
+ for (let route of F.routes.files) {
428
+
429
+ if (route.fixed) {
430
+ cache[route.url2] = route;
431
+ continue;
432
+ }
433
+
434
+ tmp = cache;
435
+ let key = route.url.join('/');
436
+
437
+ if (route.wildcard)
438
+ key += '/*';
439
+
440
+ if (cache[key])
441
+ cache[key].push(route);
442
+ else
443
+ cache[key] = [route];
444
+ }
445
+
446
+ F.routes.filescache = cache;
447
+ DEBUG && F.TSourceMap.refresh();
448
+ };
449
+
450
+ function compareflags(ctrl, routes, auth) {
451
+
452
+ for (let route of routes) {
453
+
454
+ if (auth && route.auth && route.auth !== auth)
455
+ continue;
456
+
457
+ if (route.flags.referrer || route.flags.referer) {
458
+ if (!ctrl.headers.referer || ctrl.headers.referer.indexOf(ctrl.headers.host) === -1)
459
+ continue;
460
+ }
461
+
462
+ if (route.flags.json && ctrl.datatype !== 'json')
463
+ continue;
464
+
465
+ if (route.flags.xml && ctrl.datatype !== 'xml')
466
+ continue;
467
+
468
+ if (route.flags.debug && !DEBUG)
469
+ continue;
470
+
471
+ if (route.flags.release && DEBUG)
472
+ continue;
473
+
474
+ if (route.flags.xhr && !ctrl.xhr)
475
+ continue;
476
+
477
+ if (route.flags.upload && ctrl.datatype !== 'multipart')
478
+ continue;
479
+
480
+ if (route.flags.mobile && !ctrl.mobile)
481
+ continue;
482
+
483
+ if (route.flags.robot && !ctrl.robot)
484
+ continue;
485
+
486
+ return route;
487
+ }
488
+ }
489
+
490
+ exports.lookup = function(ctrl, auth = 0, skip = false) {
491
+
492
+ // auth 0: does not matter
493
+ // auth 1: logged
494
+ // auth 2: unlogged
495
+
496
+ var key = ctrl.method;
497
+ var tmp = F.routes.routescache[key];
498
+ if (!tmp)
499
+ return null;
500
+
501
+ var arr = ctrl.split;
502
+ var length = arr.length;
503
+ var route;
504
+ var item;
505
+
506
+ var url = (ctrl.uri.key[ctrl.uri.key.length - 1] === '/' ? ctrl.uri.key.substring(1, ctrl.uri.key.length - 1) : ctrl.uri.key.substring(1));
507
+
508
+ // Checks fixed URL
509
+ var routes = tmp[url];
510
+ if (routes) {
511
+ if (skip)
512
+ return routes[0];
513
+ route = compareflags(ctrl, routes, auth);
514
+ if (route)
515
+ return route;
516
+ routes = null;
517
+ }
518
+
519
+ // Dynamic routes
520
+ if (tmp.D && !(length === 1 && arr[0] === '/')) {
521
+ for (let r of tmp.D) {
522
+ if (r.url.length === length || r.wildcard) {
523
+ if (r.compare(ctrl)) {
524
+ if (!routes)
525
+ routes = [];
526
+ if (skip)
527
+ return r;
528
+ routes.push(r);
529
+ }
530
+ }
531
+ }
532
+ if (routes) {
533
+ if (skip)
534
+ return routes[0];
535
+ route = compareflags(ctrl, routes, auth);
536
+ if (route)
537
+ return route;
538
+ }
539
+ routes = null;
540
+ }
541
+
542
+ // Wildcard
543
+ routes = [];
544
+ for (var i = 0; i < length; i++) {
545
+ var url = ctrl.split2.slice(0, length - i).join('/') + '/*';
546
+ item = tmp[url];
547
+ if (item) {
548
+ if (skip)
549
+ return item[0];
550
+ routes.push.apply(routes, item);
551
+ }
552
+ }
553
+
554
+ item = tmp['/*'];
555
+ if (item) {
556
+ if (skip)
557
+ return item[0];
558
+ routes.push.apply(routes, item);
559
+ }
560
+
561
+ return routes && routes.length ? compareflags(ctrl, routes, auth) : route;
562
+ };
563
+
564
+ exports.split = function(url, lowercase) {
565
+
566
+ if (lowercase)
567
+ url = url.toLowerCase();
568
+
569
+ if (url[0] === '/')
570
+ url = url.substring(1);
571
+
572
+ if (url[url.length - 1] === '/')
573
+ url = url.substring(0, url.length - 1);
574
+
575
+ var count = 0;
576
+ var end = 0;
577
+ var arr = [];
578
+
579
+ for (var i = 0; i < url.length; i++) {
580
+ switch (url[i]) {
581
+ case '/':
582
+ if (count === 0) {
583
+ arr.push(url.substring(end + (arr.length ? 1 : 0), i));
584
+ end = i;
585
+ }
586
+ break;
587
+ case '{':
588
+ count++;
589
+ break;
590
+ case '}':
591
+ count--;
592
+ break;
593
+ }
594
+ }
595
+
596
+ if (!count)
597
+ arr.push(url.substring(end + (arr.length ? 1 : 0), url.length));
598
+
599
+ if (arr.length === 1 && !arr[0])
600
+ arr[0] = '/';
601
+
602
+ return arr;
603
+ };
604
+
605
+ exports.lookupcors = function(ctrl) {
606
+
607
+ // Custom handling
608
+ if (F.def.onCORS) {
609
+ F.def.onCORS(ctrl);
610
+ return false;
611
+ }
612
+
613
+ let origin = ctrl.headers.origin;
614
+
615
+ if (F.config.$cors !== '*' && !origin.endsWith(ctrl.headers.host)) {
616
+
617
+ let resume = false;
618
+ let cors = F.temporary.cors;
619
+
620
+ if (cors) {
621
+ if (cors.strict.length && cors.strict.includes(origin)) {
622
+ resume = true;
623
+ } else if (cors.wildcard.length) {
624
+ for (let m of cors.wildcard) {
625
+ if (origin.includes(m)) {
626
+ resume = true;
627
+ break;
628
+ }
629
+ }
630
+ }
631
+ }
632
+
633
+ if (!resume) {
634
+ ctrl.fallback(400, 'Invalid origin (CORS)');
635
+ return false;
636
+ }
637
+ }
638
+
639
+ ctrl.response.headers['access-control-allow-origin'] = origin;
640
+ ctrl.response.headers['access-control-allow-credentials'] = 'true';
641
+ ctrl.response.headers['access-control-allow-methods'] = '*';
642
+ ctrl.response.headers['access-control-allow-headers'] = '*';
643
+ ctrl.response.headers['access-control-expose-headers'] = '*';
644
+
645
+ if (ctrl.method === 'OPTIONS') {
646
+ ctrl.flush();
647
+ return false;
648
+ }
649
+
650
+ // Continue
651
+ return true;
652
+
653
+ };
654
+
655
+ exports.lookupfile = function(ctrl, auth = 0) {
656
+ if (F.routes.files.length) {
657
+
658
+ // fixed
659
+ let route = F.routes.filescache[ctrl.url];
660
+ if (route)
661
+ return route;
662
+
663
+ let key = '';
664
+ for (let i = 0; i < ctrl.split2.length - 1; i++)
665
+ key += (i ? '/' : '') + ctrl.split2[i];
666
+ let routes = F.routes.filescache[key];
667
+ if (routes)
668
+ return compareflags(ctrl, routes, auth);
669
+
670
+ // Wildcard
671
+ let length = ctrl.split2.length;
672
+ routes = [];
673
+
674
+ for (let i = 0; i < length; i++) {
675
+ let url = ctrl.split2.slice(0, length - i).join('/') + '/*';
676
+ let item = F.routes.filescache[url];
677
+ if (item)
678
+ return item[0];
679
+ }
680
+ }
681
+ };
682
+
683
+ exports.lookupwebsocket = function(ctrl, auth = 0, skip = false) {
684
+
685
+ // auth 0: does not matter
686
+ // auth 1: logged
687
+ // auth 2: unlogged
688
+
689
+ var tmp = F.routes.websocketscache;
690
+ var arr = ctrl.split2;
691
+ var length = arr.length;
692
+ var route;
693
+ var item;
694
+
695
+ var url = (ctrl.uri.key[ctrl.uri.key.length - 1] === '/' ? ctrl.uri.key.substring(1, ctrl.uri.key.length - 1) : ctrl.uri.key.substring(1));
696
+
697
+ // Checks fixed URL
698
+ var routes = tmp[url];
699
+ if (routes) {
700
+ if (skip)
701
+ return routes[0];
702
+ route = compareflags(ctrl, routes, auth);
703
+ if (route)
704
+ return route;
705
+ routes = null;
706
+ }
707
+
708
+ // Dynamic routes
709
+ if (tmp.D) {
710
+ for (var i = 0; i < tmp.D.length; i++) {
711
+ var r = tmp.D[i];
712
+ if (r.url.length === length || r.wildcard) {
713
+ if (r.compare(ctrl)) {
714
+ if (!routes)
715
+ routes = [];
716
+ routes.push(r);
717
+ }
718
+ }
719
+ }
720
+
721
+ if (routes) {
722
+ if (skip)
723
+ return routes[0];
724
+ route = compareflags(ctrl, routes, auth);
725
+ if (route)
726
+ return route;
727
+ }
728
+ routes = null;
729
+ }
730
+
731
+ // Wildcard
732
+ routes = [];
733
+ for (var i = 0; i < length; i++) {
734
+ var url = ctrl.split2.slice(0, length - i).join('/') + '/*';
735
+ item = tmp[url];
736
+ if (item) {
737
+ if (skip)
738
+ return item[0];
739
+ routes.push.apply(routes, item);
740
+ }
741
+ }
742
+
743
+ item = tmp['/*'];
744
+ if (item) {
745
+ if (skip)
746
+ return item[0];
747
+ routes.push.apply(routes, item);
748
+ }
749
+
750
+ return routes && routes.length ? compareflags(ctrl, routes, auth) : null;
751
+ };
752
+
753
+ function Proxy(url, target) {
754
+
755
+ var t = this;
756
+
757
+ t.url = url.toLowerCase();
758
+ t.copypath = 'none'; // replace|extend|none
759
+
760
+ if ((/^(https|http):\/\//).test(target))
761
+ t.target = F.Url.parse(target);
762
+ else
763
+ t.target = { socketPath: target };
764
+
765
+ if (t.target.href) {
766
+ let index = t.target.href.indexOf('?');
767
+ if (index !== -1)
768
+ t.query = t.target.href.substring(index + 1);
769
+ t.path = t.target.pathname[t.target.pathname.length - 1] === '/' ? t.target.pathname.substring(0, t.target.pathname.length - 1) : t.target.pathname;
770
+ }
771
+
772
+ t.uri = t.target;
773
+ }
774
+
775
+ Proxy.prototype.copy = function(type) {
776
+
777
+ // @type {String} none|replace|extend
778
+
779
+ // "none":
780
+ // PROXY('/cl/', 'https://yourdomain.com');
781
+ // GET /cl/?q=search --> https://yourdomain.com/?q=search');
782
+ // GET /cl/something/?q=search --> https://yourdomain.com/?q=search');
783
+
784
+ // "replace":
785
+ // PROXY('/cl/', 'https://yourdomain.com');
786
+ // GET /cl/?q=search --> https://yourdomain.com/');
787
+ // GET /cl/something/?q=search --> https://yourdomain.com/something/');
788
+
789
+ // "extend":
790
+ // PROXY('/cl/', 'https://yourdomain.com');
791
+ // GET /cl/?q=search --> https://yourdomain.com/?q=search');
792
+ // GET /cl/something/?q=search --> https://yourdomain.com/something/?q=search');
793
+
794
+ var t = this;
795
+ if (type === 'replace' && t.target.pathname.length > 1)
796
+ type = 'extend';
797
+
798
+ t.copypath = type;
799
+ return t;
800
+ };
801
+
802
+ Proxy.prototype.after = function(callback) {
803
+ // callback(response)
804
+ var t = this;
805
+ t.$after = callback;
806
+ return t;
807
+ };
808
+
809
+ Proxy.prototype.timeout = function(timeout) {
810
+ var t = this;
811
+ t.$timeout = timeout ? timeout / 1000 : 0;
812
+ return t;
813
+ };
814
+
815
+ Proxy.prototype.check = function(callback) {
816
+ var t = this;
817
+ // callback(ctrl)
818
+ t.$check = callback;
819
+ return t;
820
+ };
821
+
822
+ Proxy.prototype.before = function(callback) {
823
+ // callback(uri, ctrl)
824
+ var t = this;
825
+ t.$before = callback;
826
+ return t;
827
+ };
828
+
829
+ Proxy.prototype.remove = function() {
830
+ var t = this;
831
+ var index = F.routes.proxies.indexOf(t);
832
+ if (index !== -1)
833
+ F.routes.proxies.splice(index, 1);
834
+ };
835
+
836
+ exports.proxy = function(url, target) {
837
+
838
+ if (!target) {
839
+ let index = F.routes.proxies.TfindIndex('url', url.toLowerCase());
840
+ if (index !== -1)
841
+ F.routes.proxies.splice(index, 1);
842
+ return;
843
+ }
844
+
845
+ let proxy = new Proxy(url, target);
846
+ F.routes.proxies.push(proxy);
847
+ return proxy;
848
+ };
849
+
850
+ exports.lookupproxy = function(ctrl) {
851
+ for (var proxy of F.routes.proxies) {
852
+ var u = ctrl.uri.key.substring(0, proxy.url.length);
853
+ if (u[u.length - 1] !== '/')
854
+ u += '/';
855
+ if (u === proxy.url && (!proxy.$check || proxy.$check(ctrl))) {
856
+ F.stats.response.proxy++;
857
+ proxycreate(proxy, ctrl);
858
+ return true;
859
+ }
860
+ }
861
+ };
862
+
863
+ function proxyheadersws(header, headers) {
864
+
865
+ var output = [];
866
+
867
+ for (let key in headers) {
868
+ var value = headers[key];
869
+ if (value instanceof Array) {
870
+ for (let item of value)
871
+ output.push(key + ': ' + item);
872
+ } else
873
+ output.push(key + ': ' + value);
874
+ }
875
+
876
+ output.unshift(header);
877
+ return output.join(NEWLINE) + NEWLINE + NEWLINE;
878
+ }
879
+
880
+ function proxycreate(proxy, ctrl) {
881
+
882
+ var secured = proxy.uri.protocol === 'https:';
883
+ var uri = {};
884
+
885
+ if (proxy.uri.host) {
886
+ uri.host = proxy.uri.host;
887
+ uri.hostname = proxy.uri.hostname;
888
+ } else
889
+ uri.socketPath = proxy.uri.socketPath;
890
+
891
+ var tmp;
892
+
893
+ uri.method = ctrl.method === 'SOCKET' ? 'GET' : ctrl.method;
894
+ uri.headers = ctrl.headers;
895
+ ctrl.$proxy = proxy;
896
+
897
+ if (uri.socketPath) {
898
+ uri.path = (proxy.copypath == 'none' || proxy.copypath === 'replace' ? ctrl.url.substring(proxy.url.length - 1) : ctrl.uri.pathname) + (ctrl.uri.search ? ((proxy.uri.search && proxy.uri.search.length > 1 ? '&' : '?') + ctrl.uri.search) : '');
899
+ } else {
900
+
901
+ if (proxy.copypath === 'none') {
902
+ uri.path = proxy.uri.path + (ctrl.uri.search ? ((proxy.uri.search && proxy.uri.search.length > 1 ? '&' : '?') + ctrl.uri.search) : '');
903
+ } else if (proxy.copypath === 'replace') {
904
+ uri.path = ctrl.url.substring(proxy.url.length - 1);
905
+ } else if (proxy.copypath === 'extend') {
906
+ tmp = ctrl.uri.pathname.substring(proxy.url.length) + (ctrl.uri.search ? ('?' + ctrl.uri.search) : '');
907
+ uri.path = proxy.path + (tmp ? ((tmp[0] === '/' ? '' : '/') + tmp) : '') + (proxy.query ? (ctrl.uri.search ? ('&' + proxy.query) : ('?' + proxy.query)) : '');
908
+ } else {
909
+ tmp = ctrl.uri.pathname + (ctrl.uri.search ? ('?' + ctrl.uri.search) : '');
910
+ uri.path = proxy.path + (tmp ? ((tmp[0] === '/' ? '' : '/') + tmp) : '') + (proxy.query ? (ctrl.uri.search ? ('&' + proxy.query) : ('?' + proxy.query)) : '');
911
+ }
912
+
913
+ if (proxy.uri.port)
914
+ uri.port = proxy.uri.port;
915
+ }
916
+
917
+ if (!ctrl.iswebsocket && uri.headers.connection)
918
+ delete uri.headers.connection;
919
+
920
+ uri.headers['x-forwarded-for'] = ctrl.ip;
921
+ uri.headers['x-forwarded-url'] = ctrl.url;
922
+ uri.headers['x-forwarded-host'] = ctrl.headers.host;
923
+ uri.agent = secured ? PROXY_KEEPALIVEHTTPS : PROXY_KEEPALIVE;
924
+
925
+ delete uri.headers.host;
926
+
927
+ proxy.$before && proxy.$before(uri, ctrl);
928
+ F.stats.performance.external++;
929
+ F.stats.request.external++;
930
+
931
+ // ctrl.res {HttpResponse} or {Socket}
932
+ if (ctrl.res.headersSent || ctrl.destroyed)
933
+ return;
934
+
935
+ var get = (ctrl.method === 'GET' || ctrl.method === 'HEAD' || ctrl.method === 'OPTIONS');
936
+ var kind = secured ? F.Https : F.Http;
937
+ var request = get && !ctrl.iswebsocket ? kind.get(uri, proxycreatecallback) : kind.request(uri, proxycreatecallback);
938
+
939
+ request.on('error', proxyerror);
940
+ request.on('abort', proxyerror);
941
+ request.on('aborted', proxyerror);
942
+
943
+ request.$controller = ctrl;
944
+ request.$proxy = proxy;
945
+ request.$timeout = proxy.timeout || F.config.$proxytimeout;
946
+ request.iswebsocket = !!ctrl.iswebsocket;
947
+ request.$destroy = proxyerror;
948
+
949
+ if (!ctrl.iswebsocket && proxy.timeout) {
950
+ F.temporary.pending.push(ctrl);
951
+ F.TUtils.onfinished(ctrl.res, function() {
952
+ request.$destroyed = true;
953
+ ctrl.destroyed = true;
954
+ });
955
+ }
956
+
957
+ if (ctrl.iswebsocket) {
958
+
959
+ ctrl.res.setTimeout(0);
960
+ ctrl.res.setNoDelay(true);
961
+ ctrl.res.setKeepAlive(true, 0);
962
+
963
+ ctrl.iswebsocket && ctrl.head && ctrl.res.unshift(ctrl.head);
964
+ request.on('response', function(proxyres) {
965
+
966
+ if (request.$destroyed)
967
+ return;
968
+
969
+ if (!proxyres.upgrade) {
970
+ ctrl.res.write(proxyheadersws('HTTP/' + proxyres.httpVersion + ' ' + proxyres.statusCode + ' ' + proxyres.statusMessage, proxyres.headers));
971
+ proxyres.pipe(ctrl.res);
972
+ }
973
+
974
+ });
975
+
976
+ request.on('upgrade', function(proxyres, proxysocket, proxyhead) {
977
+
978
+ proxysocket.on('close', function() {
979
+ ctrl.destroy();
980
+ request.destroy();
981
+ proxyres.destroy();
982
+ });
983
+
984
+ if (request.$destroyed)
985
+ return;
986
+
987
+ if (proxyhead && proxyhead.length)
988
+ proxysocket.unshift(proxyhead);
989
+
990
+ ctrl.res.write(proxyheadersws('HTTP/1.1 101 Switching Protocols', proxyres.headers));
991
+ proxysocket.pipe(ctrl.res).pipe(proxysocket);
992
+ });
993
+ }
994
+
995
+ if (get)
996
+ request.end();
997
+ else
998
+ ctrl.req.pipe(request, PROXY_OPTIONS);
999
+ }
1000
+
1001
+ function proxydestroy(self, err) {
1002
+ if (!self.$destroyed) {
1003
+ self.$destroyed = true;
1004
+ if (self.$controller) {
1005
+ if (self.$controller.socket)
1006
+ self.$controller.destroy();
1007
+ else
1008
+ self.$controller.fallback(503, err);
1009
+ }
1010
+ self.destroy();
1011
+ }
1012
+ }
1013
+
1014
+ function proxyerror(err) {
1015
+ proxydestroy(this, err);
1016
+ }
1017
+
1018
+ function proxycreatecallback(response) {
1019
+ var self = this;
1020
+ if (!self.$destroyed) {
1021
+ let ctrl = self.$controller;
1022
+ ctrl.released = true;
1023
+ ctrl.destroyed = true;
1024
+ ctrl.$proxy.$after && self.$proxy.$after(response);
1025
+ ctrl.res.writeHead && ctrl.res.writeHead(response.statusCode, response.headers);
1026
+ response.pipe(ctrl.res, PROXY_OPTIONS);
1027
+ }
1028
+ }