成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

koa源碼分析系列(一)

Atom / 407人閱讀

摘要:很明顯是一個構(gòu)造函數(shù)。默認(rèn)為根據(jù)原生的對象生成一個對象回調(diào)函數(shù)處理服務(wù)器響應(yīng)可以看到,方法返回的函數(shù)就是方法所需要的回調(diào)函數(shù)。

koa 是什么這里不介紹了,這里通過一個小例子結(jié)合源碼講一講它的實現(xiàn)。

koa 源碼結(jié)構(gòu)

通過 npm 安裝 koa(v2.2.0) 后,代碼都在 lib 文件夾內(nèi),包括 4 個文件,application.js, context.js, request.js, response.js。

application.js 包含 app 的構(gòu)造以及啟動一個服務(wù)器

context.js app 的 context 對象, 傳入中間件的上下文對象

request.js app 的請求對象,包含請求相關(guān)的一些屬性

response.js app 的響應(yīng)對象,包含響應(yīng)相關(guān)的一些屬性

本文主要關(guān)于 application.js 。

先看一個最簡單的例子

// app.js
const Koa = require("koa")
const app = new Koa()

app.use(ctx => {
    ctx.body = "hello world"
})

app.listen(3000)

然后通過 node app.js 啟動應(yīng)用,一個最簡單的 koa 服務(wù)器就搭建好了,瀏覽器訪問 http://localhost:3000,服務(wù)器返回一個 hello world 的響應(yīng)主體。

源碼分析

接下來通過源碼看看這個服務(wù)器是怎么啟動的。

const app = new Koa(), 很明顯 Koa 是一個構(gòu)造函數(shù)。

module.exports = class Application extends Emitter {}

Application 類繼承了 nodejs 的 Events 類,從而可以監(jiān)聽以及觸發(fā)事件。

看一下構(gòu)造函數(shù)的實現(xiàn)。

constructor() {
    super();

    this.proxy = false;
    this.middleware = [];
    this.subdomainOffset = 2;
    this.env = process.env.NODE_ENV || "development";
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);

構(gòu)造函數(shù)定義了一些 app 的實例屬性,包括 proxy, middleware, subdomainOffset, env, context, request, response等。

至此 我們就生成了一個 app 的koa實例。

接下來就該用 app.use(middleware) 來使用中間件了。

use(fn) {
    if (typeof fn !== "function") throw new TypeError("middleware must be a 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 || "-");
    this.middleware.push(fn);
    return this;
  }

首先會驗證傳入的參數(shù)是否是一個函數(shù)。如果不是一個函數(shù),會報錯。之后如果傳入的函數(shù)是一個generator 函數(shù),那么會將這個函數(shù)轉(zhuǎn)化為一個 async 函數(shù)。使用的是 koa-convert 模塊, 這是一個很重要的模塊,能將很多 koa1 時代下的中間件轉(zhuǎn)化為 koa2 下可用的中間件。并且注意到

Support for generators will be removed in v3.

在 koa3 中,將默認(rèn)不支持 generator 函數(shù)作為中間件。

之后將傳入的中間件函數(shù)推入 middleware 數(shù)組中,并且返回 this 以便鏈?zhǔn)秸{(diào)用。

app.use() 只是定義了一些要使用的中間件,并將它們放入 middleware 數(shù)組中,那么怎么使用這些中間件。來看看 app.listen 方法。

listen() { 
    debug("listen");
    const server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
  }

app.listen 算是 node 原生 listen 方法的語法糖。通過 app.callback 方法生成一個 http.createServer 方法所需要的回調(diào)函數(shù),然后再調(diào)用原生 http server 的 listen 方法。事實上也可以發(fā)現(xiàn),app 的 listen 方法接收 http server 的 listen 方法一樣的參數(shù)。

那么再看看 app 的 callback 這個方法了,也是最重要的一個方法。

callback() {
    const fn = compose(this.middleware);

    if (!this.listeners("error").length) this.on("error", this.onerror);

    const handleRequest = (req, res) => {
      res.statusCode = 404; // 默認(rèn)為 404 
      const ctx = this.createContext(req, res);
      // 根據(jù) node.js 原生的 req, res 對象生成一個 ctx 對象
      const onerror = err => ctx.onerror(err);
      // onerror 回調(diào)函數(shù)
      const handleResponse = () => respond(ctx);
      // 處理服務(wù)器響應(yīng)
      onFinished(res, onerror);
      return fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
  }

可以看到,callback 方法返回的 handleRequest 函數(shù)就是 http.createServer 方法所需要的回調(diào)函數(shù)。

callback 函數(shù)內(nèi),首先通過 koa-compose 模塊將所有的中間件合并成一個中間件函數(shù),以供 app.use 方法調(diào)用。隨后監(jiān)聽一個 error 事件,onerror 作為默認(rèn)的錯誤處理函數(shù)。

onerror(err) {
    assert(err instanceof Error, `non-error thrown: ${err}`);

    if (404 == err.status || err.expose) return;
    if (this.silent) return;
    const msg = err.stack || err.toString();
    console.error();
    console.error(msg.replace(/^/gm, "  "));
    console.error();
  }

onerror 函數(shù)只是僅僅輸出 error.stack 作為錯誤信息。

handleRequest 函數(shù)內(nèi)完成了對請求的處理以及對響應(yīng)結(jié)果的返回。首先 app.createContext 方法生成一個 ctx 供中間件函數(shù) fn 調(diào)用。

createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    // request 屬性
    const response = context.response = Object.create(this.response);
    // response 屬性
    context.app = request.app = response.app = this;
    // request 和 response 上獲得 app 屬性,指向這個 app 實例
    context.req = request.req = response.req = req;
    // req 屬性,req 是原生 node 的請求對象
    context.res = request.res = response.res = res;
    // res 屬性,res 是原生 node 的響應(yīng)對象
    request.ctx = response.ctx = context;
    // request 和 response 上獲得 ctx 屬性,指向 context 對象
    request.response = response;
    response.request = request;
    // request 和 response 互相指向?qū)Ψ?    context.originalUrl = request.originalUrl = req.url;
    // 獲得 originalUrl 屬性,為原生 req 對象的 url 屬性
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    }); // cookie 屬性
    request.ip = request.ips[0] || req.socket.remoteAddress || "";
    // ip 屬性
    context.accept = request.accept = accepts(req);
    // accept 屬性,是個方法,用于判斷 Content-Type 
    context.state = {};
    // context.state 屬性,用于保存一次請求中所需要的其他信息
    return context;
  }

所以,createContext 方法將一些常用的屬性,如 resquest , response, node 原生 req, res 掛載到 context 對象上。

再來看這句話 const handleResponse = () => respond(ctx).

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  const res = ctx.res;
  if (!ctx.writable) return;

  let body = ctx.body; // 響應(yīng)主體
  const code = ctx.status; // 響應(yīng)狀態(tài)碼

  // ignore body
  if (statuses.empty[code]) { // 這里具體是指 204 205 304 三種
    // strip headers
    ctx.body = null;
    return res.end();
  }

  if ("HEAD" == ctx.method) { // 如果是 `HEAD` 方法
    if (!res.headersSent && isJSON(body)) {
      ctx.length = Buffer.byteLength(JSON.stringify(body));
    }
    return res.end();
  }
  // status body
  if (null == body) { // 如果沒有設(shè)置 body , 設(shè)置ctx.message 為 body。當(dāng)然默認(rèn)是 Not Found ,因為 status 默認(rèn)是 404
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = "text";
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
  // 以下根據(jù) body 的類型,決定響應(yīng)結(jié)果
  // 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);
}

所以 respond 函數(shù)的作用就是,根據(jù)傳入的 ctx 對象的 body ,method 屬性來決定對 request 處理的方式以及如何 response。

onFinished(res, onerror)

首先看看 onFinished(res, listener) 函數(shù)的介紹

Attach a listener to listen for the response to finish. The listener will be invoked only once when the response finished. If the response finished to an error, the first argument will contain the error. If the response has already finished, the listener will be invoked.

也就是當(dāng)服務(wù)端響應(yīng)完成后,執(zhí)行 listener 回調(diào)函數(shù),如果響應(yīng)過程中有錯誤發(fā)生,那么 error 對象將作為 listen 回調(diào)函數(shù)的第一個參數(shù),因此 onFinished(res, onerror) 表示 當(dāng) koa 服務(wù)器發(fā)送完響應(yīng)后,如果有錯誤發(fā)生,執(zhí)行 onerror 這個回調(diào)函數(shù)。

return fn(ctx).then(handleResponse).catch(onerror)。來看看這一句,fn 之前說過了,是所有的中間件函數(shù)的 “集合”, 用這一個中間件來表示整個處理過程。同時 fn 也是一個 async 函數(shù),執(zhí)行結(jié)果返回一個 promise 對象,同時 handleResponse 作為其 resolved 函數(shù),onerror 是 rejected 函數(shù)。

總結(jié)

總結(jié)一下,application.js 描述了 一個 koa 服務(wù)器(實例)生成的整個過程。

new Koa() 生成了一個 koa 實例

app.use(middleware) 定義了這個 app 要使用的中間件

app.listen 方法,通過 callback 將合并后的中間件函數(shù)轉(zhuǎn)化成一個用于 http server.listen 調(diào)用的回調(diào)函數(shù),之后調(diào)用原生的 server.listen 方法。

全文完

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82772.html

相關(guān)文章

  • B站Up主-山地人-這位老哥2019年的前端自學(xué)計劃進(jìn)展如何?——講個B站Up主自學(xué)前端85天的故

    摘要:前言自從上次在掘金發(fā)布年山地人的前端完整自學(xué)計劃講一個站主山地人的天前端自學(xué)故事以來,一眨眼山地人老哥在站做主已經(jīng)有天了。所以這個體系里的一些框架包括也是山地人年自學(xué)計劃的一部分。月底,山地人老哥開啟了的兩個專題。 前言 自從上次在掘金發(fā)布【2019年山地人的前端完整自學(xué)計劃——講一個B站UP主山地人的40天前端自學(xué)故事】 以來,一眨眼山地人老哥在B站做Up主已經(jīng)有85天了。 時隔一個...

    cocopeak 評論0 收藏0
  • 中間件執(zhí)行模塊koa-Compose源碼分析

    摘要:原文博客地址,歡迎學(xué)習(xí)交流點(diǎn)擊預(yù)覽讀了下的源碼,寫的相當(dāng)?shù)木?,遇到處理中間件執(zhí)行的模塊決定學(xué)習(xí)一下這個模塊的源碼。當(dāng)在下游沒有更多的中間件執(zhí)行后,堆棧將展開并且每個中間件恢復(fù)執(zhí)行其上游行為。 原文博客地址,歡迎學(xué)習(xí)交流:點(diǎn)擊預(yù)覽 讀了下Koa的源碼,寫的相當(dāng)?shù)木?,遇到處理中間件執(zhí)行的模塊koa-Compose,決定學(xué)習(xí)一下這個模塊的源碼。 閱讀本文可以學(xué)到: Koa中間件的加載...

    imtianx 評論0 收藏0
  • express分析和對比

    摘要:前言目前最新版本是所以本文分析也基于這個版本。源碼分析直接切入主題由于目前是一個獨(dú)立的路由和中間件框架。所以分析的方向也以這兩個為主。源碼去年的時候有分析過現(xiàn)在對比分析思考下。 前言 目前express最新版本是4.16.2,所以本文分析也基于這個版本。目前從npm倉庫上來看express使用量挺高的,express月下載量約為koa的40倍。所以目前研究下express還是有一定意義...

    mmy123456 評論0 收藏0
  • Express與Koa中間件機(jī)制分析

    摘要:目前使用人數(shù)眾多。通過利用函數(shù),幫你丟棄回調(diào)函數(shù),并有力地增強(qiáng)錯誤處理。這個系列的博客主要講解和的中間件機(jī)制,本篇將主要講解的中間件機(jī)制。其中間件機(jī)制的核心為內(nèi)部方法的實現(xiàn)。 提到 Node.js 開發(fā),不得不提目前炙手可熱的2大框架 Express 和 Koa。 Express 是一個保持最小規(guī)模的靈活的 Node.js Web 應(yīng)用程序開發(fā)框架,為 Web 和移動應(yīng)用程序提供一組...

    zilu 評論0 收藏0
  • 深入探析koa之中間件流程控制篇

    摘要:到此為止,我們就基本講清楚了中的中間件洋蔥模型是如何自動執(zhí)行的。 koa被認(rèn)為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無疑就是解決了回調(diào)金字塔的問題,讓異步的寫法更加的簡潔。在使用koa的過程中,其實一直比較好奇koa內(nèi)部的實現(xiàn)機(jī)理。最近終于有空,比較深入的研究了一下koa一些原理,在這里會寫一系列文章來記錄一下我的學(xué)習(xí)心得和理解。 在我看來,koa最核心...

    fuchenxuan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<