一步步去閱讀koa源碼,總體架構分析

閱讀好的框架的源碼有不少好處,從大神的視角去理解整個框架的設計思想。大到架構設計,小到可取的命名風格,還有設計模式、實現某類功能使用到的數據結構和算法等等。

使用koa

其實某個框架閱讀源碼的時候,首先咱們要會去用這個框架,由於用了咱們才知道,某個API是怎麼用,哪裏有坑,哪裏設計的精妙。node

下面咱們就簡單用一下koa這個框架,以下代碼git

const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
  ctx.body = 'Hello World'
})
app.listen(4002)

運行結果github

瓦特??這個服務會涉及到從請求到響應返回數據,就這幾行代碼?? 是的,你沒有看錯,就是單單這幾行代碼就能夠搭建了一個服務器。web

下面咱們看看一探究竟。面試

閱讀源碼

去到node_modules文件夾下找到koa模塊,先喵幾眼README.md文件,裏面介紹了koa的一些安裝、用法、插件等等,這裏咱們跳過,而後轉到package.json以下圖算法

看到package.json裏面的"main": "lib/application.js"沒錯,這就是咱們的入口,在lib文件夾下面,咱們看到裏面有application.jscontext.jsrequrest.jsresponse.js。下面通過我修改簡化去掉註釋application.js就只有68行代碼。閱讀起來能夠說是很是簡單了。以下圖:json

第一步是咱們引入各類主要依賴設計模式

// 引入有不少 我只挑我閱讀主要框架的代碼模塊

const response = require('./response'); // 處理response對象
const compose = require('koa-compose'); // 合併處理中間件函數
const context = require('./context'); // 整合處理context對象
const request = require('./request'); // 整合處理request對象
const http = require('http'); // node的 http原生模塊

以上就是咱們的主要依賴數組

Application的對象中,有constructor函數,這個主要是初始化Application對象,生成context對象、request對象、response對象服務器

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    super(); // 繼承Emitter
    this.middleware = []; // 初始化middleware爲空數組
    this.context = Object.create(context); // 生成context對象
    this.request = Object.create(request); // 生成request對象
    this.response = Object.create(response); // 生成response對象
  }
}

閱讀源碼,咱們先不要去扣細節,好比說Object.create(context)生產的對象是什麼?this.request對象下面又有什麼東西???,咱們如今主要知道的是、this.context是能獲取或者設置請求和響應的信息的一個對象,。this.request是請求的對象、裏面能夠設置或者獲取請求信息的一個對象、this.response是響應請求對象、裏面能夠設置或者獲取響應參數和值的一個對象。大概先了解就能夠了。繼續往下看。

在上面運用的時候,用到了app.use(fn)app.listen(4002) 咱們看看,源碼裏面試這樣子的

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    ...
  }
  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  use(fn) {
    this.middleware.push(fn);
    return this;
  }

上面的代碼很簡單 use函數就是把傳入的fn 推入到this.middleware的數組中,而後返回this,方便鏈式調用。

而後在listen裏面用node原生的http模塊建立一個server,在這裏順便說一下,原生 http建立一個服務是這樣子滴

const http = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});
http.listen(8000)

繼續看代碼 ,在建立服務的時候,參數裏面調用了一個this.callback()函數,下面咱們看看這個函數到底是怎麼樣子的。

module.exports = class Application extends Emitter {
  // 初始化 Application
  constructor() {
    ...
  }
  listen(...args) {
    ...
  }
  use(fn) {
    ...
  }

  callback() {
    const fn = compose(this.middleware); // 集中處理中間件數組
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res); // 整合req、res、context、request、response
      return this.handleRequest(ctx, fn); // 返回handleRequest
    };
    return handleRequest;
  }
    
  handleRequest(ctx, fnMiddleware) {
    const handleResponse = () => respond(ctx); // 最終響應函數
    return fnMiddleware(ctx).then(handleResponse) // 處理完中間件,而後傳到下一響應函數
  }
  // 建立整合新的 context.
  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;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
  }
};

上面咱們能夠看出在callback函數裏面有一個const fn = compose(this.middleware); 這個函數就是把this.middleware數組傳進去,而後集中處理中間件,而後會返回處理完中間件的fn。

繼續下一行

const handleRequest = (req, res) => {
  const ctx = this.createContext(req, res);
  return this.handleRequest(ctx, fn);
};

繼續進入到handleRequest函數裏面的const ctx = this.createContext(req, res);這個把原生的http的請求對象req響應對象res做爲參數傳進去,而後在createContext函數(看上面最大那坨代碼)在裏面,把this.requestthis.responsethis.context請求對象req響應對象res都整,作各類整合、處理獲得新的context對象返回出去。

也就是強大的ctx,獲得ctx以後,下一行返回return this.handleRequest(ctx, fn);

this.handleRequest(ctx, fn)代碼以下

handleRequest(ctx, fnMiddleware) {
    const handleResponse = () => respond(ctx);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

這個函數 就是處理完中間件處理以後的返回的函數把ctx傳下去,最後流通到respond(ctx);這個函數,

那麼咱們看看這個函數被我簡化後是怎麼樣子的,以下

// 一些容錯判斷或者提示我所有刪了
function respond(ctx) {
  const res = ctx.res;
  let body = ctx.body;
  res.end(body);
}

經過ctx拿到響應對象,和響應值、經過end方法會通知服務器,全部響應頭和響應主體都已被髮送,即服務器將其視爲已完成。看上面原生的http的服務方法。

最後附上一個流程圖

這個只是介紹application整個流程,還有不少細節都沒有一一介紹到,好比、建立contextrequestresponse對象是怎麼樣子的呀?中間件是如何集中層層深刻處理而後返回的呀?等等這些細節都會在下一篇會講到(最近公司業務很是忙,不知道到猴年馬月)。

寫的很差的地方,讓你們賤笑了。

而後最後安利一波博客,喜歡的小哥哥小姐姐能夠star 喲

websit: https://github.com/naihe138/naice-blog

相關文章
相關標籤/搜索