Koa源碼分析

一.下載koa源代碼

先看看這個極簡的啓動代碼:
node

const Koa = require('koa');
const app = new Koa();

// response
app.use(ctx => {
  ctx.body = 'Hello Koa';
});

app.listen(3000);複製代碼

咱們在koa源碼文件夾下建立index.js文件, 將上面的代碼寫入,並將require('koa') 換成 require('.')git

const Koa = require('.')
debugger
const app = new Koa();
app.use(ctx => {
  ctx.body = 'Hello Koa';
});
app.listen(3000);
複製代碼

而後進入目錄運行node --inspect-brk index.jsgithub

在chrome瀏覽器打開調試 chrome://inspectchrome

   1.引入Koajson

  • require('.')通過路徑分析


require 會依照這個順序去查找須要的文件
數組

Module._extensions={
    .js:funciton(module,filename),
    .json:funciton(module,filename),
    .node:funciton(module,filename)
}複製代碼


經過讀取package.json中的main字段獲得完整路徑promise

  • 路徑分析以後,是文件定位

查找到路徑以後經過 fs.readFileSync加載模塊瀏覽器


  • 編譯執行


讀取文件後開始編譯,首先將讀取的代碼script,進行組裝,bash

即頭部添加(function (exports, require, module, __filename, __dirname) { ', cookie

尾部添加

'\n});'

NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
  ];複製代碼

這就是爲何每一個模塊沒有定義exports, require, module, __filename, __dirname變量,卻能使用的緣由

2. 建立Koa對象

Application

constructor()
首先 new Koa()的時候就是new的這個對象, 最關鍵的是建立了context,request,response對象
constructor() {
    super();

    this.proxy = false;
    // 中間件初始化爲一個列表
    this.middleware = [];

    this.subdomainOffset = 2;
    // 默認爲開發環境
    this.env = process.env.NODE_ENV || 'development';
    // 建立Context, Request,Response對象,爲何要用Object.create函數呢?
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }複製代碼
use()
經過該方法引入中間件,中間件能夠是一個普通的函數, 能夠是一個generator, 能夠是async函數,若是是generator那麼koa會幫你轉成基於promise的函數。根據use使用的前後順序, middleware數組中會存儲全部的中間件。
use(fn) {
     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);
    }
    this.middleware.push(fn);
    return this;
  }複製代碼
listen()
listen函數表面上看是用於監聽某個端口,實際上包裹了真正的nodejs提供的http server, createServer函數接受一個處理請求的函數,由callback()返回
listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
複製代碼
callback()
callback函數是Application中真正核心的處理邏輯, 其返回了一個請求處理器,封裝了koa全部的方法邏輯
callback() {
    // compose函數將註冊的中間件組合成了一個函數,相似於遞歸
    const fn = compose(this.middleware);
    // Koa Application 擴展了 Emitter類, listeners 是Emitter類的屬性.
    // 這裏表示若沒有註冊error事件處理函數, 則註冊一個
    if (!this.listeners('error').length) this.on('error', this.onerror);
    
    // 返回的請求處理函數, req, res是createServer回調時會傳入的nodejs請求和響應對象。
    const handleRequest = (req, res) => {
      // 默認的的狀態碼爲404
      res.statusCode = 404;
      // 建立koa應用的上下文, context將不少屬性和方法都代理到這個對象上方便開發.
      const ctx = this.createContext(req, res);
      // 使用 ctx.onerror處理請求錯誤, 詳見Context
      const onerror = err => ctx.onerror(err);
      // 處理響應函數
      const handleResponse = () => respond(ctx);
      // 請求完成以後若是出錯調用onerror
      onFinished(res, onerror);
      // 等中間件都處理完了以後處理響應
      return fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
  }
複製代碼
createContext()
這個函數與其說是建立Context對象,不如說是將koa的各個內部對象鏈接在一塊兒。並設置cookies和accepts.
// req, res 是node的原生請求響應對象,是全部信息的來源.
// request,response是koa提供的方便咱們開發使用的請求響應對象.
  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;
    // originalUrl即 req.url
    context.originalUrl = request.originalUrl = req.url;
    // cookies 直接使用的第三方庫
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    // 更經常使用的從ip中讀取請求方的IP地址, ips是?
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    // 使用accepts設置請求能接受的內容類型
    context.accept = request.accept = accepts(req);
    // ?
    context.state = {};
    return context;
  }
複製代碼
respond()
當咱們完成處理須要返回時咱們設置 this.body = 'xxx' 而後函數返回,不須要咱們手動調用res.end(),由於koa已經幫咱們封裝好了.
/**
 * Response helper.
 */

function respond(ctx) {
  // ...
  // 若是HTTP狀態碼錶示內容應該爲空,則設空返回
  if (statuses.empty[code]) {
    
    ctx.body = null;
    return res.end();
  }
  
  if ('HEAD' === ctx.method) {
    // 要求返回響應頭,若是headersSent爲false
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }

  // 若是沒有設置body,只設置了status,則用狀態碼或message設置body.
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
    
  // koa 支持Buffer, string, Stream類型的數據
  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 處理普通json類型返回.
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}
複製代碼

Context

Context是整個請求的上下文,其最特殊的地方實際上是整合response和request,讓你在應用的任何地方經過context得到應用相關的任何信息

Response

響應體相關的方法屬性
相關文章
相關標籤/搜索