koa-lite/lib/application.js

241 lines
4.7 KiB
JavaScript
Raw Normal View History

2013-08-17 07:15:57 +00:00
/**
* Module dependencies.
*/
var debug = require('debug')('koa:app');
var Emitter = require('events').EventEmitter;
var compose = require('koa-compose');
var context = require('./context');
var Cookies = require('cookies');
2013-08-17 07:15:57 +00:00
var Stream = require('stream');
var http = require('http');
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.on('error', this.onerror.bind(this));
this.outputErrors = 'test' != this.env;
2013-08-17 07:15:57 +00:00
this.subdomainOffset = 2;
this.poweredBy = true;
this.jsonSpaces = 2;
this.middleware = [];
2013-08-18 18:14:01 +00:00
this.Context = createContext();
this.context(context);
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(){
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
};
/**
* Use the given middleware `fn`.
*
* @param {Function} fn
* @return {Application} self
* @api public
*/
app.use = function(fn){
debug('use %s', fn.name || 'unnamed');
this.middleware.push(fn);
return this;
};
/**
2013-08-18 18:15:22 +00:00
* Mixin `obj` to this app's context.
*
2013-08-18 18:15:22 +00:00
* app.context({
* get something(){
* return 'hi';
* },
*
* set something(val){
* this._something = val;
* },
*
* render: function(){
* this.body = '<html></html>';
* }
* });
*
* @param {Object} obj
* @return {Application} self
* @api public
*/
app.context = function(obj){
2013-08-18 18:14:01 +00:00
var ctx = this.Context.prototype;
Object.getOwnPropertyNames(obj).forEach(function(name){
var descriptor = Object.getOwnPropertyDescriptor(obj, name);
Object.defineProperty(ctx, name, descriptor);
});
return this;
};
2013-08-17 07:15:57 +00:00
/**
* Return a request handler callback
* for node's native htto server.
*
* @return {Function}
* @api public
*/
app.callback = function(){
var mw = [respond].concat(this.middleware);
var fn = compose(mw)(downstream);
var self = this;
return function(req, res){
2013-08-18 18:14:01 +00:00
var ctx = new self.Context(self, req, res);
2013-08-17 07:15:57 +00:00
if (!ctx.socket.listeners('error').length) {
ctx.socket.on('error', function(err){
self.emit('error', err);
});
2013-08-17 07:15:57 +00:00
}
co.call(ctx, function *(){
yield fn;
}, function(err){
if (err) ctx.onerror(err);
});
2013-08-17 07:15:57 +00:00
}
};
2013-08-22 02:47:56 +00:00
/**
* Default error handler.
*
* @param {Error} err
* @api private
*/
app.onerror = function(err){
if (this.outputErrors) console.error(err.stack);
};
2013-08-17 07:15:57 +00:00
/**
* Response middleware.
*/
function respond(next){
return function *(){
yield next;
var app = this.app;
2013-08-17 07:15:57 +00:00
var res = this.res;
var body = this.body;
var head = 'HEAD' == this.method;
var ignore = 204 == this.status || 304 == this.status;
// 404
if (null == body && 200 == this.status) {
this.status = 404;
}
// ignore body
if (ignore) return res.end();
// status body
if (null == body) {
this.set('Content-Type', 'text/plain');
body = http.STATUS_CODES[this.status];
}
2013-08-17 07:15:57 +00:00
// Buffer body
if (Buffer.isBuffer(body)) {
var ct = this.responseHeader['content-type'];
if (!ct) this.set('Content-Type', 'application/octet-stream');
this.set('Content-Length', body.length);
if (head) return res.end();
return res.end(body);
}
// string body
if ('string' == typeof body) {
var ct = this.responseHeader['content-type'];
if (!ct) this.set('Content-Type', 'text/plain; charset=utf-8');
this.set('Content-Length', Buffer.byteLength(body));
if (head) return res.end();
return res.end(body);
}
// Stream body
if (body instanceof Stream) {
body.on('error', this.onerror.bind(this));
2013-08-17 07:15:57 +00:00
if (head) return res.end();
return body.pipe(res);
}
2013-08-17 07:15:57 +00:00
// body: json
body = JSON.stringify(body, null, this.app.jsonSpaces);
this.set('Content-Length', body.length);
this.set('Content-Type', 'application/json');
if (head) return res.end();
res.end(body);
}
}
/**
* Default downstream middleware.
2013-08-18 18:14:01 +00:00
*
* @api private
2013-08-17 07:15:57 +00:00
*/
function *downstream() {
this.status = 200;
if (this.app.poweredBy) this.set('X-Powered-By', 'koa');
}
2013-08-18 18:14:01 +00:00
/**
* Create a new `Context` constructor.
*
* @return {Function}
* @api private
*/
function createContext() {
return function Context(app, req, res){
this.app = app;
this.req = req;
this.res = res;
this.cookies = new Cookies(req, res);
2013-08-18 18:14:01 +00:00
}
}