宝哥软件园

用Node.js实现简单MVC框架的方法

编辑:宝哥软件园 来源:互联网 时间:2021-09-04

在使用Node.js搭建静态资源服务器的文章中,我们已经完成了服务器对静态资源请求的处理,但不涉及动态请求,目前还无法根据客户端发送的不同请求返回个性化内容。仅静态资源就可以支持这些复杂的网站应用。本文将介绍如何使用Node来处理动态请求,以及如何构建一个简单的MVC框架。由于已经详细描述了对静态资源请求的响应,本文将跳过所有静态部分。

一个简单的例子

从一个简单的例子开始,了解如何在Node中向客户端返回动态内容。

假设我们有这样的需求:

当用户访问/actors时,返回到actors列表页面

当用户访问/女演员时返回女演员列表

您可以使用以下代码来完成该功能:

const http=require(' http ');const URL=require(' URL ');http.createServer((req,RES)={ const pathName=URL . parse(req . URL)。路径名;if(['/演员','/女演员']。包括(pathName)) { res.writeHead(200,{ ' Content-Type ' : ' text/html ' });const演员=['莱昂纳多迪卡普里奥','布拉德皮特','约翰尼德普'];常量女演员=['詹妮弗安妮斯顿','斯嘉丽约翰逊','凯特温斯莱特'];let list=[];if(pathName==='/actors '){ list=actors;} else { lists=女演员;} const content=lists.reduce((模板、项目、索引)={ return template `pNo。$ { index 1 } $ { item }/p `;},` h1 $ { pathname . slice(1)}/h1 `);res.end(内容);} else { RES . write head(404);RES . end(' h1未找到请求的页面。/h1') }})。听(9527);上述代码的核心是路由匹配。当请求到达时,检查是否有与其路径相对应的逻辑处理。当请求不匹配任何路由时,它返回到404。匹配成功时处理相应的逻辑。

simple  request

显然,上面的代码并不是通用的,在只有两个路由匹配候选的前提下(而且请求方法还没有区分),数据库和模板文件都没有用过,代码已经有些纠结了。因此,我们将构建一个简单的MVC框架来分离数据、模型和性能。

构建一个简单的MVC框架

MVC指的是:

M:型号(数据)

V:视图(性能)

C:控制器(逻辑)

在Node中,MVC架构下处理请求的过程如下:

请求到达服务器

服务器将请求提交给路由过程

路由通过路径匹配将请求引导到相应的控制器

控制器接收请求并向模型请求数据

模型将所需数据返回给控制器

控制器可能需要对接收到的数据进行一些再处理

控制器将处理后的数据提供给查看者

视图基于数据和模板生成响应内容

服务器将这些内容返回给客户端

基于此,我们需要准备以下模块:

服务器:监听并响应请求

Router:会将请求提交给正确的控制器进行处理

控制器:执行业务逻辑,从模型中取出数据并将其传递给视图

型号:提供数据

视图:提供html

创建以下目录:

-server . js-lib-router . js-view-controller-modelsserver

创建server.js文件:

const http=require(' http ');const router=require('。/lib/router ')();router.get('/actors ',(req,res)={ res.end('莱昂纳多迪卡普里奥,布拉德皮特,约翰尼德普');});http.createServer(路由器)。听(9527,err={ if(err){ console . error(err);console.info('无法启动服务器');} else { console.info(`服务器已启动');}});不管这个文件中的细节如何,路由器是接下来要完成的模块,这里先介绍一下,当请求到达时,路由器会处理。

路由器模块

事实上,路由器模块只需要完成一件事,就可以将请求定向到正确的控制器。理想情况下,它可以这样使用:

const router=require('。/lib/router ')();const actorsController=require('。/controller/actor’);router.use((req,res,next)={ console.info('新请求到达');next()});router.get('/actors ',(req,RES)={ actorscontroller . fetchlist();});router.post('/actors/:name ',(req,RES)={ actorscontroller . createnewactor();});一般来说,我们希望它能同时支持路由中间件和非中间件,当请求到达时,由路由器交给匹配的中间件处理。中间件是一种可以访问请求对象和响应对象的功能。可以在中间件中完成的事情包括:

执行任何代码,例如添加日志和处理错误

修改请求(req)和响应对象(res),例如从req.url获取查询参数并将其分配给req.query

结束响应

调用下一个中间件(下一个)

注释:

应该注意的是,如果既没有最终响应,也没有调用下一个方法来控制下一个中间件,请求将挂起

_ _非路由中间件_ _以下列方式添加,以匹配所有请求:

router . use(fn);例如,上面的例子:

router.use((req,res,next)={ console.info('新请求到达');next()});_ _路由中间件_ _以下列方式添加,以请求方法和路径的精确匹配:

路由器。HTTP_METHOD(路径,fn)首先梳理出框架:

/lib/router.js

const METHODS=['GET ',' POST ',' PUT ',' DELETE ',' HEAD ',' OPTIONS '];module . exports=()={ const routes=[];const router=(req,RES)={ };router . use=(fn)={ routes . push({ method : null,path: null,handler : fn });};method . foreach(item={ const method=item . tolowercase();router[method]=(path,fn)={ routes.push({ method,path,handler : fn });};});};上面主要给路由器增加了use、get、post等方法,每当调用这些方法的时候都会给路由增加一个路由规则。

注释:

Javascript中的函数是一个特殊的对象,可以调用,同时也可以有属性和方法。

下一个重点是路由器功能,它需要做到:

从请求对象获取方法和路径名

根据添加顺序中的方法和路径名,将请求与路由数组中的每条路由进行匹配

如果与某个路由匹配成功,执行route.handler,然后与下一个路由匹配或结束该过程(稍后详述)

如果匹配不成功,继续匹配下一条路线,并重复步骤3和4

const router=(req,RES)={ const pathname=decodeURI(URL . parse(req . URL))。路径名);const method=req . method . tolowercase();设I=0;const next=()={ route=routes[I];if(!路线)返回;const routeForAllRequest=!路线.方法!route.pathif(routeForAllRequest | |(route . method===方法路径名===route . path)){ route . handler(req,res,next);} else { next();} } next();};对于非路由中间件,直接调用其处理程序。对于路由中间件,只有当请求方法和路径匹配成功时,才会调用其处理程序。当路线上没有匹配时,直接继续与下一条路线匹配。

需要注意的是,如果一个路由匹配成功,那么在执行它的处理程序之后,它是否会与下一个路由匹配,取决于开发人员是否调用它的处理程序中的next()来放弃控制。

在__server.js__中添加一些路由:

router.use((req,res,next)={ console.info('新请求到达');next()});router.get('/actors ',(req,res)={ res.end('莱昂纳多迪卡普里奥,布拉德皮特,约翰尼德普');});router . get('/女演员',(req,res)={ res.end('詹妮弗安妮斯顿,斯嘉丽约翰逊,凯特温斯莱特');});router.use((req,res,next)={ RES . statuscode=404;RES . end();});当每个请求到达时,首先打印一个日志,然后与其他路由匹配。当匹配到男女演员的get请求时,直接发回演员姓名,无需继续匹配其他路线。如果不匹配,返回404。

在浏览器中依次访问http://localhost:9527/erwe、http://localhost :9527/演员、http://本地主机:9527/女演员测试一下:

404

网络中观察到的结果符合预期,同时后台命令行中也打印出了三条新请求到达语句。

接下来继续改进路由器模块。

首先添加一个router.all方法,调用它即意味着为所有请求方法都添加了一条路线:

router.all=(路径,fn)={ methods。foreach(item={ const method=item。tolowercase();路由器[方法](路径,fn);}) };接着,添加错误处理。

/lib/router.js

const defaultErrorHander=(err,req,RES)={ RES . statusCode=500;RES . end();};模块。exports=(ErrorHander)={ const routes=[];const router=(req,RES)={ 0.错误处理程序=错误处理程序| | DefaultErrorhanderconst next=(err)={ if (err)返回错误处理器;} next();};server.js

.const router=require(' ./lib/router')((err,req,RES)={ console。错误(err);res.statusCode=500RES。end(err。堆栈);});默认情况下,遇到错误时会返回500,但开发者使用路由器模块时可以传入自己的错误处理函数将其替代。

修改一下代码,测试是否能正确执行错误处理:

router.use((req,res,next)={ console.info('新请求到达');下一个(新错误('错误'));});这样任何请求都应该返回500:

error  stack

继续,修改路线。路径与路径名的匹配规则。现在我们认为只有当两字符串相等时才让匹配通过,这没有考虑到全球资源定位器(统一资源定位符)中包含路径参数的情况,比如:

localhost :9527/演员/莱昂纳多与

router.get('/actors/:name ',somroutehandler);这条途径应该匹配成功才是。

新增一个函数用来将字符串类型的路线。路径转换成正则对象,并存入路由。模式:

const getroutepattern=pathname={ pathname='^'路径名。替换(/(:w )/g ',([a-za-z0-9-] s )')' $ ';返回新的正则表达式(路径名);};这样就可以匹配上带有路径参数的全球资源定位器(统一资源定位符)了,并将这些路径参数存入请求参数对象:

const matchedResults=路径名。匹配(路线。图案);if(路由。method===method matchedResults){ addparamsorquest(req,route.path,matchedResults);route.handler(req,res,next);} else { next();} const addparamsorequest=(req,routePath,matchedResults)={ req。params={ };让URlParameterNames=RoutePath。match(/:( w)/g);if (urlParameterNames) { for(让I=0;I URlParameterNames . LengTii){ req。参数名称.切片(1)]=匹配的结果[I 1];} }}添加个途径测试一下:

路由器。get('/actors/: year/: country ',(req,RES)={ RES . end(` year : $ { req。参数。年份}国家/地区: $ { req。参数。country } `));});访问http://localhost :9527/演员/1990/中国试试:

url  parameters

路由器模块就写到此,至于查询参数的格式化以及获取请求主体,比较琐碎就不试验了,需要可以直接使用博尔迪解析器等模块。

现在我们已经创建好了路由器模块,接下来将路由处理器内的业务逻辑都转移到控制器中去。

修改__server.js__,引入控制器:

.const actorsController=require(' ./controller/actor’);router.get('/actors ',(req,RES)={ actor controller。获取列表;});router.get('/actors/:name ',(req,RES)={ actor controller。getactorbyname(req,RES);});路由器。get('/actors/: year/: country ',(req,RES)={ actorscontroller。getatorsbyyeearandcountry(req,RES);});新建__控制器/执行器。js__:

const actorsTemplate=require('./视图/演员-列表');const actorsModel=require('./模特/演员');exports.getList=(req,RES)={ const data=actor model。getlist();const html str=actorstemplate。构建(数据);res.writeHead(200,{ ' Content-Type ' : ' text/html ' });RES . end(HTMl字符串);};出口。GetAttorByNAmE=(req,RES)={ const data=actors model。GetAttorByNAmE(请求。参数。姓名);const html str=actorstemplate。构建(数据);res.writeHead(200,{ ' Content-Type ' : ' text/html ' });RES . end(HTMl字符串);};出口。getatorsbyyeearandcountry=(req,RES)={ const data=actor model。getatorsbyyeearandcountry(请求。参数。年,请求。参数。国家);const html str=actorstemplate。构建(数据);res.writeHead(200,{ ' Content-Type ' : ' text/html ' });RES . end(HTMl字符串);};在控制器中同时引入了视角和模型,其充当了这二者间的粘合剂。回顾下控制器的任务:

控制器收到请求,向模型索要数据模型给控制器返回其所需数据控制器可能需要对收到的数据做一些再加工控制器将处理好的数据交给视角

在此控制器中,我们将调用模型模块的方法获取演员列表,接着将数据交给查看,交由视角生成呈现出演员列表页的超文本标记语言字符串。最后将此字符串返回给客户端,在浏览器中呈现列表。

从模型中获取数据

通常模型是需要跟数据库交互来获取数据的,这里我们就简化一下,将数据存放在一个json文件中。

/models/test-data.json

[ { '姓名' : '莱昂纳多迪卡普里奥','出生年份' : 1974,'国家' : '美国','电影' : ['泰坦尼克号','起床号','盗梦空间'] },{ '姓名' : '布拉德皮特','出生年份' : 1963,'国家' : '美国','电影' : ['搏击俱乐部','无耻混蛋','史密斯夫妇'] },{ '接着就可以在模型中定义一些方法来访问这些数据。

模特/演员。射流研究…

const actors=require(' ./test-data’);出口。get list=()=actors;出口。GetActorByNAmE=(name)=actors。筛选器(actor={ return actor。name==name});出口。getatorsbyyearandcountry=(年份,国家)=演员。筛选器(actor={ return actor['出生年份']==年份actor.country==国家;});当控制器从模型中取得想要的数据后,下一步就轮到视角发光发热了视图。层通常都会用到模板引擎,如灰尘等。同样为了简化,这里采用简单替换模板中占位符的方式获取html,渲染得非常有限,粗略理解过程即可。

创建/view/actor-list。js :

const actor template=` h1 { name }/h1 PEM born :/em { contry },{ year }/pul { movies }/ul `;出口。build=list={ let content=名单。foreach(actor={ content=actor template。替换(' { name } ',actor.name)).替换(“{contry}”,actor.country).替换(“{year}”,演员[”出生年份'])。替换(“{movies}”,演员。电影。reduce((movietml,电影名)={ return moviesHTML ` Li $ {电影名}/Li `},' ');});返回内容;};在浏览器中测试一下:

test  mvc

至此,就大功告成啦!

以上这篇使用Node.js实现简易手动音量调节框架的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

更多资讯
游戏推荐
更多+