koa-lite/lib/application.js

217 lines
4.6 KiB
JavaScript
Raw Normal View History

2013-08-17 07:15:57 +00:00
/**
* Module dependencies.
*/
var debug = require('debug')('koa:application');
2013-08-17 07:15:57 +00:00
var Emitter = require('events').EventEmitter;
var compose = require('koa-compose');
2014-05-01 23:25:08 +00:00
var isJSON = require('koa-is-json');
var response = require('./response');
var context = require('./context');
var request = require('./request');
2014-05-01 23:25:08 +00:00
var finished = require('finished');
var Cookies = require('cookies');
var accepts = require('accepts');
2014-03-13 01:29:14 +00:00
var status = require('statuses');
var assert = require('assert');
2014-05-01 23:25:08 +00:00
var Stream = require('stream');
2013-08-17 07:15:57 +00:00
var http = require('http');
2014-03-11 18:01:33 +00:00
var only = require('only');
2013-08-17 07:15:57 +00:00
var co = require('co');
/**
* Application prototype.
*/
var app = Application.prototype;
/**
* Expose `Application`.
*/
exports = module.exports = Application;
/**
* Initialize a new `Application`.
*
* @api public
*/
function Application() {
if (!(this instanceof Application)) return new Application;
this.env = process.env.NODE_ENV || 'development';
this.subdomainOffset = 2;
this.poweredBy = true;
this.middleware = [];
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
2013-08-17 07:15:57 +00:00
}
/**
* Inherit from `Emitter.prototype`.
*/
Application.prototype.__proto__ = Emitter.prototype;
/**
* Shorthand for:
*
* http.createServer(app.callback()).listen(...)
*
* @param {Mixed} ...
* @return {Server}
* @api public
*/
app.listen = function(){
2013-11-27 05:26:05 +00:00
debug('listen');
2013-08-17 07:15:57 +00:00
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
2014-03-11 18:01:33 +00:00
/**
* Return JSON representation.
*
* @return {Object}
* @api public
*/
app.toJSON = function(){
return only(this, [
'subdomainOffset',
'poweredBy',
'env'
]);
};
2013-08-17 07:15:57 +00:00
/**
* Use the given middleware `fn`.
*
* @param {GeneratorFunction} fn
2013-08-17 07:15:57 +00:00
* @return {Application} self
* @api public
*/
app.use = function(fn){
assert('GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
2013-12-30 00:04:06 +00:00
debug('use %s', fn._name || fn.name || '-');
2013-08-17 07:15:57 +00:00
this.middleware.push(fn);
return this;
};
/**
* Return a request handler callback
2013-08-30 21:09:18 +00:00
* for node's native http server.
2013-08-17 07:15:57 +00:00
*
* @return {Function}
* @api public
*/
app.callback = function(){
var mw = [respond].concat(this.middleware);
var gen = compose(mw);
2013-12-20 06:33:35 +00:00
var fn = co(gen);
2013-08-17 07:15:57 +00:00
var self = this;
2014-05-02 00:46:09 +00:00
if (!this.listeners('error').length) this.on('error', this.onerror);
return function(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
2014-05-01 23:25:08 +00:00
finished(ctx, ctx.onerror);
fn.call(ctx, ctx.onerror);
2013-08-17 07:15:57 +00:00
}
};
/**
* Initialize a new context.
*
* @api private
*/
app.createContext = function(req, res){
var context = Object.create(this.context);
var request = context.request = Object.create(this.request);
var 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.onerror = context.onerror.bind(context);
context.originalUrl = request.originalUrl = req.url;
2013-11-15 18:03:40 +00:00
context.cookies = new Cookies(req, res, this.keys);
context.accept = request.accept = accepts(req);
return context;
2013-11-15 18:09:56 +00:00
};
2013-08-22 02:47:56 +00:00
/**
* Default error handler.
*
* @param {Error} err
* @api private
*/
app.onerror = function(err){
assert(err instanceof Error, 'non-error thrown: ' + err);
if (404 == err.status) return;
if ('test' == this.env) return;
var msg = err.stack || err.toString();
2014-05-01 23:18:40 +00:00
console.error();
console.error(msg.replace(/^/gm, ' '));
2014-05-01 23:18:40 +00:00
console.error();
2013-08-22 02:47:56 +00:00
};
2013-08-17 07:15:57 +00:00
/**
* Response middleware.
*/
2014-03-24 18:21:15 +00:00
function *respond(next) {
if (this.app.poweredBy) this.set('X-Powered-By', 'koa');
2013-12-22 22:48:28 +00:00
yield *next;
2013-08-17 07:15:57 +00:00
2014-04-15 15:39:40 +00:00
// allow bypassing koa
if (false === this.respond) return;
var res = this.res;
if (res.headersSent || !this.writable) return;
2013-12-22 17:26:21 +00:00
var body = this.body;
var code = this.status;
// ignore body
if (status.empty[code]) {
// strip headers
this.body = null;
return res.end();
}
2013-08-17 07:15:57 +00:00
2014-04-15 02:06:32 +00:00
if ('HEAD' == this.method) {
if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));
return res.end();
}
2014-04-15 15:39:40 +00:00
// status body
2014-04-15 02:06:32 +00:00
if (null == body) {
this.type = 'text';
2014-03-13 01:29:14 +00:00
body = status[code];
2014-04-13 02:23:57 +00:00
if (body) this.length = Buffer.byteLength(body);
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);
this.length = Buffer.byteLength(body);
res.end(body);
2014-03-24 18:21:15 +00:00
}