aboutsummaryrefslogtreecommitdiff
path: root/node_modules/koa/lib/response.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/koa/lib/response.js')
-rw-r--r--node_modules/koa/lib/response.js558
1 files changed, 558 insertions, 0 deletions
diff --git a/node_modules/koa/lib/response.js b/node_modules/koa/lib/response.js
new file mode 100644
index 00000000..371875fe
--- /dev/null
+++ b/node_modules/koa/lib/response.js
@@ -0,0 +1,558 @@
+
+'use strict';
+
+/**
+ * Module dependencies.
+ */
+
+const contentDisposition = require('content-disposition');
+const ensureErrorHandler = require('error-inject');
+const getType = require('cache-content-type');
+const onFinish = require('on-finished');
+const isJSON = require('koa-is-json');
+const escape = require('escape-html');
+const typeis = require('type-is').is;
+const statuses = require('statuses');
+const destroy = require('destroy');
+const assert = require('assert');
+const extname = require('path').extname;
+const vary = require('vary');
+const only = require('only');
+const util = require('util');
+
+/**
+ * Prototype.
+ */
+
+module.exports = {
+
+ /**
+ * Return the request socket.
+ *
+ * @return {Connection}
+ * @api public
+ */
+
+ get socket() {
+ return this.res.socket;
+ },
+
+ /**
+ * Return response header.
+ *
+ * @return {Object}
+ * @api public
+ */
+
+ get header() {
+ const { res } = this;
+ return typeof res.getHeaders === 'function'
+ ? res.getHeaders()
+ : res._headers || {}; // Node < 7.7
+ },
+
+ /**
+ * Return response header, alias as response.header
+ *
+ * @return {Object}
+ * @api public
+ */
+
+ get headers() {
+ return this.header;
+ },
+
+ /**
+ * Get response status code.
+ *
+ * @return {Number}
+ * @api public
+ */
+
+ get status() {
+ return this.res.statusCode;
+ },
+
+ /**
+ * Set response status code.
+ *
+ * @param {Number} code
+ * @api public
+ */
+
+ set status(code) {
+ if (this.headerSent) return;
+
+ assert('number' == typeof code, 'status code must be a number');
+ assert(statuses[code], `invalid status code: ${code}`);
+ this._explicitStatus = true;
+ this.res.statusCode = code;
+ if (this.req.httpVersionMajor < 2) this.res.statusMessage = statuses[code];
+ if (this.body && statuses.empty[code]) this.body = null;
+ },
+
+ /**
+ * Get response status message
+ *
+ * @return {String}
+ * @api public
+ */
+
+ get message() {
+ return this.res.statusMessage || statuses[this.status];
+ },
+
+ /**
+ * Set response status message
+ *
+ * @param {String} msg
+ * @api public
+ */
+
+ set message(msg) {
+ this.res.statusMessage = msg;
+ },
+
+ /**
+ * Get response body.
+ *
+ * @return {Mixed}
+ * @api public
+ */
+
+ get body() {
+ return this._body;
+ },
+
+ /**
+ * Set response body.
+ *
+ * @param {String|Buffer|Object|Stream} val
+ * @api public
+ */
+
+ set body(val) {
+ const original = this._body;
+ this._body = val;
+
+ // no content
+ if (null == val) {
+ if (!statuses.empty[this.status]) this.status = 204;
+ this.remove('Content-Type');
+ this.remove('Content-Length');
+ this.remove('Transfer-Encoding');
+ return;
+ }
+
+ // set the status
+ if (!this._explicitStatus) this.status = 200;
+
+ // set the content-type only if not yet set
+ const setType = !this.header['content-type'];
+
+ // string
+ if ('string' == typeof val) {
+ if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
+ this.length = Buffer.byteLength(val);
+ return;
+ }
+
+ // buffer
+ if (Buffer.isBuffer(val)) {
+ if (setType) this.type = 'bin';
+ this.length = val.length;
+ return;
+ }
+
+ // stream
+ if ('function' == typeof val.pipe) {
+ onFinish(this.res, destroy.bind(null, val));
+ ensureErrorHandler(val, err => this.ctx.onerror(err));
+
+ // overwriting
+ if (null != original && original != val) this.remove('Content-Length');
+
+ if (setType) this.type = 'bin';
+ return;
+ }
+
+ // json
+ this.remove('Content-Length');
+ this.type = 'json';
+ },
+
+ /**
+ * Set Content-Length field to `n`.
+ *
+ * @param {Number} n
+ * @api public
+ */
+
+ set length(n) {
+ this.set('Content-Length', n);
+ },
+
+ /**
+ * Return parsed response Content-Length when present.
+ *
+ * @return {Number}
+ * @api public
+ */
+
+ get length() {
+ const len = this.header['content-length'];
+ const body = this.body;
+
+ if (null == len) {
+ if (!body) return;
+ if ('string' == typeof body) return Buffer.byteLength(body);
+ if (Buffer.isBuffer(body)) return body.length;
+ if (isJSON(body)) return Buffer.byteLength(JSON.stringify(body));
+ return;
+ }
+
+ return ~~len;
+ },
+
+ /**
+ * Check if a header has been written to the socket.
+ *
+ * @return {Boolean}
+ * @api public
+ */
+
+ get headerSent() {
+ return this.res.headersSent;
+ },
+
+ /**
+ * Vary on `field`.
+ *
+ * @param {String} field
+ * @api public
+ */
+
+ vary(field) {
+ if (this.headerSent) return;
+
+ vary(this.res, field);
+ },
+
+ /**
+ * Perform a 302 redirect to `url`.
+ *
+ * The string "back" is special-cased
+ * to provide Referrer support, when Referrer
+ * is not present `alt` or "/" is used.
+ *
+ * Examples:
+ *
+ * this.redirect('back');
+ * this.redirect('back', '/index.html');
+ * this.redirect('/login');
+ * this.redirect('http://google.com');
+ *
+ * @param {String} url
+ * @param {String} [alt]
+ * @api public
+ */
+
+ redirect(url, alt) {
+ // location
+ if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
+ this.set('Location', url);
+
+ // status
+ if (!statuses.redirect[this.status]) this.status = 302;
+
+ // html
+ if (this.ctx.accepts('html')) {
+ url = escape(url);
+ this.type = 'text/html; charset=utf-8';
+ this.body = `Redirecting to <a href="${url}">${url}</a>.`;
+ return;
+ }
+
+ // text
+ this.type = 'text/plain; charset=utf-8';
+ this.body = `Redirecting to ${url}.`;
+ },
+
+ /**
+ * Set Content-Disposition header to "attachment" with optional `filename`.
+ *
+ * @param {String} filename
+ * @api public
+ */
+
+ attachment(filename, options) {
+ if (filename) this.type = extname(filename);
+ this.set('Content-Disposition', contentDisposition(filename, options));
+ },
+
+ /**
+ * Set Content-Type response header with `type` through `mime.lookup()`
+ * when it does not contain a charset.
+ *
+ * Examples:
+ *
+ * this.type = '.html';
+ * this.type = 'html';
+ * this.type = 'json';
+ * this.type = 'application/json';
+ * this.type = 'png';
+ *
+ * @param {String} type
+ * @api public
+ */
+
+ set type(type) {
+ type = getType(type);
+ if (type) {
+ this.set('Content-Type', type);
+ } else {
+ this.remove('Content-Type');
+ }
+ },
+
+ /**
+ * Set the Last-Modified date using a string or a Date.
+ *
+ * this.response.lastModified = new Date();
+ * this.response.lastModified = '2013-09-13';
+ *
+ * @param {String|Date} type
+ * @api public
+ */
+
+ set lastModified(val) {
+ if ('string' == typeof val) val = new Date(val);
+ this.set('Last-Modified', val.toUTCString());
+ },
+
+ /**
+ * Get the Last-Modified date in Date form, if it exists.
+ *
+ * @return {Date}
+ * @api public
+ */
+
+ get lastModified() {
+ const date = this.get('last-modified');
+ if (date) return new Date(date);
+ },
+
+ /**
+ * Set the ETag of a response.
+ * This will normalize the quotes if necessary.
+ *
+ * this.response.etag = 'md5hashsum';
+ * this.response.etag = '"md5hashsum"';
+ * this.response.etag = 'W/"123456789"';
+ *
+ * @param {String} etag
+ * @api public
+ */
+
+ set etag(val) {
+ if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
+ this.set('ETag', val);
+ },
+
+ /**
+ * Get the ETag of a response.
+ *
+ * @return {String}
+ * @api public
+ */
+
+ get etag() {
+ return this.get('ETag');
+ },
+
+ /**
+ * Return the response mime type void of
+ * parameters such as "charset".
+ *
+ * @return {String}
+ * @api public
+ */
+
+ get type() {
+ const type = this.get('Content-Type');
+ if (!type) return '';
+ return type.split(';')[0];
+ },
+
+ /**
+ * Check whether the response is one of the listed types.
+ * Pretty much the same as `this.request.is()`.
+ *
+ * @param {String|Array} types...
+ * @return {String|false}
+ * @api public
+ */
+
+ is(types) {
+ const type = this.type;
+ if (!types) return type || false;
+ if (!Array.isArray(types)) types = [].slice.call(arguments);
+ return typeis(type, types);
+ },
+
+ /**
+ * Return response header.
+ *
+ * Examples:
+ *
+ * this.get('Content-Type');
+ * // => "text/plain"
+ *
+ * this.get('content-type');
+ * // => "text/plain"
+ *
+ * @param {String} field
+ * @return {String}
+ * @api public
+ */
+
+ get(field) {
+ return this.header[field.toLowerCase()] || '';
+ },
+
+ /**
+ * Set header `field` to `val`, or pass
+ * an object of header fields.
+ *
+ * Examples:
+ *
+ * this.set('Foo', ['bar', 'baz']);
+ * this.set('Accept', 'application/json');
+ * this.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
+ *
+ * @param {String|Object|Array} field
+ * @param {String} val
+ * @api public
+ */
+
+ set(field, val) {
+ if (this.headerSent) return;
+
+ if (2 == arguments.length) {
+ if (Array.isArray(val)) val = val.map(v => typeof v === 'string' ? v : String(v));
+ else if (typeof val !== 'string') val = String(val);
+ this.res.setHeader(field, val);
+ } else {
+ for (const key in field) {
+ this.set(key, field[key]);
+ }
+ }
+ },
+
+ /**
+ * Append additional header `field` with value `val`.
+ *
+ * Examples:
+ *
+ * ```
+ * this.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
+ * this.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
+ * this.append('Warning', '199 Miscellaneous warning');
+ * ```
+ *
+ * @param {String} field
+ * @param {String|Array} val
+ * @api public
+ */
+
+ append(field, val) {
+ const prev = this.get(field);
+
+ if (prev) {
+ val = Array.isArray(prev)
+ ? prev.concat(val)
+ : [prev].concat(val);
+ }
+
+ return this.set(field, val);
+ },
+
+ /**
+ * Remove header `field`.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+ remove(field) {
+ if (this.headerSent) return;
+
+ this.res.removeHeader(field);
+ },
+
+ /**
+ * Checks if the request is writable.
+ * Tests for the existence of the socket
+ * as node sometimes does not set it.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+ get writable() {
+ // can't write any more after response finished
+ if (this.res.finished) return false;
+
+ const socket = this.res.socket;
+ // There are already pending outgoing res, but still writable
+ // https://github.com/nodejs/node/blob/v4.4.7/lib/_http_server.js#L486
+ if (!socket) return true;
+ return socket.writable;
+ },
+
+ /**
+ * Inspect implementation.
+ *
+ * @return {Object}
+ * @api public
+ */
+
+ inspect() {
+ if (!this.res) return;
+ const o = this.toJSON();
+ o.body = this.body;
+ return o;
+ },
+
+ /**
+ * Return JSON representation.
+ *
+ * @return {Object}
+ * @api public
+ */
+
+ toJSON() {
+ return only(this, [
+ 'status',
+ 'message',
+ 'header'
+ ]);
+ },
+
+ /**
+ * Flush any set headers, and begin the body
+ */
+ flushHeaders() {
+ this.res.flushHeaders();
+ }
+};
+
+/**
+ * Custom inspection implementation for newer Node.js versions.
+ *
+ * @return {Object}
+ * @api public
+ */
+if (util.inspect.custom) {
+ module.exports[util.inspect.custom] = module.exports.inspect;
+}