aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/express/lib/response.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/express/lib/response.js')
-rw-r--r--node_modules/express/lib/response.js756
1 files changed, 756 insertions, 0 deletions
diff --git a/node_modules/express/lib/response.js b/node_modules/express/lib/response.js
new file mode 100644
index 0000000..50fcd05
--- /dev/null
+++ b/node_modules/express/lib/response.js
@@ -0,0 +1,756 @@
+/**
+ * Module dependencies.
+ */
+
+var http = require('http')
+ , path = require('path')
+ , connect = require('connect')
+ , utils = connect.utils
+ , sign = require('cookie-signature').sign
+ , normalizeType = require('./utils').normalizeType
+ , normalizeTypes = require('./utils').normalizeTypes
+ , etag = require('./utils').etag
+ , statusCodes = http.STATUS_CODES
+ , cookie = require('cookie')
+ , send = require('send')
+ , mime = connect.mime
+ , basename = path.basename
+ , extname = path.extname
+ , join = path.join;
+
+/**
+ * Response prototype.
+ */
+
+var res = module.exports = {
+ __proto__: http.ServerResponse.prototype
+};
+
+/**
+ * Set status `code`.
+ *
+ * @param {Number} code
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.status = function(code){
+ 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}
+ * @api public
+ */
+
+res.links = function(links){
+ return this.set('Link', Object.keys(links).map(function(rel){
+ return '<' + links[rel] + '>; rel="' + rel + '"';
+ }).join(', '));
+};
+
+/**
+ * Send a response.
+ *
+ * Examples:
+ *
+ * res.send(new Buffer('wahoo'));
+ * res.send({ some: 'json' });
+ * res.send('<p>some html</p>');
+ * res.send(404, 'Sorry, cant find that');
+ * res.send(404);
+ *
+ * @param {Mixed} body or status
+ * @param {Mixed} body
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.send = function(body){
+ var req = this.req
+ , head = 'HEAD' == req.method
+ , len;
+
+ // allow status / body
+ if (2 == arguments.length) {
+ // res.send(body, status) backwards compat
+ if ('number' != typeof body && 'number' == typeof arguments[1]) {
+ this.statusCode = arguments[1];
+ } else {
+ this.statusCode = body;
+ body = arguments[1];
+ }
+ }
+
+ switch (typeof body) {
+ // response status
+ case 'number':
+ this.get('Content-Type') || this.type('txt');
+ this.statusCode = body;
+ body = http.STATUS_CODES[body];
+ break;
+ // string defaulting to html
+ case 'string':
+ if (!this.get('Content-Type')) {
+ this.charset = this.charset || 'utf-8';
+ this.type('html');
+ }
+ break;
+ case 'boolean':
+ case 'object':
+ if (null == body) {
+ body = '';
+ } else if (Buffer.isBuffer(body)) {
+ this.get('Content-Type') || this.type('bin');
+ } else {
+ return this.json(body);
+ }
+ break;
+ }
+
+ // populate Content-Length
+ if (undefined !== body && !this.get('Content-Length')) {
+ this.set('Content-Length', len = Buffer.isBuffer(body)
+ ? body.length
+ : Buffer.byteLength(body));
+ }
+
+ // ETag support
+ // TODO: W/ support
+ if (len > 1024) {
+ if (!this.get('ETag')) {
+ this.set('ETag', etag(body));
+ }
+ }
+
+ // 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');
+ body = '';
+ }
+
+ // respond
+ this.end(head ? null : body);
+ return this;
+};
+
+/**
+ * Send JSON response.
+ *
+ * Examples:
+ *
+ * res.json(null);
+ * res.json({ user: 'tj' });
+ * res.json(500, 'oh noes!');
+ * res.json(404, 'I dont have that');
+ *
+ * @param {Mixed} obj or status
+ * @param {Mixed} obj
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.json = function(obj){
+ // allow status / body
+ if (2 == arguments.length) {
+ // res.json(body, status) backwards compat
+ if ('number' == typeof arguments[1]) {
+ this.statusCode = arguments[1];
+ } else {
+ this.statusCode = obj;
+ obj = arguments[1];
+ }
+ }
+
+ // settings
+ var app = this.app;
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
+ var body = JSON.stringify(obj, replacer, spaces);
+
+ // content-type
+ this.charset = this.charset || 'utf-8';
+ this.get('Content-Type') || this.set('Content-Type', 'application/json');
+
+ return this.send(body);
+};
+
+/**
+ * Send JSON response with JSONP callback support.
+ *
+ * Examples:
+ *
+ * res.jsonp(null);
+ * res.jsonp({ user: 'tj' });
+ * res.jsonp(500, 'oh noes!');
+ * res.jsonp(404, 'I dont have that');
+ *
+ * @param {Mixed} obj or status
+ * @param {Mixed} obj
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.jsonp = function(obj){
+ // allow status / body
+ if (2 == arguments.length) {
+ // res.json(body, status) backwards compat
+ if ('number' == typeof arguments[1]) {
+ this.statusCode = arguments[1];
+ } else {
+ this.statusCode = obj;
+ obj = arguments[1];
+ }
+ }
+
+ // settings
+ var app = this.app;
+ var replacer = app.get('json replacer');
+ var spaces = app.get('json spaces');
+ var body = JSON.stringify(obj, replacer, spaces)
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029');
+ var callback = this.req.query[app.get('jsonp callback name')];
+
+ // content-type
+ this.charset = this.charset || 'utf-8';
+ this.set('Content-Type', 'application/json');
+
+ // jsonp
+ if (callback) {
+ this.set('Content-Type', 'text/javascript');
+ var cb = callback.replace(/[^\[\]\w$.]/g, '');
+ body = cb + ' && ' + cb + '(' + body + ');';
+ }
+
+ return this.send(body);
+};
+
+/**
+ * Transfer the file at the given `path`.
+ *
+ * Automatically sets the _Content-Type_ response header field.
+ * The callback `fn(err)` is invoked when the transfer is complete
+ * or when an error occurs. Be sure to check `res.sentHeader`
+ * if you wish to attempt responding, as the header and some data
+ * may have already been transferred.
+ *
+ * Options:
+ *
+ * - `maxAge` defaulting to 0
+ * - `root` root directory for relative filenames
+ *
+ * Examples:
+ *
+ * The following example illustrates how `res.sendfile()` may
+ * be used as an alternative for the `static()` middleware for
+ * dynamic situations. The code backing `res.sendfile()` is actually
+ * the same code, so HTTP cache support etc is identical.
+ *
+ * app.get('/user/:uid/photos/:file', function(req, res){
+ * var uid = req.params.uid
+ * , file = req.params.file;
+ *
+ * req.user.mayViewFilesFrom(uid, function(yes){
+ * if (yes) {
+ * res.sendfile('/uploads/' + uid + '/' + file);
+ * } else {
+ * res.send(403, 'Sorry! you cant see that.');
+ * }
+ * });
+ * });
+ *
+ * @param {String} path
+ * @param {Object|Function} options or fn
+ * @param {Function} fn
+ * @api public
+ */
+
+res.sendfile = function(path, options, fn){
+ var self = this
+ , req = self.req
+ , next = this.req.next
+ , options = options || {}
+ , done;
+
+ // support function as second arg
+ if ('function' == typeof options) {
+ fn = options;
+ options = {};
+ }
+
+ // socket errors
+ req.socket.on('error', error);
+
+ // errors
+ function error(err) {
+ if (done) return;
+ done = true;
+
+ // clean up
+ cleanup();
+ if (!self.headerSent) self.removeHeader('Content-Disposition');
+
+ // callback available
+ if (fn) return fn(err);
+
+ // list in limbo if there's no callback
+ if (self.headerSent) return;
+
+ // delegate
+ next(err);
+ }
+
+ // streaming
+ function stream() {
+ if (done) return;
+ cleanup();
+ if (fn) self.on('finish', fn);
+ }
+
+ // cleanup
+ function cleanup() {
+ req.socket.removeListener('error', error);
+ }
+
+ // transfer
+ var file = send(req, path);
+ if (options.root) file.root(options.root);
+ file.maxage(options.maxAge || 0);
+ file.on('error', error);
+ file.on('directory', next);
+ file.on('stream', stream);
+ file.pipe(this);
+ this.on('finish', cleanup);
+};
+
+/**
+ * Transfer the file at the given `path` as an attachment.
+ *
+ * Optionally providing an alternate attachment `filename`,
+ * and optional callback `fn(err)`. The callback is invoked
+ * when the data transfer is complete, or when an error has
+ * ocurred. Be sure to check `res.headerSent` if you plan to respond.
+ *
+ * This method uses `res.sendfile()`.
+ *
+ * @param {String} path
+ * @param {String|Function} filename or fn
+ * @param {Function} fn
+ * @api public
+ */
+
+res.download = function(path, filename, fn){
+ // support function as second arg
+ if ('function' == typeof filename) {
+ fn = filename;
+ filename = null;
+ }
+
+ filename = filename || path;
+ this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"');
+ return this.sendfile(path, fn);
+};
+
+/**
+ * Set _Content-Type_ response header with `type` through `mime.lookup()`
+ * when it does not contain "/", or set the Content-Type to `type` otherwise.
+ *
+ * Examples:
+ *
+ * res.type('.html');
+ * res.type('html');
+ * res.type('json');
+ * res.type('application/json');
+ * res.type('png');
+ *
+ * @param {String} type
+ * @return {ServerResponse} for chaining
+ * @api public
+ */
+
+res.contentType =
+res.type = function(type){
+ return this.set('Content-Type', ~type.indexOf('/')
+ ? type
+ : mime.lookup(type));
+};
+
+/**
+ * Respond to the Acceptable formats using an `obj`
+ * of mime-type callbacks.
+ *
+ * This method uses `req.accepted`, an array of
+ * acceptable types ordered by their quality values.
+ * When "Accept" is not present the _first_ callback
+ * is invoked, otherwise the first match is used. When
+ * no match is performed the server responds with
+ * 406 "Not Acceptable".
+ *
+ * Content-Type is set for you, however if you choose
+ * you may alter this within the callback using `res.type()`
+ * or `res.set('Content-Type', ...)`.
+ *
+ * res.format({
+ * 'text/plain': function(){
+ * res.send('hey');
+ * },
+ *
+ * 'text/html': function(){
+ * res.send('<p>hey</p>');
+ * },
+ *
+ * 'appliation/json': function(){
+ * res.send({ message: 'hey' });
+ * }
+ * });
+ *
+ * In addition to canonicalized MIME types you may
+ * also use extnames mapped to these types:
+ *
+ * res.format({
+ * text: function(){
+ * res.send('hey');
+ * },
+ *
+ * html: function(){
+ * res.send('<p>hey</p>');
+ * },
+ *
+ * json: function(){
+ * res.send({ message: 'hey' });
+ * }
+ * });
+ *
+ * By default Express passes an `Error`
+ * with a `.status` of 406 to `next(err)`
+ * if a match is not made. If you provide
+ * a `.default` callback it will be invoked
+ * instead.
+ *
+ * @param {Object} obj
+ * @return {ServerResponse} for chaining
+ * @api public
+ */
+
+res.format = function(obj){
+ var req = this.req
+ , next = req.next;
+
+ var fn = obj.default;
+ if (fn) delete obj.default;
+ var keys = Object.keys(obj);
+
+ var key = req.accepts(keys);
+
+ this.set('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 = 406;
+ err.types = normalizeTypes(keys).map(function(o){ return o.value });
+ next(err);
+ }
+
+ return this;
+};
+
+/**
+ * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
+ *
+ * @param {String} filename
+ * @return {ServerResponse}
+ * @api public
+ */
+
+res.attachment = function(filename){
+ if (filename) this.type(extname(filename));
+ this.set('Content-Disposition', filename
+ ? 'attachment; filename="' + basename(filename) + '"'
+ : 'attachment');
+ return this;
+};
+
+/**
+ * Set header `field` to `val`, or pass
+ * an object of header fields.
+ *
+ * Examples:
+ *
+ * res.set('Foo', ['bar', 'baz']);
+ * res.set('Accept', 'application/json');
+ * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
+ *
+ * Aliased as `res.header()`.
+ *
+ * @param {String|Object|Array} field
+ * @param {String} val
+ * @return {ServerResponse} for chaining
+ * @api public
+ */
+
+res.set =
+res.header = function(field, val){
+ if (2 == arguments.length) {
+ if (Array.isArray(val)) val = val.map(String);
+ else val = String(val);
+ this.setHeader(field, val);
+ } else {
+ for (var key in field) {
+ this.set(key, field[key]);
+ }
+ }
+ return this;
+};
+
+/**
+ * Get value for header `field`.
+ *
+ * @param {String} field
+ * @return {String}
+ * @api public
+ */
+
+res.get = function(field){
+ return this.getHeader(field);
+};
+
+/**
+ * Clear cookie `name`.
+ *
+ * @param {String} name
+ * @param {Object} options
+ * @param {ServerResponse} for chaining
+ * @api public
+ */
+
+res.clearCookie = function(name, options){
+ var opts = { expires: new Date(1), path: '/' };
+ return this.cookie(name, '', options
+ ? utils.merge(opts, options)
+ : opts);
+};
+
+/**
+ * Set cookie `name` to `val`, with the given `options`.
+ *
+ * Options:
+ *
+ * - `maxAge` max-age in milliseconds, converted to `expires`
+ * - `signed` sign the cookie
+ * - `path` defaults to "/"
+ *
+ * Examples:
+ *
+ * // "Remember Me" for 15 minutes
+ * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
+ *
+ * // save as above
+ * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
+ *
+ * @param {String} name
+ * @param {String|Object} val
+ * @param {Options} options
+ * @api public
+ */
+
+res.cookie = function(name, val, options){
+ options = utils.merge({}, options);
+ var secret = this.req.secret;
+ var signed = options.signed;
+ if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies');
+ if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
+ if (signed) val = 's:' + sign(val, secret);
+ if ('maxAge' in options) {
+ options.expires = new Date(Date.now() + options.maxAge);
+ options.maxAge /= 1000;
+ }
+ if (null == options.path) options.path = '/';
+ this.set('Set-Cookie', cookie.serialize(name, String(val), options));
+ return this;
+};
+
+
+/**
+ * Set the location header to `url`.
+ *
+ * The given `url` can also be the name of a mapped url, for
+ * example by default express supports "back" which redirects
+ * to the _Referrer_ or _Referer_ headers or "/".
+ *
+ * Examples:
+ *
+ * res.location('/foo/bar').;
+ * res.location('http://example.com');
+ * res.location('../login'); // /blog/post/1 -> /blog/login
+ *
+ * Mounting:
+ *
+ * When an application is mounted and `res.location()`
+ * is given a path that does _not_ lead with "/" it becomes
+ * relative to the mount-point. For example if the application
+ * is mounted at "/blog", the following would become "/blog/login".
+ *
+ * res.location('login');
+ *
+ * While the leading slash would result in a location of "/login":
+ *
+ * res.location('/login');
+ *
+ * @param {String} url
+ * @api public
+ */
+
+res.location = function(url){
+ var app = this.app
+ , req = this.req;
+
+ // setup redirect map
+ var map = { back: req.get('Referrer') || '/' };
+
+ // perform redirect
+ url = map[url] || url;
+
+ // relative
+ if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
+ var path
+
+ // relative to path
+ if ('.' == url[0]) {
+ path = req.originalUrl.split('?')[0]
+ url = path + ('/' == path[path.length - 1] ? '' : '/') + url;
+ // relative to mount-point
+ } else if ('/' != url[0]) {
+ path = app.path();
+ url = path + '/' + url;
+ }
+ }
+
+ // Respond
+ this.set('Location', url);
+ return this;
+};
+
+/**
+ * Redirect to the given `url` with optional response `status`
+ * defaulting to 302.
+ *
+ * The resulting `url` is determined by `res.location()`, so
+ * it will play nicely with mounted apps, relative paths,
+ * `"back"` etc.
+ *
+ * Examples:
+ *
+ * res.redirect('/foo/bar');
+ * res.redirect('http://example.com');
+ * res.redirect(301, 'http://example.com');
+ * res.redirect('http://example.com', 301);
+ * res.redirect('../login'); // /blog/post/1 -> /blog/login
+ *
+ * @param {String} url
+ * @param {Number} code
+ * @api public
+ */
+
+res.redirect = function(url){
+ var app = this.app
+ , head = 'HEAD' == this.req.method
+ , status = 302
+ , body;
+
+ // allow status / url
+ if (2 == arguments.length) {
+ if ('number' == typeof url) {
+ status = url;
+ url = arguments[1];
+ } else {
+ status = arguments[1];
+ }
+ }
+
+ // Set location header
+ this.location(url);
+ url = this.get('Location');
+
+ // Support text/{plain,html} by default
+ this.format({
+ text: function(){
+ body = statusCodes[status] + '. Redirecting to ' + encodeURI(url);
+ },
+
+ html: function(){
+ var u = utils.escape(url);
+ body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
+ },
+
+ default: function(){
+ body = '';
+ }
+ });
+
+ // Respond
+ this.statusCode = status;
+ this.set('Content-Length', Buffer.byteLength(body));
+ this.end(head ? null : body);
+};
+
+/**
+ * Render `view` with the given `options` and optional callback `fn`.
+ * When a callback function is given a response will _not_ be made
+ * automatically, otherwise a response of _200_ and _text/html_ is given.
+ *
+ * Options:
+ *
+ * - `cache` boolean hinting to the engine it should cache
+ * - `filename` filename of the view being rendered
+ *
+ * @param {String} view
+ * @param {Object|Function} options or callback function
+ * @param {Function} fn
+ * @api public
+ */
+
+res.render = function(view, options, fn){
+ var self = this
+ , options = options || {}
+ , req = this.req
+ , app = req.app;
+
+ // support callback function as second arg
+ if ('function' == typeof options) {
+ fn = options, options = {};
+ }
+
+ // merge res.locals
+ options._locals = self.locals;
+
+ // default callback to respond
+ fn = fn || function(err, str){
+ if (err) return req.next(err);
+ self.send(str);
+ };
+
+ // render
+ app.render(view, options, fn);
+};