3 * Copyright(c) 2009-2013 TJ Holowaychuk
4 * Copyright(c) 2014-2015 Douglas Christopher Wilson
11 * Module dependencies.
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 merge = require('utils-merge');
24 var sign = require('cookie-signature').sign;
25 var normalizeType = require('./utils').normalizeType;
26 var normalizeTypes = require('./utils').normalizeTypes;
27 var setCharset = require('./utils').setCharset;
28 var statusCodes = http.STATUS_CODES;
29 var cookie = require('cookie');
30 var send = require('send');
31 var extname = path.extname;
33 var resolve = path.resolve;
34 var vary = require('vary');
40 var res = module.exports = {
41 __proto__: http.ServerResponse.prototype
49 var charsetRegExp = /;\s*charset\s*=/;
54 * @param {Number} code
55 * @return {ServerResponse}
59 res.status = function status(code) {
60 this.statusCode = code;
65 * Set Link header field with the given `links`.
70 * next: 'http://api.example.com/users?page=2',
71 * last: 'http://api.example.com/users?page=5'
74 * @param {Object} links
75 * @return {ServerResponse}
79 res.links = function(links){
80 var link = this.get('Link') || '';
81 if (link) link += ', ';
82 return this.set('Link', link + Object.keys(links).map(function(rel){
83 return '<' + links[rel] + '>; rel="' + rel + '"';
92 * res.send(new Buffer('wahoo'));
93 * res.send({ some: 'json' });
94 * res.send('<p>some html</p>');
96 * @param {string|number|boolean|object|Buffer} body
100 res.send = function send(body) {
110 // allow status / body
111 if (arguments.length === 2) {
112 // res.send(body, status) backwards compat
113 if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
114 deprecate('res.send(body, status): Use res.status(status).send(body) instead');
115 this.statusCode = arguments[1];
117 deprecate('res.send(status, body): Use res.status(status).send(body) instead');
118 this.statusCode = arguments[0];
119 chunk = arguments[1];
123 // disambiguate res.send(status) and res.send(status, num)
124 if (typeof chunk === 'number' && arguments.length === 1) {
125 // res.send(status) will set status message as text string
126 if (!this.get('Content-Type')) {
130 deprecate('res.send(status): Use res.sendStatus(status) instead');
131 this.statusCode = chunk;
132 chunk = statusCodes[chunk];
135 switch (typeof chunk) {
136 // string defaulting to html
138 if (!this.get('Content-Type')) {
145 if (chunk === null) {
147 } else if (Buffer.isBuffer(chunk)) {
148 if (!this.get('Content-Type')) {
152 return this.json(chunk);
157 // write strings in utf-8
158 if (typeof chunk === 'string') {
160 type = this.get('Content-Type');
162 // reflect this in content-type
163 if (typeof type === 'string') {
164 this.set('Content-Type', setCharset(type, 'utf-8'));
168 // populate Content-Length
169 if (chunk !== undefined) {
170 if (!Buffer.isBuffer(chunk)) {
171 // convert chunk to Buffer; saves later double conversions
172 chunk = new Buffer(chunk, encoding);
173 encoding = undefined;
177 this.set('Content-Length', len);
182 var generateETag = len !== undefined && app.get('etag fn');
183 if (typeof generateETag === 'function' && !this.get('ETag')) {
184 if ((etag = generateETag(chunk, encoding))) {
185 this.set('ETag', etag);
190 if (req.fresh) this.statusCode = 304;
192 // strip irrelevant headers
193 if (204 === this.statusCode || 304 === this.statusCode) {
194 this.removeHeader('Content-Type');
195 this.removeHeader('Content-Length');
196 this.removeHeader('Transfer-Encoding');
200 if (req.method === 'HEAD') {
201 // skip body for HEAD
205 this.end(chunk, encoding);
212 * Send JSON response.
217 * res.json({ user: 'tj' });
219 * @param {string|number|boolean|object} obj
223 res.json = function json(obj) {
226 // allow status / body
227 if (arguments.length === 2) {
228 // res.json(body, status) backwards compat
229 if (typeof arguments[1] === 'number') {
230 deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
231 this.statusCode = arguments[1];
233 deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
234 this.statusCode = arguments[0];
241 var replacer = app.get('json replacer');
242 var spaces = app.get('json spaces');
243 var body = stringify(val, replacer, spaces);
246 if (!this.get('Content-Type')) {
247 this.set('Content-Type', 'application/json');
250 return this.send(body);
254 * Send JSON response with JSONP callback support.
259 * res.jsonp({ user: 'tj' });
261 * @param {string|number|boolean|object} obj
265 res.jsonp = function jsonp(obj) {
268 // allow status / body
269 if (arguments.length === 2) {
270 // res.json(body, status) backwards compat
271 if (typeof arguments[1] === 'number') {
272 deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
273 this.statusCode = arguments[1];
275 deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
276 this.statusCode = arguments[0];
283 var replacer = app.get('json replacer');
284 var spaces = app.get('json spaces');
285 var body = stringify(val, replacer, spaces);
286 var callback = this.req.query[app.get('jsonp callback name')];
289 if (!this.get('Content-Type')) {
290 this.set('X-Content-Type-Options', 'nosniff');
291 this.set('Content-Type', 'application/json');
295 if (Array.isArray(callback)) {
296 callback = callback[0];
300 if (typeof callback === 'string' && callback.length !== 0) {
301 this.charset = 'utf-8';
302 this.set('X-Content-Type-Options', 'nosniff');
303 this.set('Content-Type', 'text/javascript');
305 // restrict callback charset
306 callback = callback.replace(/[^\[\]\w$.]/g, '');
308 // replace chars not allowed in JavaScript that are in JSON
310 .replace(/\u2028/g, '\\u2028')
311 .replace(/\u2029/g, '\\u2029');
313 // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
314 // the typeof check is just to reduce client error noise
315 body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
318 return this.send(body);
322 * Send given HTTP status code.
324 * Sets the response status to `statusCode` and the body of the
325 * response to the standard description from node's http.STATUS_CODES
326 * or the statusCode number if no description.
330 * res.sendStatus(200);
332 * @param {number} statusCode
336 res.sendStatus = function sendStatus(statusCode) {
337 var body = statusCodes[statusCode] || String(statusCode);
339 this.statusCode = statusCode;
342 return this.send(body);
346 * Transfer the file at the given `path`.
348 * Automatically sets the _Content-Type_ response header field.
349 * The callback `callback(err)` is invoked when the transfer is complete
350 * or when an error occurs. Be sure to check `res.sentHeader`
351 * if you wish to attempt responding, as the header and some data
352 * may have already been transferred.
356 * - `maxAge` defaulting to 0 (can be string converted by `ms`)
357 * - `root` root directory for relative filenames
358 * - `headers` object of headers to serve with file
359 * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
361 * Other options are passed along to `send`.
365 * The following example illustrates how `res.sendFile()` may
366 * be used as an alternative for the `static()` middleware for
367 * dynamic situations. The code backing `res.sendFile()` is actually
368 * the same code, so HTTP cache support etc is identical.
370 * app.get('/user/:uid/photos/:file', function(req, res){
371 * var uid = req.params.uid
372 * , file = req.params.file;
374 * req.user.mayViewFilesFrom(uid, function(yes){
376 * res.sendFile('/uploads/' + uid + '/' + file);
378 * res.send(403, 'Sorry! you cant see that.');
386 res.sendFile = function sendFile(path, options, callback) {
391 var opts = options || {};
394 throw new TypeError('path argument is required to res.sendFile');
397 // support function as second arg
398 if (typeof options === 'function') {
403 if (!opts.root && !isAbsolute(path)) {
404 throw new TypeError('path must be absolute or specify root to res.sendFile');
407 // create file stream
408 var pathname = encodeURI(path);
409 var file = send(req, pathname, opts);
412 sendfile(res, file, opts, function (err) {
413 if (done) return done(err);
414 if (err && err.code === 'EISDIR') return next();
416 // next() all but write errors
417 if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
424 * Transfer the file at the given `path`.
426 * Automatically sets the _Content-Type_ response header field.
427 * The callback `callback(err)` is invoked when the transfer is complete
428 * or when an error occurs. Be sure to check `res.sentHeader`
429 * if you wish to attempt responding, as the header and some data
430 * may have already been transferred.
434 * - `maxAge` defaulting to 0 (can be string converted by `ms`)
435 * - `root` root directory for relative filenames
436 * - `headers` object of headers to serve with file
437 * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
439 * Other options are passed along to `send`.
443 * The following example illustrates how `res.sendfile()` may
444 * be used as an alternative for the `static()` middleware for
445 * dynamic situations. The code backing `res.sendfile()` is actually
446 * the same code, so HTTP cache support etc is identical.
448 * app.get('/user/:uid/photos/:file', function(req, res){
449 * var uid = req.params.uid
450 * , file = req.params.file;
452 * req.user.mayViewFilesFrom(uid, function(yes){
454 * res.sendfile('/uploads/' + uid + '/' + file);
456 * res.send(403, 'Sorry! you cant see that.');
464 res.sendfile = function (path, options, callback) {
469 var opts = options || {};
471 // support function as second arg
472 if (typeof options === 'function') {
477 // create file stream
478 var file = send(req, path, opts);
481 sendfile(res, file, opts, function (err) {
482 if (done) return done(err);
483 if (err && err.code === 'EISDIR') return next();
485 // next() all but write errors
486 if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
492 res.sendfile = deprecate.function(res.sendfile,
493 'res.sendfile: Use res.sendFile instead');
496 * Transfer the file at the given `path` as an attachment.
498 * Optionally providing an alternate attachment `filename`,
499 * and optional callback `callback(err)`. The callback is invoked
500 * when the data transfer is complete, or when an error has
501 * ocurred. Be sure to check `res.headersSent` if you plan to respond.
503 * This method uses `res.sendfile()`.
508 res.download = function download(path, filename, callback) {
512 // support function as second arg
513 if (typeof filename === 'function') {
518 // set Content-Disposition when file is sent
520 'Content-Disposition': contentDisposition(name || path)
523 // Resolve the full path for sendFile
524 var fullPath = resolve(path);
526 return this.sendFile(fullPath, { headers: headers }, done);
530 * Set _Content-Type_ response header with `type` through `mime.lookup()`
531 * when it does not contain "/", or set the Content-Type to `type` otherwise.
538 * res.type('application/json');
541 * @param {String} type
542 * @return {ServerResponse} for chaining
547 res.type = function contentType(type) {
548 var ct = type.indexOf('/') === -1
552 return this.set('Content-Type', ct);
556 * Respond to the Acceptable formats using an `obj`
557 * of mime-type callbacks.
559 * This method uses `req.accepted`, an array of
560 * acceptable types ordered by their quality values.
561 * When "Accept" is not present the _first_ callback
562 * is invoked, otherwise the first match is used. When
563 * no match is performed the server responds with
564 * 406 "Not Acceptable".
566 * Content-Type is set for you, however if you choose
567 * you may alter this within the callback using `res.type()`
568 * or `res.set('Content-Type', ...)`.
571 * 'text/plain': function(){
575 * 'text/html': function(){
576 * res.send('<p>hey</p>');
579 * 'appliation/json': function(){
580 * res.send({ message: 'hey' });
584 * In addition to canonicalized MIME types you may
585 * also use extnames mapped to these types:
593 * res.send('<p>hey</p>');
597 * res.send({ message: 'hey' });
601 * By default Express passes an `Error`
602 * with a `.status` of 406 to `next(err)`
603 * if a match is not made. If you provide
604 * a `.default` callback it will be invoked
607 * @param {Object} obj
608 * @return {ServerResponse} for chaining
612 res.format = function(obj){
616 var fn = obj.default;
617 if (fn) delete obj.default;
618 var keys = Object.keys(obj);
620 var key = keys.length > 0
627 this.set('Content-Type', normalizeType(key).value);
628 obj[key](req, this, next);
632 var err = new Error('Not Acceptable');
633 err.status = err.statusCode = 406;
634 err.types = normalizeTypes(keys).map(function(o){ return o.value });
642 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
644 * @param {String} filename
645 * @return {ServerResponse}
649 res.attachment = function attachment(filename) {
651 this.type(extname(filename));
654 this.set('Content-Disposition', contentDisposition(filename));
660 * Append additional header `field` with value `val`.
664 * res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
665 * res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
666 * res.append('Warning', '199 Miscellaneous warning');
668 * @param {String} field
669 * @param {String|Array} val
670 * @return {ServerResponse} for chaining
674 res.append = function append(field, val) {
675 var prev = this.get(field);
679 // concat the new and prev vals
680 value = Array.isArray(prev) ? prev.concat(val)
681 : Array.isArray(val) ? [prev].concat(val)
685 return this.set(field, value);
689 * Set header `field` to `val`, or pass
690 * an object of header fields.
694 * res.set('Foo', ['bar', 'baz']);
695 * res.set('Accept', 'application/json');
696 * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
698 * Aliased as `res.header()`.
700 * @param {String|Object} field
701 * @param {String|Array} val
702 * @return {ServerResponse} for chaining
707 res.header = function header(field, val) {
708 if (arguments.length === 2) {
709 var value = Array.isArray(val)
713 // add charset to content-type
714 if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) {
715 var charset = mime.charsets.lookup(value.split(';')[0]);
716 if (charset) value += '; charset=' + charset.toLowerCase();
719 this.setHeader(field, value);
721 for (var key in field) {
722 this.set(key, field[key]);
729 * Get value for header `field`.
731 * @param {String} field
736 res.get = function(field){
737 return this.getHeader(field);
741 * Clear cookie `name`.
743 * @param {String} name
744 * @param {Object} [options]
745 * @return {ServerResponse} for chaining
749 res.clearCookie = function clearCookie(name, options) {
750 var opts = merge({ expires: new Date(1), path: '/' }, options);
752 return this.cookie(name, '', opts);
756 * Set cookie `name` to `value`, with the given `options`.
760 * - `maxAge` max-age in milliseconds, converted to `expires`
761 * - `signed` sign the cookie
762 * - `path` defaults to "/"
766 * // "Remember Me" for 15 minutes
767 * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
770 * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
772 * @param {String} name
773 * @param {String|Object} value
774 * @param {Options} options
775 * @return {ServerResponse} for chaining
779 res.cookie = function (name, value, options) {
780 var opts = merge({}, options);
781 var secret = this.req.secret;
782 var signed = opts.signed;
784 if (signed && !secret) {
785 throw new Error('cookieParser("secret") required for signed cookies');
788 var val = typeof value === 'object'
789 ? 'j:' + JSON.stringify(value)
793 val = 's:' + sign(val, secret);
796 if ('maxAge' in opts) {
797 opts.expires = new Date(Date.now() + opts.maxAge);
801 if (opts.path == null) {
805 this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
811 * Set the location header to `url`.
813 * The given `url` can also be "back", which redirects
814 * to the _Referrer_ or _Referer_ headers or "/".
818 * res.location('/foo/bar').;
819 * res.location('http://example.com');
820 * res.location('../login');
822 * @param {String} url
823 * @return {ServerResponse} for chaining
827 res.location = function location(url) {
830 // "back" is an alias for the referrer
831 if (url === 'back') {
832 loc = this.req.get('Referrer') || '/';
836 return this.set('Location', encodeUrl(loc));
840 * Redirect to the given `url` with optional response `status`
843 * The resulting `url` is determined by `res.location()`, so
844 * it will play nicely with mounted apps, relative paths,
849 * res.redirect('/foo/bar');
850 * res.redirect('http://example.com');
851 * res.redirect(301, 'http://example.com');
852 * res.redirect('../login'); // /blog/post/1 -> /blog/login
857 res.redirect = function redirect(url) {
862 // allow status / url
863 if (arguments.length === 2) {
864 if (typeof arguments[0] === 'number') {
865 status = arguments[0];
866 address = arguments[1];
868 deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
869 status = arguments[1];
873 // Set location header
874 address = this.location(address).get('Location');
876 // Support text/{plain,html} by default
879 body = statusCodes[status] + '. Redirecting to ' + address;
883 var u = escapeHtml(address);
884 body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
893 this.statusCode = status;
894 this.set('Content-Length', Buffer.byteLength(body));
896 if (this.req.method === 'HEAD') {
904 * Add `field` to Vary. If already present in the Vary set, then
905 * this call is simply ignored.
907 * @param {Array|String} field
908 * @return {ServerResponse} for chaining
912 res.vary = function(field){
913 // checks for back-compat
914 if (!field || (Array.isArray(field) && !field.length)) {
915 deprecate('res.vary(): Provide a field name');
925 * Render `view` with the given `options` and optional callback `fn`.
926 * When a callback function is given a response will _not_ be made
927 * automatically, otherwise a response of _200_ and _text/html_ is given.
931 * - `cache` boolean hinting to the engine it should cache
932 * - `filename` filename of the view being rendered
937 res.render = function render(view, options, callback) {
938 var app = this.req.app;
940 var opts = options || {};
944 // support callback function as second arg
945 if (typeof options === 'function') {
951 opts._locals = self.locals;
953 // default callback to respond
954 done = done || function (err, str) {
955 if (err) return req.next(err);
960 app.render(view, opts, done);
963 // pipe the send file stream
964 function sendfile(res, file, options, callback) {
969 function onaborted() {
973 var err = new Error('Request aborted');
974 err.code = 'ECONNABORTED';
979 function ondirectory() {
983 var err = new Error('EISDIR, read');
989 function onerror(err) {
1008 function onfinish(err) {
1009 if (err && err.code === 'ECONNRESET') return onaborted();
1010 if (err) return onerror(err);
1013 setImmediate(function () {
1014 if (streaming !== false && !done) {
1026 function onstream() {
1030 file.on('directory', ondirectory);
1031 file.on('end', onend);
1032 file.on('error', onerror);
1033 file.on('file', onfile);
1034 file.on('stream', onstream);
1035 onFinished(res, onfinish);
1037 if (options.headers) {
1038 // set headers on successful transfer
1039 file.on('headers', function headers(res) {
1040 var obj = options.headers;
1041 var keys = Object.keys(obj);
1043 for (var i = 0; i < keys.length; i++) {
1045 res.setHeader(k, obj[k]);
1055 * Stringify JSON, like JSON.stringify, but v8 optimized.
1059 function stringify(value, replacer, spaces) {
1060 // v8 checks arguments.length for optimizing simple call
1061 // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1062 return replacer || spaces
1063 ? JSON.stringify(value, replacer, spaces)
1064 : JSON.stringify(value);