koa2 源碼解讀 application

koa2的源碼比較簡單,重點解讀aplication,node

其中context源碼比較簡單,主要是一些error cookies等,重點能夠關注下delegate,delegate模塊中,主要經過prototype的方式進行屬性的增長。git

request和response兩個模塊都是get set的一些基礎api及封裝的node原始方法github

applicationjson

'use strict';   //嚴格模式

/**
 * 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 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');
const { HttpError } = require('http-errors');

/**
 * constructor()  構造函數
 * listen()  調用原生http模塊建立服務並監聽
 * use()  中間件處理
 * callback() http請求的回調函數
 * handleRequest() 請求真正的回調函數
 * createContext() 建立上下文對象
 * respond()  全部中間件處理完後自動響應
 * onerror() 處理錯誤信息
 * 
 */

/**
 * Expose `Application` class.
 * Inherits from `Emitter.prototype`.
 */

module.exports = class Application extends Emitter {
  /**
   * Initialize a new `Application`.
   *
   * @api public
   */

  /**
    *
    * @param {object} [options] Application options
    * @param {string} [options.env='development'] Environment
    * @param {string[]} [options.keys] Signed cookie keys
    * @param {boolean} [options.proxy] Trust proxy headers
    * @param {number} [options.subdomainOffset] Subdomain offset
    *
    */

  constructor(options) {
    super();
    options = options || {};
    this.proxy = options.proxy || false;       //是否容許跨域
    this.subdomainOffset = options.subdomainOffset || 2;   // 子域名容許請求幾級鏈接
    this.env = options.env || process.env.NODE_ENV || 'development'; //node的執行環境
    if (options.keys) this.keys = options.keys; 
    this.middleware = [];                     //全部的中間件的存入
    this.context = Object.create(context);    //每次實例化都從新賦值,爲保證屢次實例化時保持不衝突,和單例模式成反例
    this.request = Object.create(request);
    this.response = Object.create(response);
    if (util.inspect.custom) {
      this[util.inspect.custom] = this.inspect;    //保存88行代碼中的內容
    }
  }

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

  listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());  //原生http模塊建立服務並監聽  
    return server.listen(...args);
  }

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

  toJSON() {
    return only(this, [       //only 對傳入的數據使用reduce進行重組
      'subdomainOffset',
      'proxy',
      'env'
    ]);
  }

  /**
   * Inspect implementation.
   *
   * @return {Object}
   * @api public
   */

  inspect() {
    return this.toJSON();  //數據重組
  }

  /**
   * Use the given middleware `fn`.
   *
   * Old-style middleware will be converted.
   *
   * @param {Function} fn
   * @return {Application} self
   * @api public
   */

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');//必須是一個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 || '-');   //DEBUG=koa* node --harmony app.js 調試時輸出中間件調用及時長
    this.middleware.push(fn);  //將中間件加入到middleware數組中
    return this;
  }

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

  callback() {
    const fn = compose(this.middleware);   //將這些中間件組合後拿到執行鏈函數fn

    if (!this.listenerCount('error')) this.on('error', this.onerror); //若是沒有監聽則報錯

    const handleRequest = (req, res) => { //事件處理函數
      const ctx = this.createContext(req, res);  //建立一個ctx
      return this.handleRequest(ctx, fn);        //交給157行的handleRequest
    };

    return handleRequest;
  }

  /**
   * 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);   //211行詳解
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }

  /**
   * Initialize a new context.
   *
   * @api private
   */

  createContext(req, res) {
    const context = Object.create(this.context);//經過context對象的原型建立
    const request = context.request = Object.create(this.request);//經過request對象的原型建立,this.request指的是原生的request,修改this.request中的屬性就是修改原生的對應的屬性數據
    const response = context.response = Object.create(this.response);//經過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;
    context.state = {};
    return context;
  }

  /**
   * Default error handler.
   *
   * @param {Error} err
   * @api private
   */

  onerror(err) {
    if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));   //檢測err不是Error實例時,建立一個Error的實例

    if (404 == err.status || err.expose) return; 
    if (this.silent) return;

    const msg = err.stack || err.toString(); //將err堆棧的信息拿出來
    console.error();                              //控制檯打印Error信息
    console.error(msg.replace(/^/gm, '  '));
    console.error();
  }
};

/**
 * Response helper.
 */

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;   //容許繞過KOA 爲寫入原始的res對象而不是讓koa處理你的rsponse

  if (!ctx.writable) return; 

  const res = ctx.res;       
  let body = ctx.body;       //外面Middleware傳入的body數據
  const code = ctx.status;   //當前狀態碼

  // ignore body
  if (statuses.empty[code]) {     //當前狀態碼是空的,則清掉body並結束
    // strip headers
    ctx.body = null;
    return res.end();
  }

  if ('HEAD' == ctx.method) {        //head部分
    if (!res.headersSent && isJSON(body)) {      //判斷當前header沒有被髮送而且是 重組後的json數據
      ctx.length = Buffer.byteLength(JSON.stringify(body));   //則從新序列化 取長度
    }
    return res.end();
  }

  // status body
  if (null == body) {  // body部分  不爲null
    if (ctx.req.httpVersionMajor >= 2) {  //根據http major的版本  分別對body進行初始化
      body = String(code);
    } else {
      body = ctx.message || String(code);
    }
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
                                                        //如下爲支持各類格式的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);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

/**
 * Make HttpError available to consumers of the library so that consumers don't
 * have a direct dependency upon `http-errors`
 */
module.exports.HttpError = HttpError;
相關文章
相關標籤/搜索