agate icon indicating copy to clipboard operation
agate copied to clipboard

koa源码学习

Open RubyLouvre opened this issue 9 years ago • 6 comments

//server.js

/**
 * Module dependencies.
 */

var debug = require('debug')('koa:application');
var Emitter = require('events').EventEmitter;
var compose_es7 = require('composition');
var onFinished = require('on-finished');
var response = require('./response');
var compose = require('koa-compose');
var isJSON = require('koa-is-json');
var context = require('./context');
var request = require('./request');
var statuses = require('statuses');
var Cookies = require('cookies');
var accepts = require('accepts');
var assert = require('assert');
var Stream = require('stream');
var http = require('http');
var only = require('only');
var co = require('co');

/**
 * 这是koa的核心, (mdule.exports = Application)===koa;  
 * var app = koa() koa.listen(80)
 */

var app = Application.prototype;

/**
 * koa团队喜欢先使用后定义
 */

exports = module.exports = Application;

function Application() {
  if (!(this instanceof Application)) return new Application;
  this.env = process.env.NODE_ENV || 'development';
  this.subdomainOffset = 2;
  this.middleware = [];
  //Object.create只传入一个对象参数,相当于浅拷贝此对象,并且也作为原对象的实例而存在
  this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
}

/**
 * Object.setPrototypeOf 相当于
 var shape = {}, circle = new Circle();
// Set the object prototype
shape.__proto__ = circle;
 */

Object.setPrototypeOf(Application.prototype, Emitter.prototype);

/**
 * Shorthand for:
 *
 *    http.createServer(app.callback()).listen(...)
 *
 * @param {Mixed} ...
 * @return {Server}
 * @api public
 */

app.listen = function(){
  debug('listen');
  var server = http.createServer(this.callback());
  return server.listen.apply(server, arguments);
};

/**
 * Return JSON representation.
 * We only bother showing settings.
 *
 * @return {Object}
 * @api public
 */

/**
 * only方法,会根据第二个参数给出的键名,从第一个对象上抽出一个子对象出来
 * var obj = {
  name: 'tobi',
  last: 'holowaychuk',
  email: '[email protected]',
  _id: '12345'
};

var user = only(obj, 'name last email');
yields:
{
  name: 'tobi',
  last: 'holowaychuk',
  email: '[email protected]'
}
 * 
 * 
 */

app.inspect =
app.toJSON = function(){
  return only(this, [
    'subdomainOffset',
    'env'
  ]);
};

/**
 * Use the given middleware `fn`.
 *
 * @param {GeneratorFunction} fn
 * @return {Application} self
 * @api public
 */

app.use = function(fn){
  if (!this.experimental) {
    //如果第一个参数不是生成器函数,那么就抛错
    assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
};

/**
 * Return a request handler callback
 * for node's native http server.
 *
 * @return {Function}
 * @api public
 */

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var fn = this.experimental
    ? compose_es7(mw)
    : co.wrap(compose(mw));
  var self = this;
//从EventEmitter 继承了setMaxListeners, emit, addListener , on , once, removeListener, listeners
  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
      //在这里传入原汁原味的req, res
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
  }
};

/**
 * 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;
  context.cookies = new Cookies(req, res, this.keys);
  //返回一个Accepts对象,通常用到其type方法
  //https://github.com/jshttp/accepts/blob/master/index.js#L57
  context.accept = request.accept = accepts(req);
  context.state = {};
  return context;
};

/**
 * 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();
  console.error();
  console.error(msg.replace(/^/gm, '  '));//在每行的前面加两个空格
  console.error();
};

/**
 * Response middleware.
 */

function *respond(next) {
  yield *next;

  // allow bypassing koa
  if (false === this.respond) return;

  var res = this.res;
  //https://nodejs.org/api/http.html#http_response_headerssent
  if (res.headersSent || !this.writable) return;

  var body = this.body;
  var code = this.status;
/*
 * https://github.com/jshttp/statuses/blob/master/index.js
// status codes for empty bodies
status.empty = {
  204: true,
  205: true,
  304: true,
};
 */
  // ignore body
  if (statuses.empty[code]) {
    // strip headers
    this.body = null;
    return res.end();
  }

  if ('HEAD' == this.method) {
      /*https://github.com/koajs/is-json/blob/master/index.js
function isJSON(body) {
  if (!body) return false;
  if ('string' == typeof body) return false;
  if ('function' == typeof body.pipe) return false;
  if (Buffer.isBuffer(body)) return false;
  return true;
}
       */
    if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));
    return res.end();
  }

  // status body
  if (null == body) {
    this.type = 'text';
    body = this.message || String(code);
    if (body) this.length = Buffer.byteLength(body);
    return res.end(body);
  }

  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json
  body = JSON.stringify(body);
  this.length = Buffer.byteLength(body);
  res.end(body);
}
//https://github.com/koajs/bigpipe-example/blob/master/view/index.js

RubyLouvre avatar Apr 27 '15 07:04 RubyLouvre