2015-10-05 23:18:03 +00:00
|
|
|
|
2015-10-11 22:59:51 +00:00
|
|
|
'use strict';
|
|
|
|
|
2013-08-17 07:15:57 +00:00
|
|
|
/**
|
|
|
|
* Module dependencies.
|
|
|
|
*/
|
|
|
|
|
2015-10-14 00:45:18 +00:00
|
|
|
const isGeneratorFunction = require('is-generator-function');
|
2015-10-05 18:23:47 +00:00
|
|
|
const debug = require('debug')('koa:application');
|
|
|
|
const onFinished = require('on-finished');
|
|
|
|
const response = require('./response');
|
|
|
|
const compose = require('koa-compose');
|
|
|
|
const isJSON = require('koa-is-json');
|
|
|
|
const context = require('./context');
|
|
|
|
const request = require('./request');
|
|
|
|
const statuses = require('statuses');
|
|
|
|
const Cookies = require('cookies');
|
|
|
|
const accepts = require('accepts');
|
2015-11-05 16:42:14 +00:00
|
|
|
const Emitter = require('events');
|
2015-10-05 18:23:47 +00:00
|
|
|
const assert = require('assert');
|
|
|
|
const Stream = require('stream');
|
|
|
|
const http = require('http');
|
|
|
|
const only = require('only');
|
2016-03-14 14:51:14 +00:00
|
|
|
const convert = require('koa-convert');
|
|
|
|
const deprecate = require('depd')('koa');
|
2013-08-17 07:15:57 +00:00
|
|
|
|
|
|
|
/**
|
2015-10-13 06:19:42 +00:00
|
|
|
* Expose `Application` class.
|
|
|
|
* Inherits from `Emitter.prototype`.
|
2013-08-17 07:15:57 +00:00
|
|
|
*/
|
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
module.exports = class Application extends Emitter {
|
|
|
|
/**
|
|
|
|
* Initialize a new `Application`.
|
|
|
|
*
|
|
|
|
* @api public
|
|
|
|
*/
|
2014-03-11 18:01:33 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
constructor() {
|
|
|
|
super();
|
2014-03-11 18:01:33 +00:00
|
|
|
|
2015-10-29 16:51:11 +00:00
|
|
|
this.proxy = false;
|
2015-10-13 06:19:42 +00:00
|
|
|
this.middleware = [];
|
2015-10-29 16:51:11 +00:00
|
|
|
this.subdomainOffset = 2;
|
|
|
|
this.env = process.env.NODE_ENV || 'development';
|
2015-10-13 06:19:42 +00:00
|
|
|
this.context = Object.create(context);
|
|
|
|
this.request = Object.create(request);
|
|
|
|
this.response = Object.create(response);
|
2014-12-18 02:06:45 +00:00
|
|
|
}
|
2013-08-17 07:15:57 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Shorthand for:
|
|
|
|
*
|
|
|
|
* http.createServer(app.callback()).listen(...)
|
|
|
|
*
|
|
|
|
* @param {Mixed} ...
|
|
|
|
* @return {Server}
|
|
|
|
* @api public
|
|
|
|
*/
|
|
|
|
|
2017-04-23 23:14:16 +00:00
|
|
|
listen(...args) {
|
2015-10-13 06:19:42 +00:00
|
|
|
debug('listen');
|
|
|
|
const server = http.createServer(this.callback());
|
2017-04-23 23:14:16 +00:00
|
|
|
return server.listen(...args);
|
2015-10-13 06:19:42 +00:00
|
|
|
}
|
2014-05-02 00:46:09 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Return JSON representation.
|
|
|
|
* We only bother showing settings.
|
|
|
|
*
|
|
|
|
* @return {Object}
|
|
|
|
* @api public
|
|
|
|
*/
|
|
|
|
|
|
|
|
toJSON() {
|
|
|
|
return only(this, [
|
|
|
|
'subdomainOffset',
|
2015-10-29 16:51:11 +00:00
|
|
|
'proxy',
|
2015-10-13 06:19:42 +00:00
|
|
|
'env'
|
|
|
|
]);
|
2013-08-17 07:15:57 +00:00
|
|
|
}
|
|
|
|
|
2015-10-29 16:51:11 +00:00
|
|
|
/**
|
|
|
|
* Inspect implementation.
|
|
|
|
*
|
|
|
|
* @return {Object}
|
|
|
|
* @api public
|
|
|
|
*/
|
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
inspect() {
|
|
|
|
return this.toJSON();
|
|
|
|
}
|
2013-11-14 02:34:15 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Use the given middleware `fn`.
|
|
|
|
*
|
2016-03-14 14:51:14 +00:00
|
|
|
* Old-style middleware will be converted.
|
|
|
|
*
|
2015-11-14 13:46:29 +00:00
|
|
|
* @param {Function} fn
|
2015-10-13 06:19:42 +00:00
|
|
|
* @return {Application} self
|
|
|
|
* @api public
|
|
|
|
*/
|
|
|
|
|
|
|
|
use(fn) {
|
2015-10-14 00:45:18 +00:00
|
|
|
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
|
2016-03-14 14:51:14 +00:00
|
|
|
if (isGeneratorFunction(fn)) {
|
2016-10-17 15:45:06 +00:00
|
|
|
deprecate('Support for generators will be removed in v3. ' +
|
2016-03-14 14:51:14 +00:00
|
|
|
'See the documentation for examples of how to convert old middleware ' +
|
2017-03-08 06:59:42 +00:00
|
|
|
'https://github.com/koajs/koa/blob/master/docs/migration.md');
|
2016-03-14 14:51:14 +00:00
|
|
|
fn = convert(fn);
|
|
|
|
}
|
2015-11-25 00:06:30 +00:00
|
|
|
debug('use %s', fn._name || fn.name || '-');
|
2015-10-13 06:19:42 +00:00
|
|
|
this.middleware.push(fn);
|
|
|
|
return this;
|
|
|
|
}
|
2013-11-14 02:34:15 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Return a request handler callback
|
|
|
|
* for node's native http server.
|
|
|
|
*
|
|
|
|
* @return {Function}
|
|
|
|
* @api public
|
|
|
|
*/
|
|
|
|
|
|
|
|
callback() {
|
2015-10-14 00:45:18 +00:00
|
|
|
const fn = compose(this.middleware);
|
2015-10-13 06:19:42 +00:00
|
|
|
|
|
|
|
if (!this.listeners('error').length) this.on('error', this.onerror);
|
|
|
|
|
2017-02-25 06:06:41 +00:00
|
|
|
const handleRequest = (req, res) => {
|
2015-10-13 06:19:42 +00:00
|
|
|
const ctx = this.createContext(req, res);
|
2017-11-06 12:21:52 +00:00
|
|
|
return this.handleRequest(ctx, fn);
|
2015-10-12 20:36:03 +00:00
|
|
|
};
|
2017-02-25 06:06:41 +00:00
|
|
|
|
|
|
|
return handleRequest;
|
2015-10-13 06:19:42 +00:00
|
|
|
}
|
2013-08-22 02:47:56 +00:00
|
|
|
|
2017-11-06 12:21:52 +00:00
|
|
|
/**
|
|
|
|
* Handle request in callback.
|
|
|
|
*
|
|
|
|
* @api private
|
|
|
|
*/
|
|
|
|
|
|
|
|
handleRequest(ctx, fnMiddleware) {
|
|
|
|
const res = ctx.res;
|
|
|
|
res.statusCode = 404;
|
|
|
|
const onerror = err => ctx.onerror(err);
|
|
|
|
const handleResponse = () => respond(ctx);
|
|
|
|
onFinished(res, onerror);
|
|
|
|
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
|
|
|
|
}
|
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Initialize a new context.
|
|
|
|
*
|
|
|
|
* @api private
|
|
|
|
*/
|
|
|
|
|
|
|
|
createContext(req, res) {
|
|
|
|
const context = Object.create(this.context);
|
|
|
|
const request = context.request = Object.create(this.request);
|
|
|
|
const response = context.response = Object.create(this.response);
|
|
|
|
context.app = request.app = response.app = this;
|
|
|
|
context.req = request.req = response.req = req;
|
|
|
|
context.res = request.res = response.res = res;
|
|
|
|
request.ctx = response.ctx = context;
|
|
|
|
request.response = response;
|
|
|
|
response.request = request;
|
|
|
|
context.originalUrl = request.originalUrl = req.url;
|
2015-12-02 15:13:01 +00:00
|
|
|
context.cookies = new Cookies(req, res, {
|
|
|
|
keys: this.keys,
|
|
|
|
secure: request.secure
|
|
|
|
});
|
2016-04-04 02:30:06 +00:00
|
|
|
request.ip = request.ips[0] || req.socket.remoteAddress || '';
|
2015-10-13 06:19:42 +00:00
|
|
|
context.accept = request.accept = accepts(req);
|
|
|
|
context.state = {};
|
|
|
|
return context;
|
|
|
|
}
|
2014-05-23 13:15:03 +00:00
|
|
|
|
2015-10-13 06:19:42 +00:00
|
|
|
/**
|
|
|
|
* Default error handler.
|
|
|
|
*
|
|
|
|
* @param {Error} err
|
|
|
|
* @api private
|
|
|
|
*/
|
|
|
|
|
|
|
|
onerror(err) {
|
|
|
|
assert(err instanceof Error, `non-error thrown: ${err}`);
|
|
|
|
|
|
|
|
if (404 == err.status || err.expose) return;
|
|
|
|
if (this.silent) return;
|
|
|
|
|
|
|
|
const msg = err.stack || err.toString();
|
|
|
|
console.error();
|
|
|
|
console.error(msg.replace(/^/gm, ' '));
|
|
|
|
console.error();
|
|
|
|
}
|
2015-10-12 20:36:03 +00:00
|
|
|
};
|
2013-08-22 02:47:56 +00:00
|
|
|
|
2013-08-17 07:15:57 +00:00
|
|
|
/**
|
2015-08-23 16:08:54 +00:00
|
|
|
* Response helper.
|
2013-08-17 07:15:57 +00:00
|
|
|
*/
|
|
|
|
|
2015-10-14 00:45:18 +00:00
|
|
|
function respond(ctx) {
|
2014-04-15 15:39:40 +00:00
|
|
|
// allow bypassing koa
|
2015-10-14 00:45:18 +00:00
|
|
|
if (false === ctx.respond) return;
|
2014-01-24 22:38:40 +00:00
|
|
|
|
2015-10-14 00:45:18 +00:00
|
|
|
const res = ctx.res;
|
2016-03-04 03:57:52 +00:00
|
|
|
if (!ctx.writable) return;
|
2013-12-22 17:26:21 +00:00
|
|
|
|
2015-10-14 00:45:18 +00:00
|
|
|
let body = ctx.body;
|
|
|
|
const code = ctx.status;
|
2013-08-17 22:36:51 +00:00
|
|
|
|
2013-11-08 00:15:47 +00:00
|
|
|
// ignore body
|
2014-10-01 12:12:20 +00:00
|
|
|
if (statuses.empty[code]) {
|
2014-04-09 16:21:15 +00:00
|
|
|
// strip headers
|
2015-10-14 00:45:18 +00:00
|
|
|
ctx.body = null;
|
2014-04-09 16:21:15 +00:00
|
|
|
return res.end();
|
|
|
|
}
|
2013-08-17 07:15:57 +00:00
|
|
|
|
2015-10-14 00:45:18 +00:00
|
|
|
if ('HEAD' == ctx.method) {
|
2016-03-04 03:57:52 +00:00
|
|
|
if (!res.headersSent && isJSON(body)) {
|
|
|
|
ctx.length = Buffer.byteLength(JSON.stringify(body));
|
|
|
|
}
|
2014-04-15 02:06:32 +00:00
|
|
|
return res.end();
|
|
|
|
}
|
2014-03-23 11:01:14 +00:00
|
|
|
|
2014-04-15 15:39:40 +00:00
|
|
|
// status body
|
2014-04-15 02:06:32 +00:00
|
|
|
if (null == body) {
|
2015-10-14 00:45:18 +00:00
|
|
|
body = ctx.message || String(code);
|
2016-03-04 03:57:52 +00:00
|
|
|
if (!res.headersSent) {
|
|
|
|
ctx.type = 'text';
|
|
|
|
ctx.length = Buffer.byteLength(body);
|
|
|
|
}
|
2014-04-13 02:23:57 +00:00
|
|
|
return res.end(body);
|
2013-11-08 00:15:47 +00:00
|
|
|
}
|
2013-08-17 07:15:57 +00:00
|
|
|
|
2014-04-15 15:39:40 +00:00
|
|
|
// responses
|
2014-04-15 02:06:32 +00:00
|
|
|
if (Buffer.isBuffer(body)) return res.end(body);
|
|
|
|
if ('string' == typeof body) return res.end(body);
|
|
|
|
if (body instanceof Stream) return body.pipe(res);
|
2013-08-17 07:15:57 +00:00
|
|
|
|
2013-11-08 00:15:47 +00:00
|
|
|
// body: json
|
2014-02-01 02:39:47 +00:00
|
|
|
body = JSON.stringify(body);
|
2016-03-04 03:57:52 +00:00
|
|
|
if (!res.headersSent) {
|
|
|
|
ctx.length = Buffer.byteLength(body);
|
|
|
|
}
|
2013-11-08 00:15:47 +00:00
|
|
|
res.end(body);
|
2014-03-24 18:21:15 +00:00
|
|
|
}
|