From d627aff2fb3fe9afed3add8c5670387d3b04b5b2 Mon Sep 17 00:00:00 2001 From: jiangq Date: Fri, 21 Aug 2015 16:27:23 +0800 Subject: [PATCH 1/3] Fixed #2575 Modify the function res.links(), res.links() can accept an array as arguments --- lib/response.js | 1059 +++++++++++++++++++++++---------------------- test/res.links.js | 28 ++ 2 files changed, 573 insertions(+), 514 deletions(-) diff --git a/lib/response.js b/lib/response.js index 641704b04ae..38db5237470 100644 --- a/lib/response.js +++ b/lib/response.js @@ -37,7 +37,7 @@ var vary = require('vary'); */ var res = module.exports = { - __proto__: http.ServerResponse.prototype + __proto__: http.ServerResponse.prototype }; /** @@ -56,8 +56,8 @@ var charsetRegExp = /;\s*charset\s*=/; */ res.status = function status(code) { - this.statusCode = code; - return this; + this.statusCode = code; + return this; }; /** @@ -70,17 +70,46 @@ res.status = function status(code) { * last: 'http://api.example.com/users?page=5' * }); * - * @param {Object} links + * or + * + * res.links([ + * { + * href: 'http://api.example.com/users?page=2', + * rel: 'next', + * title: 'next chapter', + * type: 'text/plain;charset=UTF-8' + * }, + * { + * href: 'http://api.example.com/users?page=5', + * rel: 'last', + * title: 'the grand finale', + * type: 'video/webm' + * } + * ]); + * @param {Object|Array} links * @return {ServerResponse} * @public */ -res.links = function(links){ - var link = this.get('Link') || ''; - if (link) link += ', '; - return this.set('Link', link + Object.keys(links).map(function(rel){ - return '<' + links[rel] + '>; rel="' + rel + '"'; - }).join(', ')); +res.links = function (links) { + var link = this.get('Link') || ''; + if (link) link += ', '; + if (Array.isArray(links)) { + link += Object.keys(links).map(function (i) { + return Object.keys(links[i]).map(function (item) { + if (item === 'href') + return '<' + links[i][item] + '>'; + else + return item + '="' + links[i][item] + '"'; + }).join('; ') + }).join(', ') + } + else { + link += Object.keys(links).map(function (rel) { + return '<' + links[rel] + '>; rel="' + rel + '"'; + }).join(', ') + } + return this.set('Link', link); }; /** @@ -97,114 +126,114 @@ res.links = function(links){ */ res.send = function send(body) { - var chunk = body; - var encoding; - var len; - var req = this.req; - var type; - - // settings - var app = this.app; - - // allow status / body - if (arguments.length === 2) { - // res.send(body, status) backwards compat - if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { - deprecate('res.send(body, status): Use res.status(status).send(body) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.send(status, body): Use res.status(status).send(body) instead'); - this.statusCode = arguments[0]; - chunk = arguments[1]; + var chunk = body; + var encoding; + var len; + var req = this.req; + var type; + + // settings + var app = this.app; + + // allow status / body + if (arguments.length === 2) { + // res.send(body, status) backwards compat + if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { + deprecate('res.send(body, status): Use res.status(status).send(body) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.send(status, body): Use res.status(status).send(body) instead'); + this.statusCode = arguments[0]; + chunk = arguments[1]; + } } - } - // disambiguate res.send(status) and res.send(status, num) - if (typeof chunk === 'number' && arguments.length === 1) { - // res.send(status) will set status message as text string - if (!this.get('Content-Type')) { - this.type('txt'); + // disambiguate res.send(status) and res.send(status, num) + if (typeof chunk === 'number' && arguments.length === 1) { + // res.send(status) will set status message as text string + if (!this.get('Content-Type')) { + this.type('txt'); + } + + deprecate('res.send(status): Use res.sendStatus(status) instead'); + this.statusCode = chunk; + chunk = statusCodes[chunk]; } - deprecate('res.send(status): Use res.sendStatus(status) instead'); - this.statusCode = chunk; - chunk = statusCodes[chunk]; - } - - switch (typeof chunk) { - // string defaulting to html - case 'string': - if (!this.get('Content-Type')) { - this.type('html'); - } - break; - case 'boolean': - case 'number': - case 'object': - if (chunk === null) { - chunk = ''; - } else if (Buffer.isBuffer(chunk)) { - if (!this.get('Content-Type')) { - this.type('bin'); + switch (typeof chunk) { + // string defaulting to html + case 'string': + if (!this.get('Content-Type')) { + this.type('html'); + } + break; + case 'boolean': + case 'number': + case 'object': + if (chunk === null) { + chunk = ''; + } else if (Buffer.isBuffer(chunk)) { + if (!this.get('Content-Type')) { + this.type('bin'); + } + } else { + return this.json(chunk); + } + break; + } + + // write strings in utf-8 + if (typeof chunk === 'string') { + encoding = 'utf8'; + type = this.get('Content-Type'); + + // reflect this in content-type + if (typeof type === 'string') { + this.set('Content-Type', setCharset(type, 'utf-8')); + } + } + + // populate Content-Length + if (chunk !== undefined) { + if (!Buffer.isBuffer(chunk)) { + // convert chunk to Buffer; saves later double conversions + chunk = new Buffer(chunk, encoding); + encoding = undefined; } - } else { - return this.json(chunk); - } - break; - } - - // write strings in utf-8 - if (typeof chunk === 'string') { - encoding = 'utf8'; - type = this.get('Content-Type'); - - // reflect this in content-type - if (typeof type === 'string') { - this.set('Content-Type', setCharset(type, 'utf-8')); + + len = chunk.length; + this.set('Content-Length', len); } - } - - // populate Content-Length - if (chunk !== undefined) { - if (!Buffer.isBuffer(chunk)) { - // convert chunk to Buffer; saves later double conversions - chunk = new Buffer(chunk, encoding); - encoding = undefined; + + // populate ETag + var etag; + var generateETag = len !== undefined && app.get('etag fn'); + if (typeof generateETag === 'function' && !this.get('ETag')) { + if ((etag = generateETag(chunk, encoding))) { + this.set('ETag', etag); + } } - len = chunk.length; - this.set('Content-Length', len); - } + // freshness + if (req.fresh) this.statusCode = 304; - // populate ETag - var etag; - var generateETag = len !== undefined && app.get('etag fn'); - if (typeof generateETag === 'function' && !this.get('ETag')) { - if ((etag = generateETag(chunk, encoding))) { - this.set('ETag', etag); + // strip irrelevant headers + if (204 == this.statusCode || 304 == this.statusCode) { + this.removeHeader('Content-Type'); + this.removeHeader('Content-Length'); + this.removeHeader('Transfer-Encoding'); + chunk = ''; } - } - - // freshness - if (req.fresh) this.statusCode = 304; - - // strip irrelevant headers - if (204 == this.statusCode || 304 == this.statusCode) { - this.removeHeader('Content-Type'); - this.removeHeader('Content-Length'); - this.removeHeader('Transfer-Encoding'); - chunk = ''; - } - - if (req.method === 'HEAD') { - // skip body for HEAD - this.end(); - } else { - // respond - this.end(chunk, encoding); - } - - return this; + + if (req.method === 'HEAD') { + // skip body for HEAD + this.end(); + } else { + // respond + this.end(chunk, encoding); + } + + return this; }; /** @@ -220,33 +249,33 @@ res.send = function send(body) { */ res.json = function json(obj) { - var val = obj; - - // allow status / body - if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; + var val = obj; + + // allow status / body + if (arguments.length === 2) { + // res.json(body, status) backwards compat + if (typeof arguments[1] === 'number') { + deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; + } } - } - // settings - var app = this.app; - var replacer = app.get('json replacer'); - var spaces = app.get('json spaces'); - var body = JSON.stringify(val, replacer, spaces); + // settings + var app = this.app; + var replacer = app.get('json replacer'); + var spaces = app.get('json spaces'); + var body = JSON.stringify(val, replacer, spaces); - // content-type - if (!this.get('Content-Type')) { - this.set('Content-Type', 'application/json'); - } + // content-type + if (!this.get('Content-Type')) { + this.set('Content-Type', 'application/json'); + } - return this.send(body); + return this.send(body); }; /** @@ -262,59 +291,59 @@ res.json = function json(obj) { */ res.jsonp = function jsonp(obj) { - var val = obj; - - // allow status / body - if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; + var val = obj; + + // allow status / body + if (arguments.length === 2) { + // res.json(body, status) backwards compat + if (typeof arguments[1] === 'number') { + deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; + } + } + + // settings + var app = this.app; + var replacer = app.get('json replacer'); + var spaces = app.get('json spaces'); + var body = JSON.stringify(val, replacer, spaces); + var callback = this.req.query[app.get('jsonp callback name')]; + + // content-type + if (!this.get('Content-Type')) { + this.set('X-Content-Type-Options', 'nosniff'); + this.set('Content-Type', 'application/json'); + } + + // fixup callback + if (Array.isArray(callback)) { + callback = callback[0]; } - } - - // settings - var app = this.app; - var replacer = app.get('json replacer'); - var spaces = app.get('json spaces'); - var body = JSON.stringify(val, replacer, spaces); - var callback = this.req.query[app.get('jsonp callback name')]; - - // content-type - if (!this.get('Content-Type')) { - this.set('X-Content-Type-Options', 'nosniff'); - this.set('Content-Type', 'application/json'); - } - - // fixup callback - if (Array.isArray(callback)) { - callback = callback[0]; - } - - // jsonp - if (typeof callback === 'string' && callback.length !== 0) { - this.charset = 'utf-8'; - this.set('X-Content-Type-Options', 'nosniff'); - this.set('Content-Type', 'text/javascript'); - - // restrict callback charset - callback = callback.replace(/[^\[\]\w$.]/g, ''); - - // replace chars not allowed in JavaScript that are in JSON - body = body - .replace(/\u2028/g, '\\u2028') - .replace(/\u2029/g, '\\u2029'); - - // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" - // the typeof check is just to reduce client error noise - body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; - } - - return this.send(body); + + // jsonp + if (typeof callback === 'string' && callback.length !== 0) { + this.charset = 'utf-8'; + this.set('X-Content-Type-Options', 'nosniff'); + this.set('Content-Type', 'text/javascript'); + + // restrict callback charset + callback = callback.replace(/[^\[\]\w$.]/g, ''); + + // replace chars not allowed in JavaScript that are in JSON + body = body + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029'); + + // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" + // the typeof check is just to reduce client error noise + body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; + } + + return this.send(body); }; /** @@ -333,12 +362,12 @@ res.jsonp = function jsonp(obj) { */ res.sendStatus = function sendStatus(statusCode) { - var body = statusCodes[statusCode] || String(statusCode); + var body = statusCodes[statusCode] || String(statusCode); - this.statusCode = statusCode; - this.type('txt'); + this.statusCode = statusCode; + this.type('txt'); - return this.send(body); + return this.send(body); }; /** @@ -383,40 +412,40 @@ res.sendStatus = function sendStatus(statusCode) { */ res.sendFile = function sendFile(path, options, callback) { - var done = callback; - var req = this.req; - var res = this; - var next = req.next; - var opts = options || {}; - - if (!path) { - throw new TypeError('path argument is required to res.sendFile'); - } - - // support function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - if (!opts.root && !isAbsolute(path)) { - throw new TypeError('path must be absolute or specify root to res.sendFile'); - } - - // create file stream - var pathname = encodeURI(path); - var file = send(req, pathname, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { - next(err); + var done = callback; + var req = this.req; + var res = this; + var next = req.next; + var opts = options || {}; + + if (!path) { + throw new TypeError('path argument is required to res.sendFile'); } - }); + + // support function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; + } + + if (!opts.root && !isAbsolute(path)) { + throw new TypeError('path must be absolute or specify root to res.sendFile'); + } + + // create file stream + var pathname = encodeURI(path); + var file = send(req, pathname, opts); + + // transfer + sendfile(res, file, opts, function (err) { + if (done) return done(err); + if (err && err.code === 'EISDIR') return next(); + + // next() all but write errors + if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { + next(err); + } + }); }; /** @@ -461,35 +490,35 @@ res.sendFile = function sendFile(path, options, callback) { */ res.sendfile = function (path, options, callback) { - var done = callback; - var req = this.req; - var res = this; - var next = req.next; - var opts = options || {}; - - // support function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - // create file stream - var file = send(req, path, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') { - next(err); + var done = callback; + var req = this.req; + var res = this; + var next = req.next; + var opts = options || {}; + + // support function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; } - }); + + // create file stream + var file = send(req, path, opts); + + // transfer + sendfile(res, file, opts, function (err) { + if (done) return done(err); + if (err && err.code === 'EISDIR') return next(); + + // next() all but write errors + if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') { + next(err); + } + }); }; res.sendfile = deprecate.function(res.sendfile, - 'res.sendfile: Use res.sendFile instead'); + 'res.sendfile: Use res.sendFile instead'); /** * Transfer the file at the given `path` as an attachment. @@ -505,24 +534,24 @@ res.sendfile = deprecate.function(res.sendfile, */ res.download = function download(path, filename, callback) { - var done = callback; - var name = filename; + var done = callback; + var name = filename; - // support function as second arg - if (typeof filename === 'function') { - done = filename; - name = null; - } + // support function as second arg + if (typeof filename === 'function') { + done = filename; + name = null; + } - // set Content-Disposition when file is sent - var headers = { - 'Content-Disposition': contentDisposition(name || path) - }; + // set Content-Disposition when file is sent + var headers = { + 'Content-Disposition': contentDisposition(name || path) + }; - // Resolve the full path for sendFile - var fullPath = resolve(path); + // Resolve the full path for sendFile + var fullPath = resolve(path); - return this.sendFile(fullPath, { headers: headers }, done); + return this.sendFile(fullPath, {headers: headers}, done); }; /** @@ -543,13 +572,13 @@ res.download = function download(path, filename, callback) { */ res.contentType = -res.type = function contentType(type) { - var ct = type.indexOf('/') === -1 - ? mime.lookup(type) - : type; + res.type = function contentType(type) { + var ct = type.indexOf('/') === -1 + ? mime.lookup(type) + : type; - return this.set('Content-Type', ct); -}; + return this.set('Content-Type', ct); + }; /** * Respond to the Acceptable formats using an `obj` @@ -608,33 +637,35 @@ res.type = function contentType(type) { * @public */ -res.format = function(obj){ - var req = this.req; - var next = req.next; - - var fn = obj.default; - if (fn) delete obj.default; - var keys = Object.keys(obj); - - var key = keys.length > 0 - ? req.accepts(keys) - : false; - - this.vary("Accept"); - - if (key) { - this.set('Content-Type', normalizeType(key).value); - obj[key](req, this, next); - } else if (fn) { - fn(); - } else { - var err = new Error('Not Acceptable'); - err.status = err.statusCode = 406; - err.types = normalizeTypes(keys).map(function(o){ return o.value }); - next(err); - } - - return this; +res.format = function (obj) { + var req = this.req; + var next = req.next; + + var fn = obj.default; + if (fn) delete obj.default; + var keys = Object.keys(obj); + + var key = keys.length > 0 + ? req.accepts(keys) + : false; + + this.vary("Accept"); + + if (key) { + this.set('Content-Type', normalizeType(key).value); + obj[key](req, this, next); + } else if (fn) { + fn(); + } else { + var err = new Error('Not Acceptable'); + err.status = err.statusCode = 406; + err.types = normalizeTypes(keys).map(function (o) { + return o.value + }); + next(err); + } + + return this; }; /** @@ -646,13 +677,13 @@ res.format = function(obj){ */ res.attachment = function attachment(filename) { - if (filename) { - this.type(extname(filename)); - } + if (filename) { + this.type(extname(filename)); + } - this.set('Content-Disposition', contentDisposition(filename)); + this.set('Content-Disposition', contentDisposition(filename)); - return this; + return this; }; /** @@ -671,17 +702,17 @@ res.attachment = function attachment(filename) { */ res.append = function append(field, val) { - var prev = this.get(field); - var value = val; - - if (prev) { - // concat the new and prev vals - value = Array.isArray(prev) ? prev.concat(val) - : Array.isArray(val) ? [prev].concat(val) - : [prev, val]; - } + var prev = this.get(field); + var value = val; + + if (prev) { + // concat the new and prev vals + value = Array.isArray(prev) ? prev.concat(val) + : Array.isArray(val) ? [prev].concat(val) + : [prev, val]; + } - return this.set(field, value); + return this.set(field, value); }; /** @@ -703,26 +734,26 @@ res.append = function append(field, val) { */ res.set = -res.header = function header(field, val) { - if (arguments.length === 2) { - var value = Array.isArray(val) - ? val.map(String) - : String(val); - - // add charset to content-type - if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { - var charset = mime.charsets.lookup(value.split(';')[0]); - if (charset) value += '; charset=' + charset.toLowerCase(); - } - - this.setHeader(field, value); - } else { - for (var key in field) { - this.set(key, field[key]); - } - } - return this; -}; + res.header = function header(field, val) { + if (arguments.length === 2) { + var value = Array.isArray(val) + ? val.map(String) + : String(val); + + // add charset to content-type + if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { + var charset = mime.charsets.lookup(value.split(';')[0]); + if (charset) value += '; charset=' + charset.toLowerCase(); + } + + this.setHeader(field, value); + } else { + for (var key in field) { + this.set(key, field[key]); + } + } + return this; + }; /** * Get value for header `field`. @@ -732,8 +763,8 @@ res.header = function header(field, val) { * @public */ -res.get = function(field){ - return this.getHeader(field); +res.get = function (field) { + return this.getHeader(field); }; /** @@ -746,9 +777,9 @@ res.get = function(field){ */ res.clearCookie = function clearCookie(name, options) { - var opts = merge({ expires: new Date(1), path: '/' }, options); + var opts = merge({expires: new Date(1), path: '/'}, options); - return this.cookie(name, '', opts); + return this.cookie(name, '', opts); }; /** @@ -776,34 +807,34 @@ res.clearCookie = function clearCookie(name, options) { */ res.cookie = function (name, value, options) { - var opts = merge({}, options); - var secret = this.req.secret; - var signed = opts.signed; + var opts = merge({}, options); + var secret = this.req.secret; + var signed = opts.signed; - if (signed && !secret) { - throw new Error('cookieParser("secret") required for signed cookies'); - } + if (signed && !secret) { + throw new Error('cookieParser("secret") required for signed cookies'); + } - var val = typeof value === 'object' - ? 'j:' + JSON.stringify(value) - : String(value); + var val = typeof value === 'object' + ? 'j:' + JSON.stringify(value) + : String(value); - if (signed) { - val = 's:' + sign(val, secret); - } + if (signed) { + val = 's:' + sign(val, secret); + } - if ('maxAge' in opts) { - opts.expires = new Date(Date.now() + opts.maxAge); - opts.maxAge /= 1000; - } + if ('maxAge' in opts) { + opts.expires = new Date(Date.now() + opts.maxAge); + opts.maxAge /= 1000; + } - if (opts.path == null) { - opts.path = '/'; - } + if (opts.path == null) { + opts.path = '/'; + } - this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); + this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); - return this; + return this; }; /** @@ -824,16 +855,16 @@ res.cookie = function (name, value, options) { */ res.location = function location(url) { - var loc = url; + var loc = url; - // "back" is an alias for the referrer - if (url === 'back') { - loc = this.req.get('Referrer') || '/'; - } + // "back" is an alias for the referrer + if (url === 'back') { + loc = this.req.get('Referrer') || '/'; + } - // set location - this.set('Location', loc); - return this; + // set location + this.set('Location', loc); + return this; }; /** @@ -855,50 +886,50 @@ res.location = function location(url) { */ res.redirect = function redirect(url) { - var address = url; - var body; - var status = 302; - - // allow status / url - if (arguments.length === 2) { - if (typeof arguments[0] === 'number') { - status = arguments[0]; - address = arguments[1]; - } else { - deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); - status = arguments[1]; + var address = url; + var body; + var status = 302; + + // allow status / url + if (arguments.length === 2) { + if (typeof arguments[0] === 'number') { + status = arguments[0]; + address = arguments[1]; + } else { + deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); + status = arguments[1]; + } } - } - // Set location header - this.location(address); - address = this.get('Location'); + // Set location header + this.location(address); + address = this.get('Location'); - // Support text/{plain,html} by default - this.format({ - text: function(){ - body = statusCodes[status] + '. Redirecting to ' + encodeURI(address); - }, + // Support text/{plain,html} by default + this.format({ + text: function () { + body = statusCodes[status] + '. Redirecting to ' + encodeURI(address); + }, - html: function(){ - var u = escapeHtml(address); - body = '

' + statusCodes[status] + '. Redirecting to ' + u + '

'; - }, + html: function () { + var u = escapeHtml(address); + body = '

' + statusCodes[status] + '. Redirecting to ' + u + '

'; + }, - default: function(){ - body = ''; - } - }); + default: function () { + body = ''; + } + }); - // Respond - this.statusCode = status; - this.set('Content-Length', Buffer.byteLength(body)); + // Respond + this.statusCode = status; + this.set('Content-Length', Buffer.byteLength(body)); - if (this.req.method === 'HEAD') { - this.end(); - } else { - this.end(body); - } + if (this.req.method === 'HEAD') { + this.end(); + } else { + this.end(body); + } }; /** @@ -910,16 +941,16 @@ res.redirect = function redirect(url) { * @public */ -res.vary = function(field){ - // checks for back-compat - if (!field || (Array.isArray(field) && !field.length)) { - deprecate('res.vary(): Provide a field name'); - return this; - } +res.vary = function (field) { + // checks for back-compat + if (!field || (Array.isArray(field) && !field.length)) { + deprecate('res.vary(): Provide a field name'); + return this; + } - vary(this, field); + vary(this, field); - return this; + return this; }; /** @@ -936,118 +967,118 @@ res.vary = function(field){ */ res.render = function render(view, options, callback) { - var app = this.req.app; - var done = callback; - var opts = options || {}; - var req = this.req; - var self = this; - - // support callback function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - // merge res.locals - opts._locals = self.locals; - - // default callback to respond - done = done || function (err, str) { - if (err) return req.next(err); - self.send(str); - }; - - // render - app.render(view, opts, done); + var app = this.req.app; + var done = callback; + var opts = options || {}; + var req = this.req; + var self = this; + + // support callback function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; + } + + // merge res.locals + opts._locals = self.locals; + + // default callback to respond + done = done || function (err, str) { + if (err) return req.next(err); + self.send(str); + }; + + // render + app.render(view, opts, done); }; // pipe the send file stream function sendfile(res, file, options, callback) { - var done = false; - var streaming; - - // request aborted - function onaborted() { - if (done) return; - done = true; - - var err = new Error('Request aborted'); - err.code = 'ECONNABORTED'; - callback(err); - } - - // directory - function ondirectory() { - if (done) return; - done = true; - - var err = new Error('EISDIR, read'); - err.code = 'EISDIR'; - callback(err); - } - - // errors - function onerror(err) { - if (done) return; - done = true; - callback(err); - } - - // ended - function onend() { - if (done) return; - done = true; - callback(); - } - - // file - function onfile() { - streaming = false; - } - - // finished - function onfinish(err) { - if (err && err.code === 'ECONNRESET') return onaborted(); - if (err) return onerror(err); - if (done) return; - - setImmediate(function () { - if (streaming !== false && !done) { - onaborted(); - return; - } - - if (done) return; - done = true; - callback(); - }); - } - - // streaming - function onstream() { - streaming = true; - } - - file.on('directory', ondirectory); - file.on('end', onend); - file.on('error', onerror); - file.on('file', onfile); - file.on('stream', onstream); - onFinished(res, onfinish); - - if (options.headers) { - // set headers on successful transfer - file.on('headers', function headers(res) { - var obj = options.headers; - var keys = Object.keys(obj); - - for (var i = 0; i < keys.length; i++) { - var k = keys[i]; - res.setHeader(k, obj[k]); - } - }); - } + var done = false; + var streaming; + + // request aborted + function onaborted() { + if (done) return; + done = true; + + var err = new Error('Request aborted'); + err.code = 'ECONNABORTED'; + callback(err); + } + + // directory + function ondirectory() { + if (done) return; + done = true; + + var err = new Error('EISDIR, read'); + err.code = 'EISDIR'; + callback(err); + } + + // errors + function onerror(err) { + if (done) return; + done = true; + callback(err); + } + + // ended + function onend() { + if (done) return; + done = true; + callback(); + } + + // file + function onfile() { + streaming = false; + } + + // finished + function onfinish(err) { + if (err && err.code === 'ECONNRESET') return onaborted(); + if (err) return onerror(err); + if (done) return; + + setImmediate(function () { + if (streaming !== false && !done) { + onaborted(); + return; + } + + if (done) return; + done = true; + callback(); + }); + } + + // streaming + function onstream() { + streaming = true; + } + + file.on('directory', ondirectory); + file.on('end', onend); + file.on('error', onerror); + file.on('file', onfile); + file.on('stream', onstream); + onFinished(res, onfinish); + + if (options.headers) { + // set headers on successful transfer + file.on('headers', function headers(res) { + var obj = options.headers; + var keys = Object.keys(obj); + + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + res.setHeader(k, obj[k]); + } + }); + } - // pipe - file.pipe(res); + // pipe + file.pipe(res); } diff --git a/test/res.links.js b/test/res.links.js index 36630c9ccc5..15271faf980 100644 --- a/test/res.links.js +++ b/test/res.links.js @@ -42,5 +42,33 @@ describe('res', function(){ .expect('Link', '; rel="next", ; rel="last", ; rel="prev"') .expect(200, done); }) + it('should set Link header field with an Array', function (done) { + var app = express(); + + app.use(function (req, res) { + res.links([ + { + href: 'http://api.example.com/users?page=2', + rel: 'next', + title: 'next chapter', + type: 'text/plain;charset=UTF-8' + }, + { + href: 'http://api.example.com/users?page=5', + rel: 'last', + title: 'the grand finale', + type: 'video/webm' + } + ]); + + res.end(); + }); + + request(app) + .get('/') + .expect('Link', '; rel="next"; title="next chapter"; type="text/plain;charset=UTF-8",' + + ' ; rel="last"; title="the grand finale"; type="video/webm"') + .expect(200, done); + }) }) }) From d311ac13ae9307e0e4939bedb114411f97498951 Mon Sep 17 00:00:00 2001 From: jiangq Date: Fri, 21 Aug 2015 16:42:27 +0800 Subject: [PATCH 2/3] Fixed #2575 Modify the function res.links(), res.links() can accept an array as arguments(cancel the code format) --- lib/response.js | 1031 ++++++++++++++++++++++++----------------------- 1 file changed, 522 insertions(+), 509 deletions(-) diff --git a/lib/response.js b/lib/response.js index 38db5237470..2497f71f9a8 100644 --- a/lib/response.js +++ b/lib/response.js @@ -37,7 +37,7 @@ var vary = require('vary'); */ var res = module.exports = { - __proto__: http.ServerResponse.prototype + __proto__: http.ServerResponse.prototype }; /** @@ -56,10 +56,25 @@ var charsetRegExp = /;\s*charset\s*=/; */ res.status = function status(code) { - this.statusCode = code; - return this; + this.statusCode = code; + return this; }; +/** + * Set Link header field with the given `links`. + * + * Examples: + * + * res.links({ + * next: 'http://api.example.com/users?page=2', + * last: 'http://api.example.com/users?page=5' + * }); + * + * @param {Object} links + * @return {ServerResponse} + * @public + */ + /** * Set Link header field with the given `links`. * @@ -126,114 +141,114 @@ res.links = function (links) { */ res.send = function send(body) { - var chunk = body; - var encoding; - var len; - var req = this.req; - var type; - - // settings - var app = this.app; - - // allow status / body - if (arguments.length === 2) { - // res.send(body, status) backwards compat - if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { - deprecate('res.send(body, status): Use res.status(status).send(body) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.send(status, body): Use res.status(status).send(body) instead'); - this.statusCode = arguments[0]; - chunk = arguments[1]; - } - } - - // disambiguate res.send(status) and res.send(status, num) - if (typeof chunk === 'number' && arguments.length === 1) { - // res.send(status) will set status message as text string - if (!this.get('Content-Type')) { - this.type('txt'); - } - - deprecate('res.send(status): Use res.sendStatus(status) instead'); - this.statusCode = chunk; - chunk = statusCodes[chunk]; - } - - switch (typeof chunk) { - // string defaulting to html - case 'string': - if (!this.get('Content-Type')) { - this.type('html'); - } - break; - case 'boolean': - case 'number': - case 'object': - if (chunk === null) { - chunk = ''; - } else if (Buffer.isBuffer(chunk)) { - if (!this.get('Content-Type')) { - this.type('bin'); - } - } else { - return this.json(chunk); - } - break; + var chunk = body; + var encoding; + var len; + var req = this.req; + var type; + + // settings + var app = this.app; + + // allow status / body + if (arguments.length === 2) { + // res.send(body, status) backwards compat + if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { + deprecate('res.send(body, status): Use res.status(status).send(body) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.send(status, body): Use res.status(status).send(body) instead'); + this.statusCode = arguments[0]; + chunk = arguments[1]; } + } - // write strings in utf-8 - if (typeof chunk === 'string') { - encoding = 'utf8'; - type = this.get('Content-Type'); - - // reflect this in content-type - if (typeof type === 'string') { - this.set('Content-Type', setCharset(type, 'utf-8')); - } + // disambiguate res.send(status) and res.send(status, num) + if (typeof chunk === 'number' && arguments.length === 1) { + // res.send(status) will set status message as text string + if (!this.get('Content-Type')) { + this.type('txt'); } - // populate Content-Length - if (chunk !== undefined) { - if (!Buffer.isBuffer(chunk)) { - // convert chunk to Buffer; saves later double conversions - chunk = new Buffer(chunk, encoding); - encoding = undefined; + deprecate('res.send(status): Use res.sendStatus(status) instead'); + this.statusCode = chunk; + chunk = statusCodes[chunk]; + } + + switch (typeof chunk) { + // string defaulting to html + case 'string': + if (!this.get('Content-Type')) { + this.type('html'); + } + break; + case 'boolean': + case 'number': + case 'object': + if (chunk === null) { + chunk = ''; + } else if (Buffer.isBuffer(chunk)) { + if (!this.get('Content-Type')) { + this.type('bin'); } - - len = chunk.length; - this.set('Content-Length', len); + } else { + return this.json(chunk); + } + break; + } + + // write strings in utf-8 + if (typeof chunk === 'string') { + encoding = 'utf8'; + type = this.get('Content-Type'); + + // reflect this in content-type + if (typeof type === 'string') { + this.set('Content-Type', setCharset(type, 'utf-8')); } - - // populate ETag - var etag; - var generateETag = len !== undefined && app.get('etag fn'); - if (typeof generateETag === 'function' && !this.get('ETag')) { - if ((etag = generateETag(chunk, encoding))) { - this.set('ETag', etag); - } + } + + // populate Content-Length + if (chunk !== undefined) { + if (!Buffer.isBuffer(chunk)) { + // convert chunk to Buffer; saves later double conversions + chunk = new Buffer(chunk, encoding); + encoding = undefined; } - // freshness - if (req.fresh) this.statusCode = 304; + len = chunk.length; + this.set('Content-Length', len); + } - // strip irrelevant headers - if (204 == this.statusCode || 304 == this.statusCode) { - this.removeHeader('Content-Type'); - this.removeHeader('Content-Length'); - this.removeHeader('Transfer-Encoding'); - chunk = ''; + // populate ETag + var etag; + var generateETag = len !== undefined && app.get('etag fn'); + if (typeof generateETag === 'function' && !this.get('ETag')) { + if ((etag = generateETag(chunk, encoding))) { + this.set('ETag', etag); } - - if (req.method === 'HEAD') { - // skip body for HEAD - this.end(); - } else { - // respond - this.end(chunk, encoding); - } - - return this; + } + + // freshness + if (req.fresh) this.statusCode = 304; + + // strip irrelevant headers + if (204 == this.statusCode || 304 == this.statusCode) { + this.removeHeader('Content-Type'); + this.removeHeader('Content-Length'); + this.removeHeader('Transfer-Encoding'); + chunk = ''; + } + + if (req.method === 'HEAD') { + // skip body for HEAD + this.end(); + } else { + // respond + this.end(chunk, encoding); + } + + return this; }; /** @@ -249,33 +264,33 @@ res.send = function send(body) { */ res.json = function json(obj) { - var val = obj; - - // allow status / body - if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; - } + var val = obj; + + // allow status / body + if (arguments.length === 2) { + // res.json(body, status) backwards compat + if (typeof arguments[1] === 'number') { + deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; } + } - // settings - var app = this.app; - var replacer = app.get('json replacer'); - var spaces = app.get('json spaces'); - var body = JSON.stringify(val, replacer, spaces); + // settings + var app = this.app; + var replacer = app.get('json replacer'); + var spaces = app.get('json spaces'); + var body = JSON.stringify(val, replacer, spaces); - // content-type - if (!this.get('Content-Type')) { - this.set('Content-Type', 'application/json'); - } + // content-type + if (!this.get('Content-Type')) { + this.set('Content-Type', 'application/json'); + } - return this.send(body); + return this.send(body); }; /** @@ -291,59 +306,59 @@ res.json = function json(obj) { */ res.jsonp = function jsonp(obj) { - var val = obj; - - // allow status / body - if (arguments.length === 2) { - // res.json(body, status) backwards compat - if (typeof arguments[1] === 'number') { - deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); - this.statusCode = arguments[1]; - } else { - deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); - this.statusCode = arguments[0]; - val = arguments[1]; - } - } - - // settings - var app = this.app; - var replacer = app.get('json replacer'); - var spaces = app.get('json spaces'); - var body = JSON.stringify(val, replacer, spaces); - var callback = this.req.query[app.get('jsonp callback name')]; - - // content-type - if (!this.get('Content-Type')) { - this.set('X-Content-Type-Options', 'nosniff'); - this.set('Content-Type', 'application/json'); - } - - // fixup callback - if (Array.isArray(callback)) { - callback = callback[0]; - } - - // jsonp - if (typeof callback === 'string' && callback.length !== 0) { - this.charset = 'utf-8'; - this.set('X-Content-Type-Options', 'nosniff'); - this.set('Content-Type', 'text/javascript'); - - // restrict callback charset - callback = callback.replace(/[^\[\]\w$.]/g, ''); - - // replace chars not allowed in JavaScript that are in JSON - body = body - .replace(/\u2028/g, '\\u2028') - .replace(/\u2029/g, '\\u2029'); - - // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" - // the typeof check is just to reduce client error noise - body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; + var val = obj; + + // allow status / body + if (arguments.length === 2) { + // res.json(body, status) backwards compat + if (typeof arguments[1] === 'number') { + deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); + this.statusCode = arguments[1]; + } else { + deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); + this.statusCode = arguments[0]; + val = arguments[1]; } - - return this.send(body); + } + + // settings + var app = this.app; + var replacer = app.get('json replacer'); + var spaces = app.get('json spaces'); + var body = JSON.stringify(val, replacer, spaces); + var callback = this.req.query[app.get('jsonp callback name')]; + + // content-type + if (!this.get('Content-Type')) { + this.set('X-Content-Type-Options', 'nosniff'); + this.set('Content-Type', 'application/json'); + } + + // fixup callback + if (Array.isArray(callback)) { + callback = callback[0]; + } + + // jsonp + if (typeof callback === 'string' && callback.length !== 0) { + this.charset = 'utf-8'; + this.set('X-Content-Type-Options', 'nosniff'); + this.set('Content-Type', 'text/javascript'); + + // restrict callback charset + callback = callback.replace(/[^\[\]\w$.]/g, ''); + + // replace chars not allowed in JavaScript that are in JSON + body = body + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029'); + + // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" + // the typeof check is just to reduce client error noise + body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; + } + + return this.send(body); }; /** @@ -362,12 +377,12 @@ res.jsonp = function jsonp(obj) { */ res.sendStatus = function sendStatus(statusCode) { - var body = statusCodes[statusCode] || String(statusCode); + var body = statusCodes[statusCode] || String(statusCode); - this.statusCode = statusCode; - this.type('txt'); + this.statusCode = statusCode; + this.type('txt'); - return this.send(body); + return this.send(body); }; /** @@ -412,40 +427,40 @@ res.sendStatus = function sendStatus(statusCode) { */ res.sendFile = function sendFile(path, options, callback) { - var done = callback; - var req = this.req; - var res = this; - var next = req.next; - var opts = options || {}; - - if (!path) { - throw new TypeError('path argument is required to res.sendFile'); - } - - // support function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - if (!opts.root && !isAbsolute(path)) { - throw new TypeError('path must be absolute or specify root to res.sendFile'); + var done = callback; + var req = this.req; + var res = this; + var next = req.next; + var opts = options || {}; + + if (!path) { + throw new TypeError('path argument is required to res.sendFile'); + } + + // support function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; + } + + if (!opts.root && !isAbsolute(path)) { + throw new TypeError('path must be absolute or specify root to res.sendFile'); + } + + // create file stream + var pathname = encodeURI(path); + var file = send(req, pathname, opts); + + // transfer + sendfile(res, file, opts, function (err) { + if (done) return done(err); + if (err && err.code === 'EISDIR') return next(); + + // next() all but write errors + if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { + next(err); } - - // create file stream - var pathname = encodeURI(path); - var file = send(req, pathname, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { - next(err); - } - }); + }); }; /** @@ -490,35 +505,35 @@ res.sendFile = function sendFile(path, options, callback) { */ res.sendfile = function (path, options, callback) { - var done = callback; - var req = this.req; - var res = this; - var next = req.next; - var opts = options || {}; - - // support function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; + var done = callback; + var req = this.req; + var res = this; + var next = req.next; + var opts = options || {}; + + // support function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; + } + + // create file stream + var file = send(req, path, opts); + + // transfer + sendfile(res, file, opts, function (err) { + if (done) return done(err); + if (err && err.code === 'EISDIR') return next(); + + // next() all but write errors + if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') { + next(err); } - - // create file stream - var file = send(req, path, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') { - next(err); - } - }); + }); }; res.sendfile = deprecate.function(res.sendfile, - 'res.sendfile: Use res.sendFile instead'); + 'res.sendfile: Use res.sendFile instead'); /** * Transfer the file at the given `path` as an attachment. @@ -534,24 +549,24 @@ res.sendfile = deprecate.function(res.sendfile, */ res.download = function download(path, filename, callback) { - var done = callback; - var name = filename; + var done = callback; + var name = filename; - // support function as second arg - if (typeof filename === 'function') { - done = filename; - name = null; - } + // support function as second arg + if (typeof filename === 'function') { + done = filename; + name = null; + } - // set Content-Disposition when file is sent - var headers = { - 'Content-Disposition': contentDisposition(name || path) - }; + // set Content-Disposition when file is sent + var headers = { + 'Content-Disposition': contentDisposition(name || path) + }; - // Resolve the full path for sendFile - var fullPath = resolve(path); + // Resolve the full path for sendFile + var fullPath = resolve(path); - return this.sendFile(fullPath, {headers: headers}, done); + return this.sendFile(fullPath, { headers: headers }, done); }; /** @@ -572,13 +587,13 @@ res.download = function download(path, filename, callback) { */ res.contentType = - res.type = function contentType(type) { - var ct = type.indexOf('/') === -1 - ? mime.lookup(type) - : type; +res.type = function contentType(type) { + var ct = type.indexOf('/') === -1 + ? mime.lookup(type) + : type; - return this.set('Content-Type', ct); - }; + return this.set('Content-Type', ct); +}; /** * Respond to the Acceptable formats using an `obj` @@ -637,35 +652,33 @@ res.contentType = * @public */ -res.format = function (obj) { - var req = this.req; - var next = req.next; - - var fn = obj.default; - if (fn) delete obj.default; - var keys = Object.keys(obj); - - var key = keys.length > 0 - ? req.accepts(keys) - : false; - - this.vary("Accept"); - - if (key) { - this.set('Content-Type', normalizeType(key).value); - obj[key](req, this, next); - } else if (fn) { - fn(); - } else { - var err = new Error('Not Acceptable'); - err.status = err.statusCode = 406; - err.types = normalizeTypes(keys).map(function (o) { - return o.value - }); - next(err); - } - - return this; +res.format = function(obj){ + var req = this.req; + var next = req.next; + + var fn = obj.default; + if (fn) delete obj.default; + var keys = Object.keys(obj); + + var key = keys.length > 0 + ? req.accepts(keys) + : false; + + this.vary("Accept"); + + if (key) { + this.set('Content-Type', normalizeType(key).value); + obj[key](req, this, next); + } else if (fn) { + fn(); + } else { + var err = new Error('Not Acceptable'); + err.status = err.statusCode = 406; + err.types = normalizeTypes(keys).map(function(o){ return o.value }); + next(err); + } + + return this; }; /** @@ -677,13 +690,13 @@ res.format = function (obj) { */ res.attachment = function attachment(filename) { - if (filename) { - this.type(extname(filename)); - } + if (filename) { + this.type(extname(filename)); + } - this.set('Content-Disposition', contentDisposition(filename)); + this.set('Content-Disposition', contentDisposition(filename)); - return this; + return this; }; /** @@ -702,17 +715,17 @@ res.attachment = function attachment(filename) { */ res.append = function append(field, val) { - var prev = this.get(field); - var value = val; - - if (prev) { - // concat the new and prev vals - value = Array.isArray(prev) ? prev.concat(val) - : Array.isArray(val) ? [prev].concat(val) - : [prev, val]; - } + var prev = this.get(field); + var value = val; + + if (prev) { + // concat the new and prev vals + value = Array.isArray(prev) ? prev.concat(val) + : Array.isArray(val) ? [prev].concat(val) + : [prev, val]; + } - return this.set(field, value); + return this.set(field, value); }; /** @@ -734,26 +747,26 @@ res.append = function append(field, val) { */ res.set = - res.header = function header(field, val) { - if (arguments.length === 2) { - var value = Array.isArray(val) - ? val.map(String) - : String(val); - - // add charset to content-type - if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { - var charset = mime.charsets.lookup(value.split(';')[0]); - if (charset) value += '; charset=' + charset.toLowerCase(); - } - - this.setHeader(field, value); - } else { - for (var key in field) { - this.set(key, field[key]); - } - } - return this; - }; +res.header = function header(field, val) { + if (arguments.length === 2) { + var value = Array.isArray(val) + ? val.map(String) + : String(val); + + // add charset to content-type + if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { + var charset = mime.charsets.lookup(value.split(';')[0]); + if (charset) value += '; charset=' + charset.toLowerCase(); + } + + this.setHeader(field, value); + } else { + for (var key in field) { + this.set(key, field[key]); + } + } + return this; +}; /** * Get value for header `field`. @@ -763,8 +776,8 @@ res.set = * @public */ -res.get = function (field) { - return this.getHeader(field); +res.get = function(field){ + return this.getHeader(field); }; /** @@ -777,9 +790,9 @@ res.get = function (field) { */ res.clearCookie = function clearCookie(name, options) { - var opts = merge({expires: new Date(1), path: '/'}, options); + var opts = merge({ expires: new Date(1), path: '/' }, options); - return this.cookie(name, '', opts); + return this.cookie(name, '', opts); }; /** @@ -807,34 +820,34 @@ res.clearCookie = function clearCookie(name, options) { */ res.cookie = function (name, value, options) { - var opts = merge({}, options); - var secret = this.req.secret; - var signed = opts.signed; + var opts = merge({}, options); + var secret = this.req.secret; + var signed = opts.signed; - if (signed && !secret) { - throw new Error('cookieParser("secret") required for signed cookies'); - } + if (signed && !secret) { + throw new Error('cookieParser("secret") required for signed cookies'); + } - var val = typeof value === 'object' - ? 'j:' + JSON.stringify(value) - : String(value); + var val = typeof value === 'object' + ? 'j:' + JSON.stringify(value) + : String(value); - if (signed) { - val = 's:' + sign(val, secret); - } + if (signed) { + val = 's:' + sign(val, secret); + } - if ('maxAge' in opts) { - opts.expires = new Date(Date.now() + opts.maxAge); - opts.maxAge /= 1000; - } + if ('maxAge' in opts) { + opts.expires = new Date(Date.now() + opts.maxAge); + opts.maxAge /= 1000; + } - if (opts.path == null) { - opts.path = '/'; - } + if (opts.path == null) { + opts.path = '/'; + } - this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); + this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); - return this; + return this; }; /** @@ -855,16 +868,16 @@ res.cookie = function (name, value, options) { */ res.location = function location(url) { - var loc = url; + var loc = url; - // "back" is an alias for the referrer - if (url === 'back') { - loc = this.req.get('Referrer') || '/'; - } + // "back" is an alias for the referrer + if (url === 'back') { + loc = this.req.get('Referrer') || '/'; + } - // set location - this.set('Location', loc); - return this; + // set location + this.set('Location', loc); + return this; }; /** @@ -886,50 +899,50 @@ res.location = function location(url) { */ res.redirect = function redirect(url) { - var address = url; - var body; - var status = 302; - - // allow status / url - if (arguments.length === 2) { - if (typeof arguments[0] === 'number') { - status = arguments[0]; - address = arguments[1]; - } else { - deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); - status = arguments[1]; - } + var address = url; + var body; + var status = 302; + + // allow status / url + if (arguments.length === 2) { + if (typeof arguments[0] === 'number') { + status = arguments[0]; + address = arguments[1]; + } else { + deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); + status = arguments[1]; } + } - // Set location header - this.location(address); - address = this.get('Location'); + // Set location header + this.location(address); + address = this.get('Location'); - // Support text/{plain,html} by default - this.format({ - text: function () { - body = statusCodes[status] + '. Redirecting to ' + encodeURI(address); - }, + // Support text/{plain,html} by default + this.format({ + text: function(){ + body = statusCodes[status] + '. Redirecting to ' + encodeURI(address); + }, - html: function () { - var u = escapeHtml(address); - body = '

' + statusCodes[status] + '. Redirecting to ' + u + '

'; - }, + html: function(){ + var u = escapeHtml(address); + body = '

' + statusCodes[status] + '. Redirecting to ' + u + '

'; + }, - default: function () { - body = ''; - } - }); + default: function(){ + body = ''; + } + }); - // Respond - this.statusCode = status; - this.set('Content-Length', Buffer.byteLength(body)); + // Respond + this.statusCode = status; + this.set('Content-Length', Buffer.byteLength(body)); - if (this.req.method === 'HEAD') { - this.end(); - } else { - this.end(body); - } + if (this.req.method === 'HEAD') { + this.end(); + } else { + this.end(body); + } }; /** @@ -941,16 +954,16 @@ res.redirect = function redirect(url) { * @public */ -res.vary = function (field) { - // checks for back-compat - if (!field || (Array.isArray(field) && !field.length)) { - deprecate('res.vary(): Provide a field name'); - return this; - } +res.vary = function(field){ + // checks for back-compat + if (!field || (Array.isArray(field) && !field.length)) { + deprecate('res.vary(): Provide a field name'); + return this; + } - vary(this, field); + vary(this, field); - return this; + return this; }; /** @@ -967,118 +980,118 @@ res.vary = function (field) { */ res.render = function render(view, options, callback) { - var app = this.req.app; - var done = callback; - var opts = options || {}; - var req = this.req; - var self = this; - - // support callback function as second arg - if (typeof options === 'function') { - done = options; - opts = {}; - } - - // merge res.locals - opts._locals = self.locals; - - // default callback to respond - done = done || function (err, str) { - if (err) return req.next(err); - self.send(str); - }; - - // render - app.render(view, opts, done); + var app = this.req.app; + var done = callback; + var opts = options || {}; + var req = this.req; + var self = this; + + // support callback function as second arg + if (typeof options === 'function') { + done = options; + opts = {}; + } + + // merge res.locals + opts._locals = self.locals; + + // default callback to respond + done = done || function (err, str) { + if (err) return req.next(err); + self.send(str); + }; + + // render + app.render(view, opts, done); }; // pipe the send file stream function sendfile(res, file, options, callback) { - var done = false; - var streaming; - - // request aborted - function onaborted() { - if (done) return; - done = true; - - var err = new Error('Request aborted'); - err.code = 'ECONNABORTED'; - callback(err); - } - - // directory - function ondirectory() { - if (done) return; - done = true; - - var err = new Error('EISDIR, read'); - err.code = 'EISDIR'; - callback(err); - } - - // errors - function onerror(err) { - if (done) return; - done = true; - callback(err); - } - - // ended - function onend() { - if (done) return; - done = true; - callback(); - } - - // file - function onfile() { - streaming = false; - } - - // finished - function onfinish(err) { - if (err && err.code === 'ECONNRESET') return onaborted(); - if (err) return onerror(err); - if (done) return; - - setImmediate(function () { - if (streaming !== false && !done) { - onaborted(); - return; - } - - if (done) return; - done = true; - callback(); - }); - } - - // streaming - function onstream() { - streaming = true; - } - - file.on('directory', ondirectory); - file.on('end', onend); - file.on('error', onerror); - file.on('file', onfile); - file.on('stream', onstream); - onFinished(res, onfinish); - - if (options.headers) { - // set headers on successful transfer - file.on('headers', function headers(res) { - var obj = options.headers; - var keys = Object.keys(obj); - - for (var i = 0; i < keys.length; i++) { - var k = keys[i]; - res.setHeader(k, obj[k]); - } - }); - } + var done = false; + var streaming; + + // request aborted + function onaborted() { + if (done) return; + done = true; + + var err = new Error('Request aborted'); + err.code = 'ECONNABORTED'; + callback(err); + } + + // directory + function ondirectory() { + if (done) return; + done = true; + + var err = new Error('EISDIR, read'); + err.code = 'EISDIR'; + callback(err); + } + + // errors + function onerror(err) { + if (done) return; + done = true; + callback(err); + } + + // ended + function onend() { + if (done) return; + done = true; + callback(); + } + + // file + function onfile() { + streaming = false; + } + + // finished + function onfinish(err) { + if (err && err.code === 'ECONNRESET') return onaborted(); + if (err) return onerror(err); + if (done) return; + + setImmediate(function () { + if (streaming !== false && !done) { + onaborted(); + return; + } + + if (done) return; + done = true; + callback(); + }); + } + + // streaming + function onstream() { + streaming = true; + } + + file.on('directory', ondirectory); + file.on('end', onend); + file.on('error', onerror); + file.on('file', onfile); + file.on('stream', onstream); + onFinished(res, onfinish); + + if (options.headers) { + // set headers on successful transfer + file.on('headers', function headers(res) { + var obj = options.headers; + var keys = Object.keys(obj); + + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + res.setHeader(k, obj[k]); + } + }); + } - // pipe - file.pipe(res); + // pipe + file.pipe(res); } From 1e61fc917f8bc7d54d30c2a867eb18e824fd86a4 Mon Sep 17 00:00:00 2001 From: jiangq Date: Fri, 21 Aug 2015 16:45:39 +0800 Subject: [PATCH 3/3] update the annotation --- lib/response.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/response.js b/lib/response.js index 2497f71f9a8..4411e19d2d3 100644 --- a/lib/response.js +++ b/lib/response.js @@ -60,21 +60,6 @@ res.status = function status(code) { return this; }; -/** - * Set Link header field with the given `links`. - * - * Examples: - * - * res.links({ - * next: 'http://api.example.com/users?page=2', - * last: 'http://api.example.com/users?page=5' - * }); - * - * @param {Object} links - * @return {ServerResponse} - * @public - */ - /** * Set Link header field with the given `links`. *