欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何從零開始手寫Koa2框架

 更新時(shí)間:2019年03月22日 10:36:02   作者:xpromise  
這篇文章主要介紹了如何從零開始手寫Koa2框架,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

01、介紹

  • Koa-- 基于 Node.js 平臺(tái)的下一代 web 開發(fā)框架
  • Koa 是一個(gè)新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應(yīng)用和 API 開發(fā)領(lǐng)域中的一個(gè)更小、更富有表現(xiàn)力、更健壯的基石。
  • 與其對(duì)應(yīng)的 Express 來(lái)比,Koa 更加小巧、精壯,本文將帶大家從零開始實(shí)現(xiàn) Koa 的源碼,從根源上解決大家對(duì) Koa 的困惑
本文 Koa 版本為 2.7.0, 版本不一樣源碼可能會(huì)有變動(dòng)

02、源碼目錄介紹

Koa 源碼目錄截圖

通過(guò)源碼目錄可以知道,Koa主要分為4個(gè)部分,分別是:

  • application: Koa 最主要的模塊, 對(duì)應(yīng) app 應(yīng)用對(duì)象
  • context: 對(duì)應(yīng) ctx 對(duì)象
  • request: 對(duì)應(yīng) Koa 中請(qǐng)求對(duì)象
  • response: 對(duì)應(yīng) Koa 中響應(yīng)對(duì)象

這4個(gè)文件就是 Koa 的全部?jī)?nèi)容了,其中 application 又是其中最核心的文件。我們將會(huì)從此文件入手,一步步實(shí)現(xiàn) Koa 框架

03、實(shí)現(xiàn)一個(gè)基本服務(wù)器代碼目錄

my-application

const {createServer} = require('http');

module.exports = class Application {
 constructor() {
 // 初始化中間件數(shù)組, 所有中間件函數(shù)都會(huì)添加到當(dāng)前數(shù)組中
 this.middleware = [];
 }
 // 使用中間件方法
 use(fn) {
 // 將所有中間件函數(shù)添加到中間件數(shù)組中
 this.middleware.push(fn);
 }
 // 監(jiān)聽端口號(hào)方法
 listen(...args) {
 // 使用nodejs的http模塊監(jiān)聽端口號(hào)
 const server = createServer((req, res) => {
  /*
  處理請(qǐng)求的回調(diào)函數(shù),在這里執(zhí)行了所有中間件函數(shù)
  req 是 node 原生的 request 對(duì)象
  res 是 node 原生的 response 對(duì)象
  */
  this.middleware.forEach((fn) => fn(req, res));
 })
 server.listen(...args);
 }
}

index.js

// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 創(chuàng)建實(shí)例對(duì)象
const app = new MyKoa();
// 使用中間件
app.use((req, res) => {
 console.log('中間件函數(shù)執(zhí)行了~~~111');
})
app.use((req, res) => {
 console.log('中間件函數(shù)執(zhí)行了~~~222');
 res.end('hello myKoa');
})
// 監(jiān)聽端口號(hào)
app.listen(3000, err => {
 if (!err) console.log('服務(wù)器啟動(dòng)成功了');
 else console.log(err);
})

運(yùn)行入口文件 index.js 后,通過(guò)瀏覽器輸入網(wǎng)址訪問(wèn) http://localhost:3000/ , 就可以看到結(jié)果了~~

神奇吧!一個(gè)最簡(jiǎn)單的服務(wù)器模型就搭建完了。當(dāng)然我們這個(gè)極簡(jiǎn)服務(wù)器還存在很多問(wèn)題,接下來(lái)讓我們一一解決

04、實(shí)現(xiàn)中間件函數(shù)的 next 方法

提取createServer的回調(diào)函數(shù),封裝成一個(gè)callback方法(可復(fù)用)

// 監(jiān)聽端口號(hào)方法
listen(...args) {
 // 使用nodejs的http模塊監(jiān)聽端口號(hào)
 const server = createServer(this.callback());
 server.listen(...args);
}
callback() {
 const handleRequest = (req, res) => {
 this.middleware.forEach((fn) => fn(req, res));
 }
 return handleRequest;
}

封裝compose函數(shù)實(shí)現(xiàn)next方法

// 負(fù)責(zé)執(zhí)行中間件函數(shù)的函數(shù)
function compose(middleware) {
 // compose方法返回值是一個(gè)函數(shù),這個(gè)函數(shù)返回值是一個(gè)promise對(duì)象
 // 當(dāng)前函數(shù)就是調(diào)度
 return (req, res) => {
 // 默認(rèn)調(diào)用一次,為了執(zhí)行第一個(gè)中間件函數(shù)
 return dispatch(0);
 function dispatch(i) {
  // 提取中間件數(shù)組的函數(shù)fn
  let fn = middleware[i];
  // 如果最后一個(gè)中間件也調(diào)用了next方法,直接返回一個(gè)成功狀態(tài)的promise對(duì)象
  if (!fn) return Promise.resolve();
  /*
  dispatch.bind(null, i + 1)) 作為中間件函數(shù)調(diào)用的第三個(gè)參數(shù),其實(shí)就是對(duì)應(yīng)的next
   舉個(gè)栗子:如果 i = 0 那么 dispatch.bind(null, 1)) 
   --> 也就是如果調(diào)用了next方法 實(shí)際上就是執(zhí)行 dispatch(1) 
    --> 它利用遞歸重新進(jìn)來(lái)取出下一個(gè)中間件函數(shù)接著執(zhí)行
  fn(req, res, dispatch.bind(null, i + 1))
   --> 這也是為什么中間件函數(shù)能有三個(gè)參數(shù),在調(diào)用時(shí)我們傳進(jìn)來(lái)了
  */
  return Promise.resolve(fn(req, res, dispatch.bind(null, i + 1)));
 }
 }
}

使用compose函數(shù)

callback () {
 // 執(zhí)行compose方法返回一個(gè)函數(shù)
 const fn = compose(this.middleware);
 
 const handleRequest = (req, res) => {
 // 調(diào)用該函數(shù),返回值為promise對(duì)象
 // then方法觸發(fā)了, 說(shuō)明所有中間件函數(shù)都被調(diào)用完成
 fn(req, res).then(() => {
  // 在這里就是所有處理的函數(shù)的最后階段,可以允許返回響應(yīng)了~
 });
 }
 
 return handleRequest;
}

修改入口文件 index.js 代碼

// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 創(chuàng)建實(shí)例對(duì)象
const app = new MyKoa();
// 使用中間件
app.use((req, res, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~111');
 // 調(diào)用next方法,就是調(diào)用堆棧中下一個(gè)中間件函數(shù)
 next();
})
app.use((req, res, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~222');
 res.end('hello myKoa');
 // 最后的next方法沒發(fā)調(diào)用下一個(gè)中間件函數(shù),直接返回Promise.resolve()
 next();
})
// 監(jiān)聽端口號(hào)
app.listen(3000, err => {
 if (!err) console.log('服務(wù)器啟動(dòng)成功了');
 else console.log(err);
})

此時(shí)我們實(shí)現(xiàn)了next方法,最核心的就是compose函數(shù),極簡(jiǎn)的代碼實(shí)現(xiàn)了功能,不可思議!

05、處理返回響應(yīng)

定義返回響應(yīng)函數(shù)respond

function respond(req, res) {
 // 獲取設(shè)置的body數(shù)據(jù)
 let body = res.body;
 
 if (typeof body === 'object') {
 // 如果是對(duì)象,轉(zhuǎn)化成json數(shù)據(jù)返回
 body = JSON.stringify(body);
 res.end(body);
 } else {
 // 默認(rèn)其他數(shù)據(jù)直接返回
 res.end(body);
 }
}

callback中調(diào)用

callback() {
 const fn = compose(this.middleware);
 
 const handleRequest = (req, res) => {
 // 當(dāng)中間件函數(shù)全部執(zhí)行完畢時(shí),會(huì)觸發(fā)then方法,從而執(zhí)行respond方法返回響應(yīng)
 const handleResponse = () => respond(req, res);
 fn(req, res).then(handleResponse);
 }
 
 return handleRequest;
}

修改入口文件 index.js 代碼

// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 創(chuàng)建實(shí)例對(duì)象
const app = new MyKoa();
// 使用中間件
app.use((req, res, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~111');
 next();
})
app.use((req, res, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~222');
 // 設(shè)置響應(yīng)內(nèi)容,由框架負(fù)責(zé)返回響應(yīng)~
 res.body = 'hello myKoa';
})
// 監(jiān)聽端口號(hào)
app.listen(3000, err => {
 if (!err) console.log('服務(wù)器啟動(dòng)成功了');
 else console.log(err);
})

此時(shí)我們就能根據(jù)不同響應(yīng)內(nèi)容做出處理了~當(dāng)然還是比較簡(jiǎn)單的,可以接著去擴(kuò)展~

06、定義 Request 模塊

// 此模塊需要npm下載
const parse = require('parseurl');
const qs = require('querystring');

module.exports = {
 /**
 * 獲取請(qǐng)求頭信息
 */
 get headers() {
 return this.req.headers;
 },
 /**
 * 設(shè)置請(qǐng)求頭信息
 */
 set headers(val) {
 this.req.headers = val;
 },
 /**
 * 獲取查詢字符串
 */
 get query() {
 // 解析查詢字符串參數(shù) --> key1=value1&key2=value2
 const querystring = parse(this.req).query;
 // 將其解析為對(duì)象返回 --> {key1: value1, key2: value2}
 return qs.parse(querystring);
 }
}

07、定義 Response 模塊

module.exports = {
 /**
 * 設(shè)置響應(yīng)頭的信息
 */
 set(key, value) {
 this.res.setHeader(key, value);
 },
 /**
 * 獲取響應(yīng)狀態(tài)碼
 */
 get status() {
 return this.res.statusCode;
 },
 /**
 * 設(shè)置響應(yīng)狀態(tài)碼
 */
 set status(code) {
 this.res.statusCode = code;
 },
 /**
 * 獲取響應(yīng)體信息
 */
 get body() {
 return this._body;
 },
 /**
 * 設(shè)置響應(yīng)體信息
 */
 set body(val) {
 // 設(shè)置響應(yīng)體內(nèi)容
 this._body = val;
 // 設(shè)置響應(yīng)狀態(tài)碼
 this.status = 200;
 // json
 if (typeof val === 'object') {
  this.set('Content-Type', 'application/json');
 }
 },
}

08、定義 Context 模塊

// 此模塊需要npm下載
const delegate = require('delegates');

const proto = module.exports = {};

// 將response對(duì)象上的屬性/方法克隆到proto上
delegate(proto, 'response')
 .method('set') // 克隆普通方法
 .access('status') // 克隆帶有g(shù)et和set描述符的方法
 .access('body') 

// 將request對(duì)象上的屬性/方法克隆到proto上
delegate(proto, 'request')
 .access('query')
 .getter('headers') // 克隆帶有g(shù)et描述符的方法

09、揭秘 delegates 模塊

module.exports = Delegator;

/**
 * 初始化一個(gè) delegator.
 */
function Delegator(proto, target) {
 // this必須指向Delegator的實(shí)例對(duì)象
 if (!(this instanceof Delegator)) return new Delegator(proto, target);
 // 需要克隆的對(duì)象
 this.proto = proto;
 // 被克隆的目標(biāo)對(duì)象
 this.target = target;
 // 所有普通方法的數(shù)組
 this.methods = [];
 // 所有帶有g(shù)et描述符的方法數(shù)組
 this.getters = [];
 // 所有帶有set描述符的方法數(shù)組
 this.setters = [];
}

/**
 * 克隆普通方法
 */
Delegator.prototype.method = function(name){
 // 需要克隆的對(duì)象
 var proto = this.proto;
 // 被克隆的目標(biāo)對(duì)象
 var target = this.target;
 // 方法添加到method數(shù)組中
 this.methods.push(name);
 // 給proto添加克隆的屬性
 proto[name] = function(){
 /*
  this指向proto, 也就是ctx
  舉個(gè)栗子:ctx.response.set.apply(ctx.response, arguments)
  arguments對(duì)應(yīng)實(shí)參列表,剛好與apply方法傳參一致
  執(zhí)行ctx.set('key', 'value') 實(shí)際上相當(dāng)于執(zhí)行 response.set('key', 'value')
 */
 return this[target][name].apply(this[target], arguments);
 };
 // 方便鏈?zhǔn)秸{(diào)用
 return this;
};

/**
 * 克隆帶有g(shù)et和set描述符的方法.
 */
Delegator.prototype.access = function(name){
 return this.getter(name).setter(name);
};

/**
 * 克隆帶有g(shù)et描述符的方法.
 */
Delegator.prototype.getter = function(name){
 var proto = this.proto;
 var target = this.target;
 this.getters.push(name);
 // 方法可以為一個(gè)已經(jīng)存在的對(duì)象設(shè)置get描述符屬性
 proto.__defineGetter__(name, function(){
 return this[target][name];
 });

 return this;
};

/**
 * 克隆帶有set描述符的方法.
 */
Delegator.prototype.setter = function(name){
 var proto = this.proto;
 var target = this.target;
 this.setters.push(name);
 // 方法可以為一個(gè)已經(jīng)存在的對(duì)象設(shè)置set描述符屬性
 proto.__defineSetter__(name, function(val){
 return this[target][name] = val;
 });

 return this;
};

10、使用 ctx 取代 req 和 res

修改 my-application

const {createServer} = require('http');
const context = require('./my-context');
const request = require('./my-request');
const response = require('./my-response');

module.exports = class Application {
 constructor() {
 this.middleware = [];
 // Object.create(target) 以target對(duì)象為原型, 創(chuàng)建新對(duì)象, 新對(duì)象原型有target對(duì)象的屬性和方法
 this.context = Object.create(context);
 this.request = Object.create(request);
 this.response = Object.create(response);
 }
 
 use(fn) {
 this.middleware.push(fn);
 }
 
 listen(...args) {
 // 使用nodejs的http模塊監(jiān)聽端口號(hào)
 const server = createServer(this.callback());
 server.listen(...args);
 }
 
 callback() {
 const fn = compose(this.middleware);
 
 const handleRequest = (req, res) => {
  // 創(chuàng)建context
  const ctx = this.createContext(req, res);
  const handleResponse = () => respond(ctx);
  fn(ctx).then(handleResponse);
 }
 
 return handleRequest;
 }
 
 // 創(chuàng)建context 上下文對(duì)象的方法
 createContext(req, res) {
 /*
  凡是req/res,就是node原生對(duì)象
  凡是request/response,就是自定義對(duì)象
  這是實(shí)現(xiàn)互相掛載引用,從而在任意對(duì)象上都能獲取其他對(duì)象的方法
  */
 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;
 
 return context;
 }
}
// 將原來(lái)使用req,res的地方改用ctx
function compose(middleware) {
 return (ctx) => {
 return dispatch(0);
 function dispatch(i) {
  let fn = middleware[i];
  if (!fn) return Promise.resolve();
  return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
 }
 }
}

function respond(ctx) {
 let body = ctx.body;
 const res = ctx.res;
 if (typeof body === 'object') {
 body = JSON.stringify(body);
 res.end(body);
 } else {
 res.end(body);
 }
}

修改入口文件 index.js 代碼

// 引入自定義模塊
const MyKoa = require('./js/my-application');
// 創(chuàng)建實(shí)例對(duì)象
const app = new MyKoa();
// 使用中間件
app.use((ctx, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~111');
 next();
})
app.use((ctx, next) => {
 console.log('中間件函數(shù)執(zhí)行了~~~222');
 // 獲取請(qǐng)求頭參數(shù)
 console.log(ctx.headers);
 // 獲取查詢字符串參數(shù)
 console.log(ctx.query);
 // 設(shè)置響應(yīng)頭信息
 ctx.set('content-type', 'text/html;charset=utf-8');
 // 設(shè)置響應(yīng)內(nèi)容,由框架負(fù)責(zé)返回響應(yīng)~
 ctx.body = '<h1>hello myKoa</h1>';
})
// 監(jiān)聽端口號(hào)
app.listen(3000, err => {
 if (!err) console.log('服務(wù)器啟動(dòng)成功了');
 else console.log(err);
})
到這里已經(jīng)寫完了 Koa 主要代碼,有一句古話 - 看萬(wàn)遍代碼不如寫上一遍。 還等什么,趕緊寫上一遍吧~
當(dāng)你能夠?qū)懗鰜?lái),再去閱讀源碼,你會(huì)發(fā)現(xiàn)源碼如此簡(jiǎn)單~

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作

    輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作

    這篇文章主要介紹了輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作,本系列文章會(huì)教你一步一步創(chuàng)建一個(gè)完整的服務(wù)器,要的朋友可以參考下
    2014-12-12
  • 基于node.js制作簡(jiǎn)單爬蟲教程

    基于node.js制作簡(jiǎn)單爬蟲教程

    這篇文章主要為大家詳細(xì)介紹了基于node.js制作簡(jiǎn)單爬蟲的教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Node4-5靜態(tài)資源服務(wù)器實(shí)戰(zhàn)以及優(yōu)化壓縮文件實(shí)例內(nèi)容

    Node4-5靜態(tài)資源服務(wù)器實(shí)戰(zhàn)以及優(yōu)化壓縮文件實(shí)例內(nèi)容

    這篇文章主要介紹了Node4-5靜態(tài)資源服務(wù)器實(shí)戰(zhàn)以及優(yōu)化壓縮文件實(shí)例內(nèi)容,有需要的朋友們可以參考學(xué)習(xí)下。
    2019-08-08
  • MQTT Client實(shí)現(xiàn)消息推送功能的方法詳解

    MQTT Client實(shí)現(xiàn)消息推送功能的方法詳解

    這篇文章主要介紹了MQTT Client實(shí)現(xiàn)消息推送功能的方法,結(jié)合實(shí)例形式詳細(xì)分析了MQTT Client實(shí)現(xiàn)消息推送的基本原理、實(shí)現(xiàn)方法與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2023-05-05
  • node實(shí)現(xiàn)簡(jiǎn)單的增刪改查接口實(shí)例代碼

    node實(shí)現(xiàn)簡(jiǎn)單的增刪改查接口實(shí)例代碼

    在本篇文章里小編給大家整理的是關(guān)于node實(shí)現(xiàn)簡(jiǎn)單的增刪改查接口的相關(guān)實(shí)例內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。
    2019-08-08
  • linux服務(wù)器快速卸載安裝node環(huán)境(簡(jiǎn)單上手)

    linux服務(wù)器快速卸載安裝node環(huán)境(簡(jiǎn)單上手)

    這篇文章主要介紹了linux服務(wù)器快速卸載安裝node環(huán)境(簡(jiǎn)單上手),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • 解決Mac下安裝nmp的淘寶鏡像失敗問(wèn)題

    解決Mac下安裝nmp的淘寶鏡像失敗問(wèn)題

    今天小編就為大家分享一篇解決Mac下安裝nmp的淘寶鏡像失敗問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • node檢測(cè)端口可用性的實(shí)踐示例

    node檢測(cè)端口可用性的實(shí)踐示例

    本文主要介紹了node檢測(cè)端口可用性的實(shí)踐示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • 利用Node轉(zhuǎn)換Excel成JSON的詳細(xì)步驟

    利用Node轉(zhuǎn)換Excel成JSON的詳細(xì)步驟

    最近工作中遇到一個(gè)需求,大致需求就是將Excel文件在導(dǎo)入時(shí)解析為json格式轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu)再傳輸給后臺(tái),下面這篇文章主要給大家介紹了關(guān)于如何利用Node轉(zhuǎn)換Excel成JSON的詳細(xì)步驟,需要的朋友可以參考下
    2022-11-11
  • node.js中的fs.readSync方法使用說(shuō)明

    node.js中的fs.readSync方法使用說(shuō)明

    這篇文章主要介紹了node.js中的fs.readSync方法使用說(shuō)明,本文介紹了fs.readSync方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12

最新評(píng)論