Heim  >  Artikel  >  Web-Frontend  >  Vergleichen Sie die NodeJS-Middleware Koa und Express

Vergleichen Sie die NodeJS-Middleware Koa und Express

青灯夜游
青灯夜游nach vorne
2021-02-24 09:52:142256Durchsuche

Vergleichen Sie die NodeJS-Middleware Koa und Express

Verwandte Empfehlungen: „nodejs Tutorial

Apropos Middleware: Viele Entwickler werden an Koa.js denken, und sein Middleware-Design ist zweifellos einer der typischen Vertreter des Front-End-Middleware-Denkens.

Als ich diesen Teil des Inhalts kürzlich überprüft habe, konnte ich nicht anders, als mit euch Lesern über seine Wunderbarkeit zu plaudern!


Koa ist sehr benutzerfreundlich – im Vergleich zu Express wirken die Funktionen aufgrund des „perfekten Middleware“-Designs sehr einfach! Der Autor hat dies im Projekt verwendet:

const Koa=require('koa')
const app=new Koa()
const Router=require('koa-router')
const router=new Router()
const cors=require('koa2-cors')
const koaBody=require('koa-body')

const ENV='test-mpin2'

app.use(cors({
	origin:['http://localhost:9528'],   // 也可以写为:['*']
	credentials:true
}))
app.use(koaBody({
	multipart:true
}))
app.use(async(ctx,next)=>{
	console.log('访问全局中间件')
	ctx.state.env=ENV   // 全局缓存
	await next()
})

const playlist=require('./controller/playlist.js')
router.use('/playlist',playlist.routes())
const blog=require('./controller/blog.js')
router.use('/blog',blog.routes())

app.use(router.routes()).use(router.allowedMethods())

app.listen(3000,()=>{
	console.log('服务已开启')
})

Es ​​extrahiert den Routing-Router und verwendet ihn als separate Middleware, und die App ist nur für die globale Verarbeitung verantwortlich. Ein weiteres Beispiel:

// 最外层中间件,可以用于兜底 Koa 全局错误
app.use(async (ctx, next) => {
  try {
    // 执行下一个中间件
    await next();
  } catch (error) {
    console.log(`[koa error]: ${error.message}`)
  }
});
// 第二层中间件,可以用于日志记录
app.use(async (ctx, next) => {
  const { req } = ctx;
  console.log(`req is ${JSON.stringify(req)}`);
  await next();
  console.log(`res is ${JSON.stringify(ctx.res)}`);
});

Ein Koa einfach umzusetzen!

Wie im obigen Code gezeigt, schauen wir uns die Koa-Instanz an und registrieren und verketten die Middleware über die use-Methode. Die einfache Implementierung des Quellcodes kann ausgedrückt werden als:

use(fn) {
    this.middleware.push(fn);
    return this;
}

Wir speichern die Middleware im this.middleware array , also wie wird die Middleware ausgeführt? Sehen Sie sich den Quellcode unten an: this.middleware数组中,那么中间件是如何被执行的呢?参考下面源码:

// 通过 createServer 方法启动一个 Node.js 服务
listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
}

Koa 框架通过 http 模块的 createServer 方法创建一个 Node.js 服务,并传入 this.callback() 方法, callback源码简单实现如下:

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

handleRequest(ctx, fn) {
    const onerror = err => ctx.onerror(err);
    // 将 ctx 对象传递给中间件函数 fn
    return fn(ctx).catch(onerror);
}

如上代码,我们将 Koa 一个中间件组合和执行流程梳理为以下步骤:

  • 通过一个方法(我们称为compose)组合各种中间件,返回一个中间件组合函数fn

  • 请求过来时,会先调用handleRequest方法,该方法完成:

    • 调用createContext方法,对该次请求封装出一个ctx对象;
    • 接着调用this.handleRequest(ctx, fn)处理该次请求。

其中,核心过程就是使用compose方法组合各种中间件 —— 这是一个单独的方法,它应该不受Koa其余方法的约束。其源码简单实现为:

// 组合中间件
// 和express中的next函数意义一样
function compose(middlewareList){
	// return function意思是返回一个函数
	return function(ctx,next){
		// 各种中间件调用的逻辑
		function dispatch(i){
			const fn=middlewareList[i] || next
			if(fn){
				try{
					// koa中都是async,其返回的是一个promise(对象)
					return Promise.resolve(fn(ctx,function next(){
						return dispatch(i+1)
					}))
				}catch(err){
					return Promise.reject(err)
				}
			}else{
				return Promise.resolve()
			}
		}
		return dispatch(0)
	}
}

其功能可以表示为这样(非源码):

async function middleware1() {
  //...
  await (async function middleware2() {
    //...
    await (async function middleware3() {
      //...
    });
    //...
  });
  //...
}

到这里我们其实可以“初窥”其原理,有两点:

  • Koa 的中间件机制被社区形象地总结为洋葱模型;

所谓洋葱模型,就是指每一个 Koa 中间件都是一层洋葱圈,它即可以掌管请求进入,也可以掌管响应返回。换句话说:外层的中间件可以影响内层的请求和响应阶段,内层的中间件只能影响外层的响应阶段。

  • dispatch(n)对应第 n 个中间件的执行,在使用中即第 n 个中间件可以通过await next()来“插入”执行下一个中间件,同时在最后一个中间件执行完成后,依然有恢复执行的能力。即:通过洋葱模型,await next()控制调用后面的中间件,直到全局没有可执行的中间件且堆栈执行完毕,最终“原路返回”至第一个执行next的中间件。这种方式有个优点,特别是对于日志记录以及错误处理等全局功能需要非常友好

Koa1 的中间件实现利用了 Generator 函数 + co 库(一种基于 Promise 的 Generator 函数流程管理工具),来实现协程运行。本质上,Koa v1 中间件和 Koa v2 中间件思想是类似的,只不过 Koa v2 改用了 Async/Await 来替换 Generator 函数 + co 库,整体实现更加巧妙,代码更加优雅。—— from《狼书》

经过上述部分源码的描述,我们就可以采用es6的方式将其组合起来:

// myKoa.js文件

const http=require('http')

function compose(){}   //见上

class LikeKoa2{
	constructor() {
	    this.middlewareList=[]
	}
	use(){}   //见上
	
	// 把所有的req,res属性、事件都交给ctx(这里只是简写)
	createContext(req,res){
		const ctx={
			req,
			res
		}
		// 比如
		ctx.query=req,query
		return ctx
	}
	handleRequest(){}   //见上
	callback(){}   //见上
	listen(){}   //见上
}

// koa和express的不同之一:
// express在调用时直接调用函数:const app=express();所以暴露出去new过的对象——具体见下面链接中代码
// 但是koa调用时以类的方式:const app=new Koa();所以直接暴露出去
module.exports=LikeKoa2

那use方法和其余方法并不相通,它是如何被执行的呢?执行了createServer后是不是相当于建立了一个通道、挂载了一个监听函数呢?
这一点恐怕就要到Node的源码中一探究竟了…


对比 Koa,聊聊 Express 原理

说起 Node.js 框架,我们一定忘不了 Express —— 不同于 Koa,它继承了路由、静态服务器和模板引擎等功能,虽然比之Koa显得“臃肿”了许多,但看上去比 Koa 更像是一个框架。通过学习 Express 源码,笔者简单的总结了它的工作机制:

  • 通过app.use方法注册中间件。

  • 一个中间件可以理解为一个 Layer 对象,其中包含了当前路由匹配的正则信息以及 handle 方法。

  • 所有中间件(Layer 对象)使用stack数组存储起来。

  • 当一个请求过来时,会从 req 中获取请求 path,根据 path 从stack中找到匹配的 Layer,具体匹配过程由router.handle函数实现。

  • router.handle函数通过next()

    ((req, res) => {
      console.log('第一个中间件');
      ((req, res) => {
        console.log('第二个中间件');
        (async(req, res) => {
          console.log('第三个中间件');
          await sleep(2000)
          res.status(200).send('hello')
        })(req, res)
        console.log('第二个中间件调用结束');
      })(req, res)
      console.log('第一个中间件调用结束')
    })(req, res)
    Das Koa-Framework erstellt einen Node.js-Dienst über die Methode createServer des http-Moduls und übergibt den Code this.callback() >-Methode. Der Callback-Quellcode wird einfach wie folgt implementiert: 🎜rrreee🎜Wie im Code oben gezeigt, organisieren wir die Koa-Middleware-Kombination und den Ausführungsprozess in die folgenden Schritte: 🎜<ul style="list-style-type: disc;"> <li>🎜Durch eine Methode (wir nennen sie compose) werden verschiedene Middleware-Funktionen kombiniert und eine Middleware-Kombinationsfunktion zurückgegeben <code>fn🎜
  • 🎜Wenn eine Anfrage kommt, wird die handleRequest
wird zuerst aufgerufen. Diese Methode wurde abgeschlossen: 🎜
  • Rufen Sie die Methode createContext auf, um ein ctx-Objekt für diese Anfrage zu kapseln >Dann rufen Sie this.handleRequest(ctx, fn)auf. Verarbeiten Sie die Anfrage.
🎜Unter diesen besteht der Kernprozess darin, die Compose-Methode zu verwenden, um verschiedene Middlewares zu kombinieren – dies ist eine separate Methode und sollte nicht durch den Rest eingeschränkt werden Koas Methoden. Sein Quellcode wird einfach wie folgt implementiert: 🎜rrreee🎜 Seine Funktion kann wie folgt ausgedrückt werden (Nicht-Quellcode): 🎜rrreee🎜An diesem Punkt können wir tatsächlich einen „ersten Blick“ auf sein Prinzip werfen, es gibt zwei Punkte: 🎜
  • Koas Middleware Der Mechanismus wird von der Community anschaulich als Zwiebelmodell zusammengefasst
🎜Das sogenannte Zwiebelmodell bedeutet, dass jede Koa-Middleware eine Schicht aus Zwiebelringen ist , das sowohl den Anforderungseintrag als auch die zurückgegebene Antwort verarbeiten kann. Mit anderen Worten: Die äußere Middleware kann die Anforderungs- und Antwortphasen der inneren Schicht beeinflussen, und die innere Middleware kann nur die Antwortphase der äußeren Schicht beeinflussen. 🎜
  • dispatch(n) entspricht der Ausführung der n-ten Middleware. Bei Verwendung kann die n-te Middleware über „await next()“ „eingefügt“ werden, um die nächste Middleware auszuführen gleichzeitig Nachdem die letzte Middleware-Ausführung abgeschlossen ist, besteht immer noch die Möglichkeit, die Ausführung fortzusetzen. Das heißt: Über das Zwiebelmodell steuert das Warten auf next () den Aufruf nachfolgender Middleware, bis global keine ausführbare Middleware mehr vorhanden ist und der Stapel ausgeführt wird, und kehrt schließlich zum ursprünglichen Pfad zur ersten Middleware zurück, die als nächstes ausgeführt wird. Dieser Ansatz hat Vorteile, insbesondere für globale Funktionen wie Protokollierung und Fehlerbehandlung, die sehr benutzerfreundlich sein müssen.
🎜Koa1s Middleware-Implementierung verwendet die Generator-Funktion + Co-Bibliothek (ein auf Promise basierendes Prozessmanagement-Tool für Generatorfunktionen), um den Coroutine-Betrieb zu implementieren. Im Wesentlichen sind die Ideen der Koa v1-Middleware und der Koa v2-Middleware ähnlich, mit der Ausnahme, dass Koa v2 Async/Await verwendet, um die Generator-Funktion + die Co-Bibliothek zu ersetzen. Die Gesamtimplementierung ist cleverer und der Code eleganter. —— aus „Wolf Book“ 🎜
🎜Nachdem wir den obigen Teil des Quellcodes beschrieben haben, können wir ihn mit es6 kombinieren: 🎜rrreee🎜Die Verwendungsmethode ist nicht mit anderen Methoden verbunden. Wie wird sie ausgeführt? ? Entspricht die Ausführung von createServer dem Einrichten eines Kanals und dem Mounten einer Abhörfunktion?
Ich fürchte, wir müssen mehr darüber im Quellcode von Node herausfinden ... 🎜🎜🎜🎜Im Vergleich zu Koa sprechen wir über das Prinzip von Express🎜🎜🎜Apropos Node.js-Framework , wir dürfen Express nicht vergessen – im Gegensatz zu Koa erbt es Funktionen wie Routing, statischen Server und Template-Engine. Obwohl es viel „aufgedunsen“ ist als Koa, ähnelt es eher einem Framework als Koa. Durch das Studium des Express-Quellcodes fasste der Autor einfach seinen Arbeitsmechanismus zusammen: 🎜
  • 🎜Middleware über die app.use-Methode registrieren. 🎜
  • 🎜Eine Middleware kann als Layer-Objekt verstanden werden, das die regulären Informationen enthält, die mit der aktuellen Route und der Handle-Methode übereinstimmen. 🎜
  • 🎜Alle Middleware (Layer-Objekte) werden mithilfe des Stack-Arrays gespeichert. 🎜
  • 🎜Wenn eine Anfrage eingeht, wird der Anfragepfad von req abgerufen und die passende Ebene wird entsprechend dem Pfad aus dem Stapel gefunden. Der spezifische Matching-Prozess wird durch den implementiert router.handle-Funktion. 🎜
  • 🎜Die Funktion router.handle durchläuft jede Ebene zum Vergleich mit der Methode next(): 🎜
    • next()方法通过闭包维持了对于 Stack Index 游标的引用,当调用next()方法时,就会从下一个中间件开始查找;
    • 如果比对结果为 true,则调用layer.handle_request方法,layer.handle_request方法中会调用next()方法 ,实现中间件的执行。

通过上述内容,我们可以看到,Express 其实是通过 next() 方法维护了遍历中间件列表的 Index 游标,中间件每次调用next()方法时,会通过增加 Index 游标的方式找到下一个中间件并执行。它的功能就像这样:

((req, res) => {
  console.log('第一个中间件');
  ((req, res) => {
    console.log('第二个中间件');
    (async(req, res) => {
      console.log('第三个中间件');
      await sleep(2000)
      res.status(200).send('hello')
    })(req, res)
    console.log('第二个中间件调用结束');
  })(req, res)
  console.log('第一个中间件调用结束')
})(req, res)

如上代码,Express 中间件设计并不是一个洋葱模型,它是基于回调实现的线形模型,不利于组合,不利于互操,在设计上并不像 Koa 一样简单。而且业务代码有一定程度的侵扰,甚至会造成不同中间件间的耦合。

express的简单实现笔者已上传至腾讯微云,需要者可自行查看&下载:express的简单实现

更多编程相关知识,请访问:编程视频!!

Das obige ist der detaillierte Inhalt vonVergleichen Sie die NodeJS-Middleware Koa und Express. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen