当前位置: 首页 > 后端技术 > Node.js

Express实践(四):中间件

时间:2023-04-03 10:20:15 Node.js

nativeNode的单请求处理功能随着功能的扩展必然会越来越难维护。Express框架可以通过中间件按模块和功能拆分处理功能。这样的拆分模块,不仅逻辑清晰,更重要的是对后期的维护和开发非常有利。本文将详细介绍Express的使用,主要内容包括:什么是中间件?用于请求处理的中间件堆栈和工作流。中间件的使用。如何实现自己的中间件。Express常用的第三方中间件。希望大家在阅读本文后,能够对这个Express的主要组成部分有一个更清晰的认识。中间件和中间件栈对于所有的web应用来说,它的处理流程可以简单的描述为:监听请求,解析请求,做出响应。当然,Node也遵循这个过程,只是将那些请求转换为JavaScript对象。与本机Node代码不同,Express将上图的最后一部分拆分为一组中间件函数(中间件堆栈)。所以Express的工作流程大致如下:与纯Node不同,Express中的中间件栈函数除了代表请求和响应的参数外,还增加了第三个参数。参数是一个函数对象,按照惯例我们称之为next。用于传递中间件栈对一个请求的处理流程。在整个中间件栈的处理流程中,至少有一个函数需要调用res.end方法来结束响应处理。接下来,我们将通过构建静态文件服务来加深对中间件栈的理解。示例:静态文件服务器创建一个文件夹并从中提供静态文件。您可以在该文件夹中存储任何文件,例如:HTML文件、图片。最终,所有这些文件都可以通过示例程序进行网络访问。示例程序的功能一般包括:能正确返回已有文件;文件不存在时返回404错误;打印所有访问请求。因此,此示例的中间件堆栈如下:日志记录中间件。该函数会将所有网络请求打印到终端,打印完介绍后继续执行下一个中间件函数。静态文件发送中间件。如果访问的文件存在,则返回给客户端。如果文件不存在,则跳转到错误处理中间件。404处理中间件。如果该文件不存在,中间件将向客户端发送404错误消息。流程图如下:明确了示例的目标和需求后,我们将实现下面的代码。准备工作和之前一样,新建一个项目目录,将以下内容复制到package.json中:{"name":"static-file-fun","private":true,"scripts":{"start":"nodeapp.js"}}接下来,我们执行npminstallexpress--save来安装最新版本的Express。确保安装完成后,我们在项目目录下新建文件夹static,并在里面存放一些文件。最后,我们新建一个项目主入口文件app.js。一切准备就绪后,项目的总目录如下:另外,值得一提的是,之所以配置npmstart命令,不仅仅是因为开发协议,更重要的是允许其他人使用它开箱即用,无需手动查找程序入口。第一个中间件:Logging按照前面制定的处理流程,首先要实现的是日志中间件。将以下代码复制到入口文件app.js中:varexpress=require("express");varpath=require("path");varfs=require("fs");varapp=express();app.use(function(req,res,next){console.log("请求IP:"+req.url);console.log("请求日期:"+newDate());});app.listen(3000,function(){console.log("App在3000端口启动");});通过上面的app.use函数,我们成功实现了应用中的第一个功能,即记录每一次网络请求。当然,这里还有一个问题,当前应用没有响应请求。这意味着:如果您使用npmstart启动服务并访问loaclhost:3000,浏览器将挂起等待响应,直到发生超时错误。不过不用担心,我们可以在完成以下功能后调用中间件上的next()将响应任务交给后续的中间件。这里我们只需要明白:理论上,一个中间件函数处理完成后,必须执行以下两个步骤之一。所有处理完成后,在Express中发送red.end或red.sendFile结束响应。调用next函数执行下一个中间件函数。所以这里我们先完成next()的调用,理清日志中间件的逻辑://...app.use(function(req,res,next){console.log("RequestIP:"+req.url);console.log("Requestdate:"+newDate());next();//这一行很重要});//...重启服务并访问http://localhost:3000,访问请求将被完整记录。但由于程序没有响应,Express会向客户端发送错误消息。那么,接下来我们就要完成后续的流程了。静态文件服务中间件静态文件服务中间件应该具备以下功能:检查目录中是否存在该文件,如果存在则调用res.sendFile结束响应处理。如果文件不存在,继续调用下一个中间件,从代码上看就是调用next。其中,我们需要使用内置的path模块来指定路径,然后使用内置的fs模块来判断释放文件是否存在。在日志中间件后面添加如下代码://日志中间件app.use(function(req,res,next){//...});app.use(function(req,res,next){varfilePath=PATH.JOIN(__DIRNAME,"Static",Req.url);fs.exists(filepath,function(exist){if(exist){res.Sendfile(文件路径);}else{下一步();});}););app.listen(3000,function(){...}在中间件中,我们首先使用path.join加入文件的完整路径。例如,如果用户访问http://localhost:3000/celine。...file那么req.url的值为/celine.mp3,拼接后的完整路径为“/path/to/your/project/static/celine.mp3”,然后中间件调用fs.exists函数检查文件是否存在,如果文件存在,则生成文件,否则调用next()继续执行下一个中间件,如果访问的URL没有对应的文件,会出现之前同样的错误。所以下面需要实现最后一个中间件:404处理中间件。404处理中间件404中间件的任务是发送404错误信息,复制如下实现代码,添加到静态服务中间件后面:app.use(function(req,res){//设置状态码为404res。status(404);//发送错误消息res.send("Filenotfound!");});//...整个项目完成。如果再次启动该服务,之前的错误将被404错误取代。另外,如果你把中间件函数移到中间件栈的第一个,你会发现所有的请求都会得到一个404错误信息。这意味着中间件堆栈中函数的顺序非常重要。至此,app.js中完整的代码如下:varexpress=require("express");varpath=require("path");varfs=require("fs");varapp=express();app.use(function(req,res,next){console.log("请求IP:"+req.url);console.log("请求日期:"+newDate());next();});app.use(function(req,res,next){varfilePath=path.join(__dirname,"static",req.url);fs.stat(filePath,function(err,fileInfo){if(err){nextreturn;}if(fileInfo.isFile()){res.sendFile(filePath);}else{next();}});});app.use(函数(req,res){res.status(404);res.send("找不到文件!");});app.listen(3000,function(){console.log("Appstartedonport3000");});当然,这只是初步的代码,还有很多可以优化的地方。将日志记录中间件替换为:软件开发中的Morgan如果您的问题已经存在好的解决方案,那么理想的做法是使用该解决方案而不是“重新发明轮子”。所以,下面我们使用强大的Morgan来代替上面自己实现的日志中间件。虽然,这个中间件不是Express内置的模块,但是由Express团队维护和试用。运行npminstallmorgan--save安装最新版本的Morgan模块。然后用Morgan替换之前的日志中间件:varexpress=require("express");varmorgan=require("morgan");...varapp=express();app.use(morgan("short"));...当你再次启动服务访问资源时,终端会打印IP地址等有用信息:代码中的morgan是一个函数,它的返回值是一个中间件函数。当你调用它时,它会返回一个类似实现的日志记录中间件。为了让代码更清晰,也可以将代码重写为:varmorganMiddleware=morgan("short");app.use(morganMiddleware);此外,此处调用函数时,short用作输出选项。事实上,该模块还提供了另外两个输出选项:组合打印最多的信息;tiny打印最少的信息。除了使用Morgan替换原来的日志中间件,我们还可以使用内置的静态中间件来替换之前的代码实现。使用Express内置的静态文件中间件接下来,我们使用Express内置的express.static模块来替换之前的静态文件中间件。它的工作方式与之前的中间件代码类似,但具有更好的安全性和性能。例如,它在内部实现了资源缓存。和Morgan一样,express.static函数的返回值也是一个中间件函数。我们只需要为express.static函数指定路径参数。代码如下:varstaticPath=path.join(__dirname,"static");//设置静态文件的路径app.use(express.static(staticPath));//使用express.static从静态路径提供服务//。..替换之后你会发现代码明显比以前更简洁了,同时功能也比以前更强了。此外,这些久经考验的中间件模块的功能和可靠性远高于其自身的代码实现。此时app.js中的完整代码:varexpress=require("express");varmorgan=require("morgan");varpath=require("path");varapp=express();app.使用(morgan("short"));varstaticPath=path.join(__dirname,"static");app.use(express.static(staticPath));app.use(function(req,res){res.status(404);res.send("找不到文件!");});app.listen(3000,function(){console.log("App在3000端口启动");});在错误处理中间件i之前,据说调用next()会依次执行下一个中间件。事实上,真实情况并没有这么简单。事实上,有两种类型的Express中间件。到目前为止,你接触到的是第一种:一个常规的三个参数的中间件函数(有时next会被忽略,只保留两个参数),程序中大部分时间都是采用这种常规模式。第二种非常少见:错误处理中间件。当您的应用程序处于错误模式时,将跳过所有常规中间件并直接执行Express错误处理中间件。要进入错误模式,只需调用带参数的next。这是调用错误对象的约定,例如:next(newError("Somethingbadhappened!"))。错误处理中间件需要四个参数,后三个与常规形式一致,第一个参数为传入的Error对象next(newError("Somethingbadhappened!"))。您可以像使用常规中间件一样使用错误处理中间件,例如呼叫res.end或next。如果调用带参数的下一个中间件将继续下一个错误处理中间件,否则它将退出错误模式并调用下一个正常中间件。假设,现在有四个中间件依次排列,第三个是错误处理中间件,其他都是常规中间件。如果没有报错,流程应该是:如上图,当没有报错时,错误处理中间件就好像不存在一样。但是,一旦出错,所有常规的中间件都会被跳过,所以处理流程会是这样的:虽然Express没有强制规定,但是一般的错误处理中间件都会放在中间件栈的底部。这样,之前所有的正则中间件在出错的时候都会被这个错误处理中间件捕获。Express的错误处理中间件只会捕获next触发的错误,throw关键字触发的异常不在处理范围内。对于这些异常,Express有自己的保护机制。当请求失败时,应用会返回500错误,整个服务会继续运行。但是语法错误等异常会直接导致服务崩溃。现在让我们通过一个简单的例子来看看Express中的错误处理中间件。假设应用程序针对用户的任何请求,通过res.sendFile向用户发送图片。代码如下:varexpress=require("express");varpath=require("path");varapp=express();varfilePath=path.join(__dirname,"celine.jpg");app.use(function(req,res){res.sendFile(filePath);});app.listen(3000,function(){console.log("Appstartedonport3000");});可以看到这是之前静态简化版的文件服务,任何请求都会生成celine.jpg图片。但是如果文件不存在,或者文件读取过程中出现错误怎么办?这就需要某种机制来处理这种异常错误,这就是错误处理中间件存在的原因。为了触发异常处理,我们在res.sendFile中完成异常回调函数。这个回调函数会在文件发送完成后执行,回调函数中有一个参数来标记文件是否发送成功。代码示例如下:res.sendFile(filePath,function(err){if(err){console.error("Filefailedtosend.");}else{console.log("Filesent!");}});当然,除了打印错误信息,我们还可以通过触发异常进入错误处理中间件函数,这部分代码实现如下://...app.use(function(req,res,next){res.sendFile(filePath,function(err){if(err){next(newError("发送文件出错!"));}});});//...异常触发后,接下来就是错误处理中间件的实现了。一般情况下,我们会先记录错误信息,而这些信息一般不会显示给用户。毕竟,向非技术用户展示一个长长的JavaScript堆栈调用会给他们带来不必要的困惑。尤其是一旦这些信息暴露给黑客,他们就有可能对网站的运作方式进行逆向工程,从而造成信息风险。下面,我们只是在处理中间件中打印错误信息,没有做任何进一步的处理。它类似于前面的中间件,只是它打印错误消息而不是请求消息。在所有常规中间件之后复制以下代码://...app.use(function(err,req,res,next){//记录错误console.error(err);//继续下一个错误处理中间件next(err);});//...现在,当程序发生异常时,这些错误信息将被记录在控制台中,以便稍后进一步分析。当然这里还有一些事情需要处理,比如:响应请求。在前面的中间件之后放置以下代码://...app.use(function(err,req,res,next){//将状态码设置为500res.status(500);//发送错误消息res.send("Internalservererror.");});//...记住,这些错误处理中间件无论在什么地方,都只能被带有参数的next触发。对于这个简单的应用程序,可能不会有那么多会触发错误处理中间件的异常和错误。但是随着应用程序的扩展,您需要仔细测试不当行为。如果出现异常,那么你应该优雅地处理它们而不是让程序崩溃。总结在本文中,我们仔细研究了Express的核心模块:中间件。内容包括:Express中间件栈和工作流的概念。如何编写自定义中间件函数。如何编写错误处理中间件。使用通用中间件模块。原地址