Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50650 views
1
/*!
2
* express
3
* Copyright(c) 2009-2013 TJ Holowaychuk
4
* Copyright(c) 2014-2015 Douglas Christopher Wilson
5
* MIT Licensed
6
*/
7
8
'use strict';
9
10
/**
11
* Module dependencies.
12
* @private
13
*/
14
15
var contentDisposition = require('content-disposition');
16
var deprecate = require('depd')('express');
17
var encodeUrl = require('encodeurl');
18
var escapeHtml = require('escape-html');
19
var http = require('http');
20
var isAbsolute = require('./utils').isAbsolute;
21
var onFinished = require('on-finished');
22
var path = require('path');
23
var statuses = require('statuses')
24
var merge = require('utils-merge');
25
var sign = require('cookie-signature').sign;
26
var normalizeType = require('./utils').normalizeType;
27
var normalizeTypes = require('./utils').normalizeTypes;
28
var setCharset = require('./utils').setCharset;
29
var cookie = require('cookie');
30
var send = require('send');
31
var extname = path.extname;
32
var mime = send.mime;
33
var resolve = path.resolve;
34
var vary = require('vary');
35
36
/**
37
* Response prototype.
38
* @public
39
*/
40
41
var res = Object.create(http.ServerResponse.prototype)
42
43
/**
44
* Module exports.
45
* @public
46
*/
47
48
module.exports = res
49
50
/**
51
* Module variables.
52
* @private
53
*/
54
55
var charsetRegExp = /;\s*charset\s*=/;
56
57
/**
58
* Set status `code`.
59
*
60
* @param {Number} code
61
* @return {ServerResponse}
62
* @public
63
*/
64
65
res.status = function status(code) {
66
this.statusCode = code;
67
return this;
68
};
69
70
/**
71
* Set Link header field with the given `links`.
72
*
73
* Examples:
74
*
75
* res.links({
76
* next: 'http://api.example.com/users?page=2',
77
* last: 'http://api.example.com/users?page=5'
78
* });
79
*
80
* @param {Object} links
81
* @return {ServerResponse}
82
* @public
83
*/
84
85
res.links = function(links){
86
var link = this.get('Link') || '';
87
if (link) link += ', ';
88
return this.set('Link', link + Object.keys(links).map(function(rel){
89
return '<' + links[rel] + '>; rel="' + rel + '"';
90
}).join(', '));
91
};
92
93
/**
94
* Send a response.
95
*
96
* Examples:
97
*
98
* res.send(new Buffer('wahoo'));
99
* res.send({ some: 'json' });
100
* res.send('<p>some html</p>');
101
*
102
* @param {string|number|boolean|object|Buffer} body
103
* @public
104
*/
105
106
res.send = function send(body) {
107
var chunk = body;
108
var encoding;
109
var len;
110
var req = this.req;
111
var type;
112
113
// settings
114
var app = this.app;
115
116
// allow status / body
117
if (arguments.length === 2) {
118
// res.send(body, status) backwards compat
119
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
120
deprecate('res.send(body, status): Use res.status(status).send(body) instead');
121
this.statusCode = arguments[1];
122
} else {
123
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
124
this.statusCode = arguments[0];
125
chunk = arguments[1];
126
}
127
}
128
129
// disambiguate res.send(status) and res.send(status, num)
130
if (typeof chunk === 'number' && arguments.length === 1) {
131
// res.send(status) will set status message as text string
132
if (!this.get('Content-Type')) {
133
this.type('txt');
134
}
135
136
deprecate('res.send(status): Use res.sendStatus(status) instead');
137
this.statusCode = chunk;
138
chunk = statuses[chunk]
139
}
140
141
switch (typeof chunk) {
142
// string defaulting to html
143
case 'string':
144
if (!this.get('Content-Type')) {
145
this.type('html');
146
}
147
break;
148
case 'boolean':
149
case 'number':
150
case 'object':
151
if (chunk === null) {
152
chunk = '';
153
} else if (Buffer.isBuffer(chunk)) {
154
if (!this.get('Content-Type')) {
155
this.type('bin');
156
}
157
} else {
158
return this.json(chunk);
159
}
160
break;
161
}
162
163
// write strings in utf-8
164
if (typeof chunk === 'string') {
165
encoding = 'utf8';
166
type = this.get('Content-Type');
167
168
// reflect this in content-type
169
if (typeof type === 'string') {
170
this.set('Content-Type', setCharset(type, 'utf-8'));
171
}
172
}
173
174
// populate Content-Length
175
if (chunk !== undefined) {
176
if (!Buffer.isBuffer(chunk)) {
177
// convert chunk to Buffer; saves later double conversions
178
chunk = new Buffer(chunk, encoding);
179
encoding = undefined;
180
}
181
182
len = chunk.length;
183
this.set('Content-Length', len);
184
}
185
186
// populate ETag
187
var etag;
188
var generateETag = len !== undefined && app.get('etag fn');
189
if (typeof generateETag === 'function' && !this.get('ETag')) {
190
if ((etag = generateETag(chunk, encoding))) {
191
this.set('ETag', etag);
192
}
193
}
194
195
// freshness
196
if (req.fresh) this.statusCode = 304;
197
198
// strip irrelevant headers
199
if (204 === this.statusCode || 304 === this.statusCode) {
200
this.removeHeader('Content-Type');
201
this.removeHeader('Content-Length');
202
this.removeHeader('Transfer-Encoding');
203
chunk = '';
204
}
205
206
if (req.method === 'HEAD') {
207
// skip body for HEAD
208
this.end();
209
} else {
210
// respond
211
this.end(chunk, encoding);
212
}
213
214
return this;
215
};
216
217
/**
218
* Send JSON response.
219
*
220
* Examples:
221
*
222
* res.json(null);
223
* res.json({ user: 'tj' });
224
*
225
* @param {string|number|boolean|object} obj
226
* @public
227
*/
228
229
res.json = function json(obj) {
230
var val = obj;
231
232
// allow status / body
233
if (arguments.length === 2) {
234
// res.json(body, status) backwards compat
235
if (typeof arguments[1] === 'number') {
236
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
237
this.statusCode = arguments[1];
238
} else {
239
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
240
this.statusCode = arguments[0];
241
val = arguments[1];
242
}
243
}
244
245
// settings
246
var app = this.app;
247
var replacer = app.get('json replacer');
248
var spaces = app.get('json spaces');
249
var body = stringify(val, replacer, spaces);
250
251
// content-type
252
if (!this.get('Content-Type')) {
253
this.set('Content-Type', 'application/json');
254
}
255
256
return this.send(body);
257
};
258
259
/**
260
* Send JSON response with JSONP callback support.
261
*
262
* Examples:
263
*
264
* res.jsonp(null);
265
* res.jsonp({ user: 'tj' });
266
*
267
* @param {string|number|boolean|object} obj
268
* @public
269
*/
270
271
res.jsonp = function jsonp(obj) {
272
var val = obj;
273
274
// allow status / body
275
if (arguments.length === 2) {
276
// res.json(body, status) backwards compat
277
if (typeof arguments[1] === 'number') {
278
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
279
this.statusCode = arguments[1];
280
} else {
281
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
282
this.statusCode = arguments[0];
283
val = arguments[1];
284
}
285
}
286
287
// settings
288
var app = this.app;
289
var replacer = app.get('json replacer');
290
var spaces = app.get('json spaces');
291
var body = stringify(val, replacer, spaces);
292
var callback = this.req.query[app.get('jsonp callback name')];
293
294
// content-type
295
if (!this.get('Content-Type')) {
296
this.set('X-Content-Type-Options', 'nosniff');
297
this.set('Content-Type', 'application/json');
298
}
299
300
// fixup callback
301
if (Array.isArray(callback)) {
302
callback = callback[0];
303
}
304
305
// jsonp
306
if (typeof callback === 'string' && callback.length !== 0) {
307
this.charset = 'utf-8';
308
this.set('X-Content-Type-Options', 'nosniff');
309
this.set('Content-Type', 'text/javascript');
310
311
// restrict callback charset
312
callback = callback.replace(/[^\[\]\w$.]/g, '');
313
314
// replace chars not allowed in JavaScript that are in JSON
315
body = body
316
.replace(/\u2028/g, '\\u2028')
317
.replace(/\u2029/g, '\\u2029');
318
319
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
320
// the typeof check is just to reduce client error noise
321
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
322
}
323
324
return this.send(body);
325
};
326
327
/**
328
* Send given HTTP status code.
329
*
330
* Sets the response status to `statusCode` and the body of the
331
* response to the standard description from node's http.STATUS_CODES
332
* or the statusCode number if no description.
333
*
334
* Examples:
335
*
336
* res.sendStatus(200);
337
*
338
* @param {number} statusCode
339
* @public
340
*/
341
342
res.sendStatus = function sendStatus(statusCode) {
343
var body = statuses[statusCode] || String(statusCode)
344
345
this.statusCode = statusCode;
346
this.type('txt');
347
348
return this.send(body);
349
};
350
351
/**
352
* Transfer the file at the given `path`.
353
*
354
* Automatically sets the _Content-Type_ response header field.
355
* The callback `callback(err)` is invoked when the transfer is complete
356
* or when an error occurs. Be sure to check `res.sentHeader`
357
* if you wish to attempt responding, as the header and some data
358
* may have already been transferred.
359
*
360
* Options:
361
*
362
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
363
* - `root` root directory for relative filenames
364
* - `headers` object of headers to serve with file
365
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
366
*
367
* Other options are passed along to `send`.
368
*
369
* Examples:
370
*
371
* The following example illustrates how `res.sendFile()` may
372
* be used as an alternative for the `static()` middleware for
373
* dynamic situations. The code backing `res.sendFile()` is actually
374
* the same code, so HTTP cache support etc is identical.
375
*
376
* app.get('/user/:uid/photos/:file', function(req, res){
377
* var uid = req.params.uid
378
* , file = req.params.file;
379
*
380
* req.user.mayViewFilesFrom(uid, function(yes){
381
* if (yes) {
382
* res.sendFile('/uploads/' + uid + '/' + file);
383
* } else {
384
* res.send(403, 'Sorry! you cant see that.');
385
* }
386
* });
387
* });
388
*
389
* @public
390
*/
391
392
res.sendFile = function sendFile(path, options, callback) {
393
var done = callback;
394
var req = this.req;
395
var res = this;
396
var next = req.next;
397
var opts = options || {};
398
399
if (!path) {
400
throw new TypeError('path argument is required to res.sendFile');
401
}
402
403
// support function as second arg
404
if (typeof options === 'function') {
405
done = options;
406
opts = {};
407
}
408
409
if (!opts.root && !isAbsolute(path)) {
410
throw new TypeError('path must be absolute or specify root to res.sendFile');
411
}
412
413
// create file stream
414
var pathname = encodeURI(path);
415
var file = send(req, pathname, opts);
416
417
// transfer
418
sendfile(res, file, opts, function (err) {
419
if (done) return done(err);
420
if (err && err.code === 'EISDIR') return next();
421
422
// next() all but write errors
423
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
424
next(err);
425
}
426
});
427
};
428
429
/**
430
* Transfer the file at the given `path`.
431
*
432
* Automatically sets the _Content-Type_ response header field.
433
* The callback `callback(err)` is invoked when the transfer is complete
434
* or when an error occurs. Be sure to check `res.sentHeader`
435
* if you wish to attempt responding, as the header and some data
436
* may have already been transferred.
437
*
438
* Options:
439
*
440
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
441
* - `root` root directory for relative filenames
442
* - `headers` object of headers to serve with file
443
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
444
*
445
* Other options are passed along to `send`.
446
*
447
* Examples:
448
*
449
* The following example illustrates how `res.sendfile()` may
450
* be used as an alternative for the `static()` middleware for
451
* dynamic situations. The code backing `res.sendfile()` is actually
452
* the same code, so HTTP cache support etc is identical.
453
*
454
* app.get('/user/:uid/photos/:file', function(req, res){
455
* var uid = req.params.uid
456
* , file = req.params.file;
457
*
458
* req.user.mayViewFilesFrom(uid, function(yes){
459
* if (yes) {
460
* res.sendfile('/uploads/' + uid + '/' + file);
461
* } else {
462
* res.send(403, 'Sorry! you cant see that.');
463
* }
464
* });
465
* });
466
*
467
* @public
468
*/
469
470
res.sendfile = function (path, options, callback) {
471
var done = callback;
472
var req = this.req;
473
var res = this;
474
var next = req.next;
475
var opts = options || {};
476
477
// support function as second arg
478
if (typeof options === 'function') {
479
done = options;
480
opts = {};
481
}
482
483
// create file stream
484
var file = send(req, path, opts);
485
486
// transfer
487
sendfile(res, file, opts, function (err) {
488
if (done) return done(err);
489
if (err && err.code === 'EISDIR') return next();
490
491
// next() all but write errors
492
if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
493
next(err);
494
}
495
});
496
};
497
498
res.sendfile = deprecate.function(res.sendfile,
499
'res.sendfile: Use res.sendFile instead');
500
501
/**
502
* Transfer the file at the given `path` as an attachment.
503
*
504
* Optionally providing an alternate attachment `filename`,
505
* and optional callback `callback(err)`. The callback is invoked
506
* when the data transfer is complete, or when an error has
507
* ocurred. Be sure to check `res.headersSent` if you plan to respond.
508
*
509
* This method uses `res.sendfile()`.
510
*
511
* @public
512
*/
513
514
res.download = function download(path, filename, callback) {
515
var done = callback;
516
var name = filename;
517
518
// support function as second arg
519
if (typeof filename === 'function') {
520
done = filename;
521
name = null;
522
}
523
524
// set Content-Disposition when file is sent
525
var headers = {
526
'Content-Disposition': contentDisposition(name || path)
527
};
528
529
// Resolve the full path for sendFile
530
var fullPath = resolve(path);
531
532
return this.sendFile(fullPath, { headers: headers }, done);
533
};
534
535
/**
536
* Set _Content-Type_ response header with `type` through `mime.lookup()`
537
* when it does not contain "/", or set the Content-Type to `type` otherwise.
538
*
539
* Examples:
540
*
541
* res.type('.html');
542
* res.type('html');
543
* res.type('json');
544
* res.type('application/json');
545
* res.type('png');
546
*
547
* @param {String} type
548
* @return {ServerResponse} for chaining
549
* @public
550
*/
551
552
res.contentType =
553
res.type = function contentType(type) {
554
var ct = type.indexOf('/') === -1
555
? mime.lookup(type)
556
: type;
557
558
return this.set('Content-Type', ct);
559
};
560
561
/**
562
* Respond to the Acceptable formats using an `obj`
563
* of mime-type callbacks.
564
*
565
* This method uses `req.accepted`, an array of
566
* acceptable types ordered by their quality values.
567
* When "Accept" is not present the _first_ callback
568
* is invoked, otherwise the first match is used. When
569
* no match is performed the server responds with
570
* 406 "Not Acceptable".
571
*
572
* Content-Type is set for you, however if you choose
573
* you may alter this within the callback using `res.type()`
574
* or `res.set('Content-Type', ...)`.
575
*
576
* res.format({
577
* 'text/plain': function(){
578
* res.send('hey');
579
* },
580
*
581
* 'text/html': function(){
582
* res.send('<p>hey</p>');
583
* },
584
*
585
* 'appliation/json': function(){
586
* res.send({ message: 'hey' });
587
* }
588
* });
589
*
590
* In addition to canonicalized MIME types you may
591
* also use extnames mapped to these types:
592
*
593
* res.format({
594
* text: function(){
595
* res.send('hey');
596
* },
597
*
598
* html: function(){
599
* res.send('<p>hey</p>');
600
* },
601
*
602
* json: function(){
603
* res.send({ message: 'hey' });
604
* }
605
* });
606
*
607
* By default Express passes an `Error`
608
* with a `.status` of 406 to `next(err)`
609
* if a match is not made. If you provide
610
* a `.default` callback it will be invoked
611
* instead.
612
*
613
* @param {Object} obj
614
* @return {ServerResponse} for chaining
615
* @public
616
*/
617
618
res.format = function(obj){
619
var req = this.req;
620
var next = req.next;
621
622
var fn = obj.default;
623
if (fn) delete obj.default;
624
var keys = Object.keys(obj);
625
626
var key = keys.length > 0
627
? req.accepts(keys)
628
: false;
629
630
this.vary("Accept");
631
632
if (key) {
633
this.set('Content-Type', normalizeType(key).value);
634
obj[key](req, this, next);
635
} else if (fn) {
636
fn();
637
} else {
638
var err = new Error('Not Acceptable');
639
err.status = err.statusCode = 406;
640
err.types = normalizeTypes(keys).map(function(o){ return o.value });
641
next(err);
642
}
643
644
return this;
645
};
646
647
/**
648
* Set _Content-Disposition_ header to _attachment_ with optional `filename`.
649
*
650
* @param {String} filename
651
* @return {ServerResponse}
652
* @public
653
*/
654
655
res.attachment = function attachment(filename) {
656
if (filename) {
657
this.type(extname(filename));
658
}
659
660
this.set('Content-Disposition', contentDisposition(filename));
661
662
return this;
663
};
664
665
/**
666
* Append additional header `field` with value `val`.
667
*
668
* Example:
669
*
670
* res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
671
* res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
672
* res.append('Warning', '199 Miscellaneous warning');
673
*
674
* @param {String} field
675
* @param {String|Array} val
676
* @return {ServerResponse} for chaining
677
* @public
678
*/
679
680
res.append = function append(field, val) {
681
var prev = this.get(field);
682
var value = val;
683
684
if (prev) {
685
// concat the new and prev vals
686
value = Array.isArray(prev) ? prev.concat(val)
687
: Array.isArray(val) ? [prev].concat(val)
688
: [prev, val];
689
}
690
691
return this.set(field, value);
692
};
693
694
/**
695
* Set header `field` to `val`, or pass
696
* an object of header fields.
697
*
698
* Examples:
699
*
700
* res.set('Foo', ['bar', 'baz']);
701
* res.set('Accept', 'application/json');
702
* res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
703
*
704
* Aliased as `res.header()`.
705
*
706
* @param {String|Object} field
707
* @param {String|Array} val
708
* @return {ServerResponse} for chaining
709
* @public
710
*/
711
712
res.set =
713
res.header = function header(field, val) {
714
if (arguments.length === 2) {
715
var value = Array.isArray(val)
716
? val.map(String)
717
: String(val);
718
719
// add charset to content-type
720
if (field.toLowerCase() === 'content-type') {
721
if (Array.isArray(value)) {
722
throw new TypeError('Content-Type cannot be set to an Array');
723
}
724
if (!charsetRegExp.test(value)) {
725
var charset = mime.charsets.lookup(value.split(';')[0]);
726
if (charset) value += '; charset=' + charset.toLowerCase();
727
}
728
}
729
730
this.setHeader(field, value);
731
} else {
732
for (var key in field) {
733
this.set(key, field[key]);
734
}
735
}
736
return this;
737
};
738
739
/**
740
* Get value for header `field`.
741
*
742
* @param {String} field
743
* @return {String}
744
* @public
745
*/
746
747
res.get = function(field){
748
return this.getHeader(field);
749
};
750
751
/**
752
* Clear cookie `name`.
753
*
754
* @param {String} name
755
* @param {Object} [options]
756
* @return {ServerResponse} for chaining
757
* @public
758
*/
759
760
res.clearCookie = function clearCookie(name, options) {
761
var opts = merge({ expires: new Date(1), path: '/' }, options);
762
763
return this.cookie(name, '', opts);
764
};
765
766
/**
767
* Set cookie `name` to `value`, with the given `options`.
768
*
769
* Options:
770
*
771
* - `maxAge` max-age in milliseconds, converted to `expires`
772
* - `signed` sign the cookie
773
* - `path` defaults to "/"
774
*
775
* Examples:
776
*
777
* // "Remember Me" for 15 minutes
778
* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
779
*
780
* // save as above
781
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
782
*
783
* @param {String} name
784
* @param {String|Object} value
785
* @param {Object} [options]
786
* @return {ServerResponse} for chaining
787
* @public
788
*/
789
790
res.cookie = function (name, value, options) {
791
var opts = merge({}, options);
792
var secret = this.req.secret;
793
var signed = opts.signed;
794
795
if (signed && !secret) {
796
throw new Error('cookieParser("secret") required for signed cookies');
797
}
798
799
var val = typeof value === 'object'
800
? 'j:' + JSON.stringify(value)
801
: String(value);
802
803
if (signed) {
804
val = 's:' + sign(val, secret);
805
}
806
807
if ('maxAge' in opts) {
808
opts.expires = new Date(Date.now() + opts.maxAge);
809
opts.maxAge /= 1000;
810
}
811
812
if (opts.path == null) {
813
opts.path = '/';
814
}
815
816
this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
817
818
return this;
819
};
820
821
/**
822
* Set the location header to `url`.
823
*
824
* The given `url` can also be "back", which redirects
825
* to the _Referrer_ or _Referer_ headers or "/".
826
*
827
* Examples:
828
*
829
* res.location('/foo/bar').;
830
* res.location('http://example.com');
831
* res.location('../login');
832
*
833
* @param {String} url
834
* @return {ServerResponse} for chaining
835
* @public
836
*/
837
838
res.location = function location(url) {
839
var loc = url;
840
841
// "back" is an alias for the referrer
842
if (url === 'back') {
843
loc = this.req.get('Referrer') || '/';
844
}
845
846
// set location
847
return this.set('Location', encodeUrl(loc));
848
};
849
850
/**
851
* Redirect to the given `url` with optional response `status`
852
* defaulting to 302.
853
*
854
* The resulting `url` is determined by `res.location()`, so
855
* it will play nicely with mounted apps, relative paths,
856
* `"back"` etc.
857
*
858
* Examples:
859
*
860
* res.redirect('/foo/bar');
861
* res.redirect('http://example.com');
862
* res.redirect(301, 'http://example.com');
863
* res.redirect('../login'); // /blog/post/1 -> /blog/login
864
*
865
* @public
866
*/
867
868
res.redirect = function redirect(url) {
869
var address = url;
870
var body;
871
var status = 302;
872
873
// allow status / url
874
if (arguments.length === 2) {
875
if (typeof arguments[0] === 'number') {
876
status = arguments[0];
877
address = arguments[1];
878
} else {
879
deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
880
status = arguments[1];
881
}
882
}
883
884
// Set location header
885
address = this.location(address).get('Location');
886
887
// Support text/{plain,html} by default
888
this.format({
889
text: function(){
890
body = statuses[status] + '. Redirecting to ' + address
891
},
892
893
html: function(){
894
var u = escapeHtml(address);
895
body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
896
},
897
898
default: function(){
899
body = '';
900
}
901
});
902
903
// Respond
904
this.statusCode = status;
905
this.set('Content-Length', Buffer.byteLength(body));
906
907
if (this.req.method === 'HEAD') {
908
this.end();
909
} else {
910
this.end(body);
911
}
912
};
913
914
/**
915
* Add `field` to Vary. If already present in the Vary set, then
916
* this call is simply ignored.
917
*
918
* @param {Array|String} field
919
* @return {ServerResponse} for chaining
920
* @public
921
*/
922
923
res.vary = function(field){
924
// checks for back-compat
925
if (!field || (Array.isArray(field) && !field.length)) {
926
deprecate('res.vary(): Provide a field name');
927
return this;
928
}
929
930
vary(this, field);
931
932
return this;
933
};
934
935
/**
936
* Render `view` with the given `options` and optional callback `fn`.
937
* When a callback function is given a response will _not_ be made
938
* automatically, otherwise a response of _200_ and _text/html_ is given.
939
*
940
* Options:
941
*
942
* - `cache` boolean hinting to the engine it should cache
943
* - `filename` filename of the view being rendered
944
*
945
* @public
946
*/
947
948
res.render = function render(view, options, callback) {
949
var app = this.req.app;
950
var done = callback;
951
var opts = options || {};
952
var req = this.req;
953
var self = this;
954
955
// support callback function as second arg
956
if (typeof options === 'function') {
957
done = options;
958
opts = {};
959
}
960
961
// merge res.locals
962
opts._locals = self.locals;
963
964
// default callback to respond
965
done = done || function (err, str) {
966
if (err) return req.next(err);
967
self.send(str);
968
};
969
970
// render
971
app.render(view, opts, done);
972
};
973
974
// pipe the send file stream
975
function sendfile(res, file, options, callback) {
976
var done = false;
977
var streaming;
978
979
// request aborted
980
function onaborted() {
981
if (done) return;
982
done = true;
983
984
var err = new Error('Request aborted');
985
err.code = 'ECONNABORTED';
986
callback(err);
987
}
988
989
// directory
990
function ondirectory() {
991
if (done) return;
992
done = true;
993
994
var err = new Error('EISDIR, read');
995
err.code = 'EISDIR';
996
callback(err);
997
}
998
999
// errors
1000
function onerror(err) {
1001
if (done) return;
1002
done = true;
1003
callback(err);
1004
}
1005
1006
// ended
1007
function onend() {
1008
if (done) return;
1009
done = true;
1010
callback();
1011
}
1012
1013
// file
1014
function onfile() {
1015
streaming = false;
1016
}
1017
1018
// finished
1019
function onfinish(err) {
1020
if (err && err.code === 'ECONNRESET') return onaborted();
1021
if (err) return onerror(err);
1022
if (done) return;
1023
1024
setImmediate(function () {
1025
if (streaming !== false && !done) {
1026
onaborted();
1027
return;
1028
}
1029
1030
if (done) return;
1031
done = true;
1032
callback();
1033
});
1034
}
1035
1036
// streaming
1037
function onstream() {
1038
streaming = true;
1039
}
1040
1041
file.on('directory', ondirectory);
1042
file.on('end', onend);
1043
file.on('error', onerror);
1044
file.on('file', onfile);
1045
file.on('stream', onstream);
1046
onFinished(res, onfinish);
1047
1048
if (options.headers) {
1049
// set headers on successful transfer
1050
file.on('headers', function headers(res) {
1051
var obj = options.headers;
1052
var keys = Object.keys(obj);
1053
1054
for (var i = 0; i < keys.length; i++) {
1055
var k = keys[i];
1056
res.setHeader(k, obj[k]);
1057
}
1058
});
1059
}
1060
1061
// pipe
1062
file.pipe(res);
1063
}
1064
1065
/**
1066
* Stringify JSON, like JSON.stringify, but v8 optimized.
1067
* @private
1068
*/
1069
1070
function stringify(value, replacer, spaces) {
1071
// v8 checks arguments.length for optimizing simple call
1072
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
1073
return replacer || spaces
1074
? JSON.stringify(value, replacer, spaces)
1075
: JSON.stringify(value);
1076
}
1077
1078