ultimate-express 1.2.16 → 1.2.19
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/EXPRESS_LICENSE +25 -25
- package/LICENSE +201 -201
- package/README.md +315 -315
- package/package.json +87 -87
- package/src/application.js +308 -302
- package/src/index.js +43 -43
- package/src/middlewares.js +330 -316
- package/src/request.js +402 -402
- package/src/response.js +769 -762
- package/src/router.js +546 -541
- package/src/utils.js +338 -338
- package/src/view.js +118 -118
- package/src/worker.js +30 -30
package/src/response.js
CHANGED
|
@@ -1,763 +1,770 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2024 dimden.dev
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const cookie = require("cookie");
|
|
18
|
-
const mime = require("mime-types");
|
|
19
|
-
const vary = require("vary");
|
|
20
|
-
const encodeUrl = require("encodeurl");
|
|
21
|
-
const {
|
|
22
|
-
normalizeType, stringify, deprecated, UP_PATH_REGEXP, decode,
|
|
23
|
-
containsDotFile, isPreconditionFailure, isRangeFresh, parseHttpDate
|
|
24
|
-
} = require("./utils.js");
|
|
25
|
-
const { Writable } = require("stream");
|
|
26
|
-
const { isAbsolute } = require("path");
|
|
27
|
-
const fs = require("fs");
|
|
28
|
-
const Path = require("path");
|
|
29
|
-
const statuses = require("statuses");
|
|
30
|
-
const { sign } = require("cookie-signature");
|
|
31
|
-
// events is faster at init, tseep is faster at sending events
|
|
32
|
-
// since we create a ton of objects and dont send a ton of events, its better to use events here
|
|
33
|
-
const { EventEmitter } = require("events");
|
|
34
|
-
const http = require("http");
|
|
35
|
-
const ms = require('ms');
|
|
36
|
-
const etag = require("etag");
|
|
37
|
-
|
|
38
|
-
const outgoingMessage = new http.OutgoingMessage();
|
|
39
|
-
const symbols = Object.getOwnPropertySymbols(outgoingMessage);
|
|
40
|
-
const kOutHeaders = symbols.find(s => s.toString() === 'Symbol(kOutHeaders)');
|
|
41
|
-
|
|
42
|
-
class Socket extends EventEmitter {
|
|
43
|
-
constructor(response) {
|
|
44
|
-
super();
|
|
45
|
-
this.response = response;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
constructor(res, req, app) {
|
|
59
|
-
super();
|
|
60
|
-
this._req = req;
|
|
61
|
-
this._res = res;
|
|
62
|
-
this.headersSent = false;
|
|
63
|
-
this.aborted = false;
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
70
|
-
this.totalSize = 0;
|
|
71
|
-
this.headers = {
|
|
72
|
-
'connection': 'keep-alive',
|
|
73
|
-
'keep-alive': 'timeout=10'
|
|
74
|
-
};
|
|
75
|
-
if(this.app.get('x-powered-by')) {
|
|
76
|
-
this.headers['x-powered-by'] = 'UltimateExpress';
|
|
77
|
-
}
|
|
78
|
-
// support for node internal
|
|
79
|
-
this[kOutHeaders] = new Proxy(this.headers, {
|
|
80
|
-
set: (obj, prop, value) => {
|
|
81
|
-
this.set(prop, value[1]);
|
|
82
|
-
return true;
|
|
83
|
-
},
|
|
84
|
-
get: (obj, prop) => {
|
|
85
|
-
return obj[prop];
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
this.body = undefined;
|
|
89
|
-
this.on('error', (err) => {
|
|
90
|
-
if(this.aborted) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
this._res.cork(() => {
|
|
94
|
-
this._res.close();
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
this
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if(typeof body === '
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if(
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
if(typeof
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
options
|
|
287
|
-
}
|
|
288
|
-
if(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if(options.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if(!
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if(options.
|
|
388
|
-
this.headers['
|
|
389
|
-
}
|
|
390
|
-
if(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
opts
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return this;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
options = {};
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
this.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return this;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if(
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
this.
|
|
673
|
-
this
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
readStream.
|
|
735
|
-
|
|
736
|
-
res.
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
})
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2024 dimden.dev
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const cookie = require("cookie");
|
|
18
|
+
const mime = require("mime-types");
|
|
19
|
+
const vary = require("vary");
|
|
20
|
+
const encodeUrl = require("encodeurl");
|
|
21
|
+
const {
|
|
22
|
+
normalizeType, stringify, deprecated, UP_PATH_REGEXP, decode,
|
|
23
|
+
containsDotFile, isPreconditionFailure, isRangeFresh, parseHttpDate
|
|
24
|
+
} = require("./utils.js");
|
|
25
|
+
const { Writable } = require("stream");
|
|
26
|
+
const { isAbsolute } = require("path");
|
|
27
|
+
const fs = require("fs");
|
|
28
|
+
const Path = require("path");
|
|
29
|
+
const statuses = require("statuses");
|
|
30
|
+
const { sign } = require("cookie-signature");
|
|
31
|
+
// events is faster at init, tseep is faster at sending events
|
|
32
|
+
// since we create a ton of objects and dont send a ton of events, its better to use events here
|
|
33
|
+
const { EventEmitter } = require("events");
|
|
34
|
+
const http = require("http");
|
|
35
|
+
const ms = require('ms');
|
|
36
|
+
const etag = require("etag");
|
|
37
|
+
|
|
38
|
+
const outgoingMessage = new http.OutgoingMessage();
|
|
39
|
+
const symbols = Object.getOwnPropertySymbols(outgoingMessage);
|
|
40
|
+
const kOutHeaders = symbols.find(s => s.toString() === 'Symbol(kOutHeaders)');
|
|
41
|
+
|
|
42
|
+
class Socket extends EventEmitter {
|
|
43
|
+
constructor(response) {
|
|
44
|
+
super();
|
|
45
|
+
this.response = response;
|
|
46
|
+
|
|
47
|
+
this.on('error', (err) => {
|
|
48
|
+
this.emit('close');
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
get writable() {
|
|
52
|
+
return !this.response.finished;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = class Response extends Writable {
|
|
57
|
+
#socket = null;
|
|
58
|
+
constructor(res, req, app) {
|
|
59
|
+
super();
|
|
60
|
+
this._req = req;
|
|
61
|
+
this._res = res;
|
|
62
|
+
this.headersSent = false;
|
|
63
|
+
this.aborted = false;
|
|
64
|
+
this.app = app;
|
|
65
|
+
this.locals = {};
|
|
66
|
+
this.aborted = false;
|
|
67
|
+
this.statusCode = 200;
|
|
68
|
+
this.chunkedTransfer = true;
|
|
69
|
+
this.finished = false;
|
|
70
|
+
this.totalSize = 0;
|
|
71
|
+
this.headers = {
|
|
72
|
+
'connection': 'keep-alive',
|
|
73
|
+
'keep-alive': 'timeout=10'
|
|
74
|
+
};
|
|
75
|
+
if(this.app.get('x-powered-by')) {
|
|
76
|
+
this.headers['x-powered-by'] = 'UltimateExpress';
|
|
77
|
+
}
|
|
78
|
+
// support for node internal
|
|
79
|
+
this[kOutHeaders] = new Proxy(this.headers, {
|
|
80
|
+
set: (obj, prop, value) => {
|
|
81
|
+
this.set(prop, value[1]);
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
get: (obj, prop) => {
|
|
85
|
+
return obj[prop];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this.body = undefined;
|
|
89
|
+
this.on('error', (err) => {
|
|
90
|
+
if(this.aborted) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this._res.cork(() => {
|
|
94
|
+
this._res.close();
|
|
95
|
+
this.finished = true;
|
|
96
|
+
if(this.socketExists) this.socket.emit('close');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get socket() {
|
|
102
|
+
this.socketExists = true;
|
|
103
|
+
if(!this.#socket) {
|
|
104
|
+
this.#socket = new Socket(this);
|
|
105
|
+
}
|
|
106
|
+
return this.#socket;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_write(chunk, encoding, callback) {
|
|
110
|
+
if(this.aborted) {
|
|
111
|
+
const err = new Error('Request aborted');
|
|
112
|
+
err.code = 'ECONNABORTED';
|
|
113
|
+
return this.destroy(err);
|
|
114
|
+
}
|
|
115
|
+
this._res.cork(() => {
|
|
116
|
+
if(!this.headersSent) {
|
|
117
|
+
this.writeHead(this.statusCode);
|
|
118
|
+
this._res.writeStatus(this.statusCode.toString());
|
|
119
|
+
this.writeHeaders(typeof chunk === 'string');
|
|
120
|
+
}
|
|
121
|
+
if(!Buffer.isBuffer(chunk) && !(chunk instanceof ArrayBuffer)) {
|
|
122
|
+
chunk = Buffer.from(chunk);
|
|
123
|
+
chunk = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
124
|
+
}
|
|
125
|
+
if(this.chunkedTransfer) {
|
|
126
|
+
// chunked transfer encoding
|
|
127
|
+
this._res.write(chunk);
|
|
128
|
+
callback();
|
|
129
|
+
} else {
|
|
130
|
+
// fixed size transfer encoding
|
|
131
|
+
const lastOffset = this._res.getWriteOffset();
|
|
132
|
+
const [ok, done] = this._res.tryEnd(chunk, this.totalSize);
|
|
133
|
+
if(done) {
|
|
134
|
+
this.destroy();
|
|
135
|
+
this.finished = true;
|
|
136
|
+
if(this.socketExists) this.socket.emit('close');
|
|
137
|
+
callback();
|
|
138
|
+
} else {
|
|
139
|
+
// still writing
|
|
140
|
+
if(!ok) {
|
|
141
|
+
// wait until uWS is ready to accept more data
|
|
142
|
+
this._res.onWritable((offset) => {
|
|
143
|
+
if(this.aborted) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
const [ok, done] = this._res.tryEnd(chunk.slice(offset - lastOffset), this.totalSize);
|
|
147
|
+
if(done) {
|
|
148
|
+
this.destroy();
|
|
149
|
+
this.finished = true;
|
|
150
|
+
if(this.socketExists) this.socket.emit('close');
|
|
151
|
+
callback();
|
|
152
|
+
} else if(ok) {
|
|
153
|
+
callback();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return ok;
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
callback();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
writeHead(statusCode, statusMessage, headers) {
|
|
166
|
+
this.statusCode = statusCode;
|
|
167
|
+
if(!headers) {
|
|
168
|
+
if(!statusMessage) return;
|
|
169
|
+
headers = statusMessage;
|
|
170
|
+
}
|
|
171
|
+
for(let header in headers) {
|
|
172
|
+
this.set(header, headers[header]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
writeHeaders(utf8) {
|
|
176
|
+
for(const header in this.headers) {
|
|
177
|
+
if(header === 'content-length') {
|
|
178
|
+
// if content-length is set, disable chunked transfer encoding, since size is known
|
|
179
|
+
this.chunkedTransfer = false;
|
|
180
|
+
this.totalSize = parseInt(this.headers[header]);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if(Array.isArray(this.headers[header])) {
|
|
184
|
+
for(let value of this.headers[header]) {
|
|
185
|
+
this._res.writeHeader(header, value);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
this._res.writeHeader(header, this.headers[header]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if(!this.headers['content-type']) {
|
|
192
|
+
this._res.writeHeader('content-type', 'text/html' + (utf8 ? `; charset=utf-8` : ''));
|
|
193
|
+
}
|
|
194
|
+
this.headersSent = true;
|
|
195
|
+
}
|
|
196
|
+
_implicitHeader() {
|
|
197
|
+
// compatibility function
|
|
198
|
+
// usually should send headers but this is useless for us
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
status(code) {
|
|
202
|
+
if(this.headersSent) {
|
|
203
|
+
throw new Error('Can\'t set status: Response was already sent');
|
|
204
|
+
}
|
|
205
|
+
this.statusCode = parseInt(code);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
sendStatus(code) {
|
|
209
|
+
return this.status(code).send(statuses.message[+code] ?? code.toString());
|
|
210
|
+
}
|
|
211
|
+
end(data) {
|
|
212
|
+
if(this.finished) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this._res.cork(() => {
|
|
217
|
+
if(!this.headersSent) {
|
|
218
|
+
const etagFn = this.app.get('etag fn');
|
|
219
|
+
if(data && !this.headers['etag'] && etagFn && !this.req.noEtag) {
|
|
220
|
+
this.headers['etag'] = etagFn(data);
|
|
221
|
+
}
|
|
222
|
+
const fresh = this.req.fresh;
|
|
223
|
+
this._res.writeStatus(fresh ? '304' : this.statusCode.toString());
|
|
224
|
+
this.writeHeaders(true);
|
|
225
|
+
if(fresh) {
|
|
226
|
+
this._res.end();
|
|
227
|
+
this.finished = true;
|
|
228
|
+
if(this.socketExists) this.socket.emit('close');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if(!data && this.headers['content-length']) {
|
|
233
|
+
this._res.endWithoutBody(this.headers['content-length'].toString());
|
|
234
|
+
} else {
|
|
235
|
+
if(data instanceof Buffer) {
|
|
236
|
+
data = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
237
|
+
}
|
|
238
|
+
if(this.req.method === 'HEAD') {
|
|
239
|
+
const length = Buffer.byteLength(data ?? '');
|
|
240
|
+
this._res.endWithoutBody(length.toString());
|
|
241
|
+
} else {
|
|
242
|
+
this._res.end(data);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
this.finished = true;
|
|
246
|
+
if(this.socketExists) this.socket.emit('close');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
send(body) {
|
|
252
|
+
if(this.headersSent) {
|
|
253
|
+
throw new Error('Can\'t write body: Response was already sent');
|
|
254
|
+
}
|
|
255
|
+
const isBuffer = Buffer.isBuffer(body);
|
|
256
|
+
if(body === null || body === undefined) {
|
|
257
|
+
body = '';
|
|
258
|
+
} else if(typeof body === 'object' && !isBuffer) {
|
|
259
|
+
return this.json(body);
|
|
260
|
+
} else if(typeof body === 'number') {
|
|
261
|
+
if(arguments[1]) {
|
|
262
|
+
deprecated('res.send(status, body)', 'res.status(status).send(body)');
|
|
263
|
+
return this.status(body).send(arguments[1]);
|
|
264
|
+
} else {
|
|
265
|
+
deprecated('res.send(status)', 'res.sendStatus(status)');
|
|
266
|
+
return this.sendStatus(body);
|
|
267
|
+
}
|
|
268
|
+
} else if(!isBuffer) {
|
|
269
|
+
body = String(body);
|
|
270
|
+
}
|
|
271
|
+
if(typeof body === 'string') {
|
|
272
|
+
const contentType = this.headers['content-type'];
|
|
273
|
+
if(contentType && !contentType.includes(';')) {
|
|
274
|
+
this.headers['content-type'] += '; charset=utf-8';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
this.writeHead(this.statusCode);
|
|
278
|
+
return this.end(body);
|
|
279
|
+
}
|
|
280
|
+
sendFile(path, options = {}, callback) {
|
|
281
|
+
if(typeof path !== 'string') {
|
|
282
|
+
throw new TypeError('path argument is required to res.sendFile');
|
|
283
|
+
}
|
|
284
|
+
if(typeof options === 'function') {
|
|
285
|
+
callback = options;
|
|
286
|
+
options = {};
|
|
287
|
+
}
|
|
288
|
+
if(!options) options = {};
|
|
289
|
+
let done = callback;
|
|
290
|
+
if(!done) done = this.req.next;
|
|
291
|
+
// default options
|
|
292
|
+
if(typeof options.maxAge === 'string') {
|
|
293
|
+
options.maxAge = ms(options.maxAge);
|
|
294
|
+
}
|
|
295
|
+
if(typeof options.maxAge === 'undefined') {
|
|
296
|
+
options.maxAge = 0;
|
|
297
|
+
}
|
|
298
|
+
if(typeof options.lastModified === 'undefined') {
|
|
299
|
+
options.lastModified = true;
|
|
300
|
+
}
|
|
301
|
+
if(typeof options.cacheControl === 'undefined') {
|
|
302
|
+
options.cacheControl = true;
|
|
303
|
+
}
|
|
304
|
+
if(typeof options.acceptRanges === 'undefined') {
|
|
305
|
+
options.acceptRanges = true;
|
|
306
|
+
}
|
|
307
|
+
if(typeof options.etag === 'undefined') {
|
|
308
|
+
options.etag = this.app.get('etag') !== false;
|
|
309
|
+
}
|
|
310
|
+
let etagFn = this.app.get('etag fn');
|
|
311
|
+
if(options.etag && !etagFn) {
|
|
312
|
+
etagFn = stat => {
|
|
313
|
+
return etag(stat, { weak: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// path checks
|
|
318
|
+
if(!options.root && !isAbsolute(path)) {
|
|
319
|
+
this.status(500);
|
|
320
|
+
return done(new Error('path must be absolute or specify root to res.sendFile'));
|
|
321
|
+
}
|
|
322
|
+
if(!options.skipEncodePath) {
|
|
323
|
+
path = encodeURI(path);
|
|
324
|
+
}
|
|
325
|
+
path = decode(path);
|
|
326
|
+
if(path === -1) {
|
|
327
|
+
this.status(400);
|
|
328
|
+
return done(new Error('Bad Request'));
|
|
329
|
+
}
|
|
330
|
+
if(~path.indexOf('\0')) {
|
|
331
|
+
this.status(400);
|
|
332
|
+
return done(new Error('Bad Request'));
|
|
333
|
+
}
|
|
334
|
+
if(UP_PATH_REGEXP.test(path)) {
|
|
335
|
+
this.status(403);
|
|
336
|
+
return done(new Error('Forbidden'));
|
|
337
|
+
}
|
|
338
|
+
const parts = Path.normalize(path).split(Path.sep);
|
|
339
|
+
const fullpath = options.root ? Path.resolve(Path.join(options.root, path)) : path;
|
|
340
|
+
if(options.root && !fullpath.startsWith(Path.resolve(options.root))) {
|
|
341
|
+
this.status(403);
|
|
342
|
+
return done(new Error('Forbidden'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// dotfile checks
|
|
346
|
+
if(containsDotFile(parts)) {
|
|
347
|
+
switch(options.dotfiles) {
|
|
348
|
+
case 'allow':
|
|
349
|
+
break;
|
|
350
|
+
case 'deny':
|
|
351
|
+
this.status(403);
|
|
352
|
+
return done(new Error('Forbidden'));
|
|
353
|
+
case 'ignore_files':
|
|
354
|
+
if(parts.length > 1 && parts[parts.length - 1].startsWith('.')) {
|
|
355
|
+
this.status(404);
|
|
356
|
+
return done(new Error('Not found'));
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
case 'ignore':
|
|
360
|
+
default:
|
|
361
|
+
this.status(404);
|
|
362
|
+
return done(new Error('Not found'));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let stat = options._stat;
|
|
367
|
+
if(!stat) {
|
|
368
|
+
try {
|
|
369
|
+
stat = fs.statSync(fullpath);
|
|
370
|
+
} catch(err) {
|
|
371
|
+
return done(err);
|
|
372
|
+
}
|
|
373
|
+
if(stat.isDirectory()) {
|
|
374
|
+
this.status(404);
|
|
375
|
+
return done(new Error(`Not found`));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// headers
|
|
380
|
+
if(!this.headers['content-type']) {
|
|
381
|
+
const m = mime.lookup(fullpath);
|
|
382
|
+
if(m) this.type(m);
|
|
383
|
+
}
|
|
384
|
+
if(options.cacheControl) {
|
|
385
|
+
this.headers['cache-control'] = `public, max-age=${options.maxAge / 1000}` + (options.immutable ? ', immutable' : '');
|
|
386
|
+
}
|
|
387
|
+
if(options.lastModified) {
|
|
388
|
+
this.headers['last-modified'] = stat.mtime.toUTCString();
|
|
389
|
+
}
|
|
390
|
+
if(options.headers) {
|
|
391
|
+
for(const header in options.headers) {
|
|
392
|
+
this.set(header, options.headers[header]);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if(options.setHeaders) {
|
|
396
|
+
options.setHeaders(this, fullpath, stat);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// etag
|
|
400
|
+
if(options.etag && !this.headers['etag'] && etagFn) {
|
|
401
|
+
this.headers['etag'] = etagFn(stat);
|
|
402
|
+
}
|
|
403
|
+
if(!options.etag) {
|
|
404
|
+
this.req.noEtag = true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// conditional requests
|
|
408
|
+
if(isPreconditionFailure(this.req, this)) {
|
|
409
|
+
this.status(412);
|
|
410
|
+
return done(new Error('Precondition Failed'));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// range requests
|
|
414
|
+
let offset = 0, len = stat.size, ranged = false;
|
|
415
|
+
if(options.acceptRanges) {
|
|
416
|
+
this.headers['accept-ranges'] = 'bytes';
|
|
417
|
+
if(this.req.headers.range) {
|
|
418
|
+
let ranges = this.req.range(stat.size, {
|
|
419
|
+
combine: true
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// if-range
|
|
423
|
+
if(!isRangeFresh(this.req, this)) {
|
|
424
|
+
ranges = -2;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if(ranges === -1) {
|
|
428
|
+
this.status(416);
|
|
429
|
+
this.headers['content-range'] = `bytes */${stat.size}`;
|
|
430
|
+
return done(new Error('Range Not Satisfiable'));
|
|
431
|
+
}
|
|
432
|
+
if(ranges !== -2 && ranges.length === 1) {
|
|
433
|
+
this.status(206);
|
|
434
|
+
this.headers['content-range'] = `bytes ${ranges[0].start}-${ranges[0].end}/${stat.size}`;
|
|
435
|
+
offset = ranges[0].start;
|
|
436
|
+
len = ranges[0].end - ranges[0].start + 1;
|
|
437
|
+
ranged = true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// if-modified-since, if-none-match
|
|
443
|
+
if(this.req.fresh) {
|
|
444
|
+
return this.end();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if(this.req.method === 'HEAD') {
|
|
448
|
+
this.set('Content-Length', stat.size);
|
|
449
|
+
return this.end();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// serve smaller files using workers
|
|
453
|
+
if(this.app.workers.length && stat.size < 768 * 1024 && !ranged) {
|
|
454
|
+
this.app.readFileWithWorker(fullpath).then((data) => {
|
|
455
|
+
if(this._res.aborted) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.end(data);
|
|
459
|
+
if(callback) callback();
|
|
460
|
+
}).catch((err) => {
|
|
461
|
+
if(callback) callback(err);
|
|
462
|
+
});
|
|
463
|
+
} else {
|
|
464
|
+
// larger files or range requests are piped over response
|
|
465
|
+
let opts = {
|
|
466
|
+
highWaterMark: 256 * 1024
|
|
467
|
+
};
|
|
468
|
+
if(ranged) {
|
|
469
|
+
opts.start = offset;
|
|
470
|
+
opts.end = Math.max(offset, offset + len - 1);
|
|
471
|
+
}
|
|
472
|
+
const file = fs.createReadStream(fullpath, opts);
|
|
473
|
+
pipeStreamOverResponse(this, file, len, callback);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
download(path, filename, options, callback) {
|
|
477
|
+
let done = callback;
|
|
478
|
+
let name = filename;
|
|
479
|
+
let opts = options || {};
|
|
480
|
+
|
|
481
|
+
// support function as second or third arg
|
|
482
|
+
if (typeof filename === 'function') {
|
|
483
|
+
done = filename;
|
|
484
|
+
name = null;
|
|
485
|
+
opts = {};
|
|
486
|
+
} else if (typeof options === 'function') {
|
|
487
|
+
done = options;
|
|
488
|
+
opts = {};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// support optional filename, where options may be in it's place
|
|
492
|
+
if (typeof filename === 'object' &&
|
|
493
|
+
(typeof options === 'function' || options === undefined)) {
|
|
494
|
+
name = null;
|
|
495
|
+
opts = filename;
|
|
496
|
+
}
|
|
497
|
+
if(!name) {
|
|
498
|
+
name = Path.basename(path);
|
|
499
|
+
}
|
|
500
|
+
if(!opts.root && !isAbsolute(path)) {
|
|
501
|
+
opts.root = process.cwd();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
this.attachment(name);
|
|
505
|
+
this.sendFile(path, opts, done);
|
|
506
|
+
}
|
|
507
|
+
set(field, value) {
|
|
508
|
+
if(this.headersSent) {
|
|
509
|
+
throw new Error('Can\'t write headers: Response was already sent');
|
|
510
|
+
}
|
|
511
|
+
if(typeof field === 'object') {
|
|
512
|
+
for(const header in field) {
|
|
513
|
+
this.set(header, field[header]);
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
field = field.toLowerCase();
|
|
517
|
+
if(field === 'set-cookie' && Array.isArray(value)) {
|
|
518
|
+
value = value.join('; ');
|
|
519
|
+
} else if(field === 'content-type') {
|
|
520
|
+
if(!value.includes('charset=') && (value.startsWith('text/') || value === 'application/json' || value === 'application/javascript')) {
|
|
521
|
+
value += '; charset=utf-8';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
this.headers[field] = String(value);
|
|
525
|
+
}
|
|
526
|
+
return this;
|
|
527
|
+
}
|
|
528
|
+
header(field, value) {
|
|
529
|
+
return this.set(field, value);
|
|
530
|
+
}
|
|
531
|
+
setHeader(field, value) {
|
|
532
|
+
return this.set(field, value);
|
|
533
|
+
}
|
|
534
|
+
get(field) {
|
|
535
|
+
return this.headers[field.toLowerCase()];
|
|
536
|
+
}
|
|
537
|
+
getHeader(field) {
|
|
538
|
+
return this.get(field);
|
|
539
|
+
}
|
|
540
|
+
removeHeader(field) {
|
|
541
|
+
delete this.headers[field.toLowerCase()];
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
append(field, value) {
|
|
545
|
+
field = field.toLowerCase();
|
|
546
|
+
const old = this.headers[field];
|
|
547
|
+
if(old) {
|
|
548
|
+
const newVal = [];
|
|
549
|
+
if(Array.isArray(old)) {
|
|
550
|
+
newVal.push(...old);
|
|
551
|
+
} else if(old) {
|
|
552
|
+
newVal.push(old);
|
|
553
|
+
}
|
|
554
|
+
if(Array.isArray(value)) {
|
|
555
|
+
newVal.push(...value);
|
|
556
|
+
} else {
|
|
557
|
+
newVal.push(value);
|
|
558
|
+
}
|
|
559
|
+
this.headers[field] = newVal;
|
|
560
|
+
} else {
|
|
561
|
+
this.headers[field] = value;
|
|
562
|
+
}
|
|
563
|
+
return this;
|
|
564
|
+
}
|
|
565
|
+
render(view, options, callback) {
|
|
566
|
+
if(typeof options === 'function') {
|
|
567
|
+
callback = options;
|
|
568
|
+
options = {};
|
|
569
|
+
}
|
|
570
|
+
if(!options) {
|
|
571
|
+
options = {};
|
|
572
|
+
} else {
|
|
573
|
+
options = Object.assign({}, options);
|
|
574
|
+
}
|
|
575
|
+
options._locals = this.locals;
|
|
576
|
+
const done = callback || ((err, str) => {
|
|
577
|
+
if(err) return this.req.next(err);
|
|
578
|
+
this.send(str);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
this.app.render(view, options, done);
|
|
582
|
+
}
|
|
583
|
+
cookie(name, value, options) {
|
|
584
|
+
if(!options) {
|
|
585
|
+
options = {};
|
|
586
|
+
}
|
|
587
|
+
let val = typeof value === 'object' ? "j:"+JSON.stringify(value) : String(value);
|
|
588
|
+
if(options.maxAge != null) {
|
|
589
|
+
const maxAge = options.maxAge - 0;
|
|
590
|
+
if(!isNaN(maxAge)) {
|
|
591
|
+
options.expires = new Date(Date.now() + maxAge);
|
|
592
|
+
options.maxAge = Math.floor(maxAge / 1000);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if(options.signed) {
|
|
596
|
+
val = 's:' + sign(val, this.req.secret);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if(options.path == null) {
|
|
600
|
+
options.path = '/';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
this.append('Set-Cookie', cookie.serialize(name, val, options));
|
|
604
|
+
return this;
|
|
605
|
+
}
|
|
606
|
+
clearCookie(name, options) {
|
|
607
|
+
const opts = { path: '/', ...options, expires: new Date(1) };
|
|
608
|
+
delete opts.maxAge;
|
|
609
|
+
return this.cookie(name, '', opts);
|
|
610
|
+
}
|
|
611
|
+
attachment(filename) {
|
|
612
|
+
this.headers['Content-Disposition'] = `attachment; filename="${filename}"`;
|
|
613
|
+
this.type(filename.split('.').pop());
|
|
614
|
+
return this;
|
|
615
|
+
}
|
|
616
|
+
format(object) {
|
|
617
|
+
const keys = Object.keys(object).filter(v => v !== 'default');
|
|
618
|
+
const key = keys.length > 0 ? this.req.accepts(keys) : false;
|
|
619
|
+
|
|
620
|
+
this.vary('Accept');
|
|
621
|
+
|
|
622
|
+
if(key) {
|
|
623
|
+
this.set('Content-Type', normalizeType(key).value);
|
|
624
|
+
object[key](this.req, this, this.req.next);
|
|
625
|
+
} else if(object.default) {
|
|
626
|
+
object.default(this.req, this, this.req.next);
|
|
627
|
+
} else {
|
|
628
|
+
this.status(406).send(this.app._generateErrorPage('Not Acceptable'));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
json(body) {
|
|
634
|
+
if(!this.headers['content-type']) {
|
|
635
|
+
this.headers['content-type'] = 'application/json; charset=utf-8';
|
|
636
|
+
}
|
|
637
|
+
const escape = this.app.get('json escape');
|
|
638
|
+
const replacer = this.app.get('json replacer');
|
|
639
|
+
const spaces = this.app.get('json spaces');
|
|
640
|
+
this.send(stringify(body, replacer, spaces, escape));
|
|
641
|
+
}
|
|
642
|
+
jsonp(object) {
|
|
643
|
+
let callback = this.req.query[this.app.get('jsonp callback name')];
|
|
644
|
+
let body = stringify(object, this.app.get('json replacer'), this.app.get('json spaces'), this.app.get('json escape'));
|
|
645
|
+
|
|
646
|
+
if(!this.headers['content-type']) {
|
|
647
|
+
this.headers['content-type'] = 'application/javascript; charset=utf-8';
|
|
648
|
+
this.headers['X-Content-Type-Options'] = 'nosniff';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if(Array.isArray(callback)) {
|
|
652
|
+
callback = callback[0];
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if(typeof callback === 'string' && callback.length !== 0) {
|
|
656
|
+
callback = callback.replace(/[^\[\]\w$.]/g, '');
|
|
657
|
+
|
|
658
|
+
if(body === undefined) {
|
|
659
|
+
body = '';
|
|
660
|
+
} else if(typeof body === 'string') {
|
|
661
|
+
// replace chars not allowed in JavaScript that are in JSON
|
|
662
|
+
body = body
|
|
663
|
+
.replace(/\u2028/g, '\\u2028')
|
|
664
|
+
.replace(/\u2029/g, '\\u2029')
|
|
665
|
+
}
|
|
666
|
+
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return this.send(body);
|
|
670
|
+
}
|
|
671
|
+
links(links) {
|
|
672
|
+
this.headers['link'] = Object.entries(links).map(([rel, url]) => `<${url}>; rel="${rel}"`).join(', ');
|
|
673
|
+
return this;
|
|
674
|
+
}
|
|
675
|
+
location(path) {
|
|
676
|
+
if(path === 'back') {
|
|
677
|
+
path = this.req.get('Referrer');
|
|
678
|
+
if(!path) path = this.req.get('Referer');
|
|
679
|
+
if(!path) path = '/';
|
|
680
|
+
}
|
|
681
|
+
return this.headers['location'] = encodeUrl(path);
|
|
682
|
+
}
|
|
683
|
+
redirect(status, url) {
|
|
684
|
+
if(typeof status !== 'number' && !url) {
|
|
685
|
+
url = status;
|
|
686
|
+
status = 302;
|
|
687
|
+
}
|
|
688
|
+
this.location(url);
|
|
689
|
+
this.status(status);
|
|
690
|
+
this.headers['content-type'] = 'text/plain; charset=utf-8';
|
|
691
|
+
return this.send(`${statuses.message[status] ?? status}. Redirecting to ${url}`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
type(type) {
|
|
695
|
+
let ct = type.indexOf('/') === -1
|
|
696
|
+
? (mime.contentType(type) || 'application/octet-stream')
|
|
697
|
+
: type;
|
|
698
|
+
if(ct.startsWith('text/') || ct === 'application/json' || ct === 'application/javascript') {
|
|
699
|
+
ct += '; charset=UTF-8';
|
|
700
|
+
}
|
|
701
|
+
return this.set('content-type', ct);
|
|
702
|
+
}
|
|
703
|
+
contentType(type) {
|
|
704
|
+
return this.type(type);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
vary(field) {
|
|
708
|
+
vary(this, field);
|
|
709
|
+
return this;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
get writableFinished() {
|
|
713
|
+
return this.finished;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function pipeStreamOverResponse(res, readStream, totalSize, callback) {
|
|
718
|
+
readStream.on('data', (chunk) => {
|
|
719
|
+
if(res.aborted) {
|
|
720
|
+
return readStream.destroy();
|
|
721
|
+
}
|
|
722
|
+
res._res.cork(() => {
|
|
723
|
+
if(!res.headersSent) {
|
|
724
|
+
res.writeHead(res.statusCode);
|
|
725
|
+
res._res.writeStatus(res.statusCode.toString());
|
|
726
|
+
res.writeHeaders(true);
|
|
727
|
+
}
|
|
728
|
+
const ab = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
|
|
729
|
+
|
|
730
|
+
const lastOffset = res._res.getWriteOffset();
|
|
731
|
+
const [ok, done] = res._res.tryEnd(ab, totalSize);
|
|
732
|
+
|
|
733
|
+
if (done) {
|
|
734
|
+
readStream.destroy();
|
|
735
|
+
res.finished = true;
|
|
736
|
+
if(res.socketExists) res.socket.emit('close');
|
|
737
|
+
if(callback) callback();
|
|
738
|
+
} else if (!ok) {
|
|
739
|
+
readStream.pause();
|
|
740
|
+
|
|
741
|
+
res._res.ab = ab;
|
|
742
|
+
res._res.abOffset = lastOffset;
|
|
743
|
+
|
|
744
|
+
res._res.onWritable((offset) => {
|
|
745
|
+
if(res.aborted) {
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
const [ok, done] = res._res.tryEnd(res._res.ab.slice(offset - res._res.abOffset), totalSize);
|
|
749
|
+
if (done) {
|
|
750
|
+
readStream.destroy();
|
|
751
|
+
res.finished = true;
|
|
752
|
+
if(res.socketExists) res.socket.emit('close');
|
|
753
|
+
if(callback) callback();
|
|
754
|
+
} else if (ok) {
|
|
755
|
+
readStream.resume();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return ok;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}).on('error', e => {
|
|
763
|
+
if(callback) callback(e);
|
|
764
|
+
if(!res.finished) {
|
|
765
|
+
res._res.close();
|
|
766
|
+
res.finished = true;
|
|
767
|
+
if(res.socketExists) res.socket.emit('error', e);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
763
770
|
}
|