koa-lite/lib/application.js

250 lines
5.6 KiB
JavaScript
Raw Normal View History

'use strict';
2013-08-17 07:15:57 +00:00
/**
* Module dependencies.
*/
const isGeneratorFunction = require('is-generator-function');
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');
const util = require('util');
const Stream = require('stream');
const http = require('http');
const only = require('only');
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
this.proxy = false;
2015-10-13 06:19:42 +00:00
this.middleware = [];
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);
}
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',
'proxy',
2015-10-13 06:19:42 +00:00
'env'
]);
2013-08-17 07:15:57 +00:00
}
/**
* Inspect implementation.
*
* @return {Object}
* @api public
*/
2015-10-13 06:19:42 +00:00
inspect() {
return this.toJSON();
}
2015-10-13 06:19:42 +00:00
/**
* Use the given middleware `fn`.
*
* Old-style middleware will be converted.
*
* @param {Function} fn
2015-10-13 06:19:42 +00:00
* @return {Application} self
* @api public
*/
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
2015-10-13 06:19:42 +00:00
this.middleware.push(fn);
return this;
}
2015-10-13 06:19:42 +00:00
/**
* Return a request handler callback
* for node's native http server.
*
* @return {Function}
* @api public
*/
callback() {
const fn = compose(this.middleware);
2015-10-13 06:19:42 +00:00
if (!this.listenerCount('error')) this.on('error', this.onerror);
2015-10-13 06:19:42 +00:00
const handleRequest = (req, res) => {
2015-10-13 06:19:42 +00:00
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
2015-10-12 20:36:03 +00:00
};
return handleRequest;
2015-10-13 06:19:42 +00:00
}
2013-08-22 02:47:56 +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;
}
2015-10-13 06:19:42 +00:00
/**
* Default error handler.
*
* @param {Error} err
* @api private
*/
onerror(err) {
if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
2015-10-13 06:19:42 +00:00
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
/**
* Response helper.
2013-08-17 07:15:57 +00:00
*/
function respond(ctx) {
2014-04-15 15:39:40 +00:00
// allow bypassing koa
if (false === ctx.respond) return;
const res = ctx.res;
2016-03-04 03:57:52 +00:00
if (!ctx.writable) return;
2013-12-22 17:26:21 +00:00
let body = ctx.body;
const code = ctx.status;
// ignore body
if (statuses.empty[code]) {
// strip headers
ctx.body = null;
return res.end();
}
2013-08-17 07:15:57 +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-04-15 15:39:40 +00:00
// status body
2014-04-15 02:06:32 +00:00
if (null == body) {
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-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
// body: json
body = JSON.stringify(body);
2016-03-04 03:57:52 +00:00
if (!res.headersSent) {
ctx.length = Buffer.byteLength(body);
}
res.end(body);
2014-03-24 18:21:15 +00:00
}