Express是一个基于Node.js的轻量级web开发框架,具有体积小,使用灵活等特点。查看Express的源码,如果不计供使用的中间件,主体框架只有一千余行代码,非常简练。
Express模型的核心为Express中定义的路由和路由器。分析Express源码可发现Express的路由提供多种灵活的应用模式。
我们首先介绍一下Express中的路由、路由器相关概念、结构及其特点,然后针对典型场景描述使用Express路由的四种应用模式。
Express具有典型的MVC模型特征。我们将路由定义为一个二元组route=(path, endpoint):其中path为HTTP请求的路径,endpoint为请求路径应映射到的端点(端点可视为处理该请求的实体),则Express中的路由器负责将请求映射到对应端点进行处理。
Express中的路由器分为两种类型:
app类型的路由器常使用如下代码创建:
var express = require('express'); var app = express();
router类型的路由器常使用如下代码创建:
var express = require('express'); var router = express.Router();
app和router是形为function(request, response, next)形式的函数对象,使用app.verb(),router.verb()形式函数实现路由注册(路由注册本质上是一个观察者模式)。
app.verb()和router.verb()中的verb常使用use、get、post、put、delete、route等动词,不同动词管辖的HTTP请求方法范围不同,这些动词函数的参数形式常为(pathExp, handleCallback)形式:其中pathExp表示请求路径,可为正则表达式;handleCallback为路径映射处理函数。
app是Express框架所构建程序的请求处理入口,app可作为顶层路由器使用,在应用中也可挂载下级路由器(使用router对象)以实现分级路由。其间的关系可由下图表示:
考察如下代码:
app.use('/reports', router1); router1.get('/querymysql/:id', queryMysqlData, handleQueryData);
对于http请求URL“/reports/querymysql/1”,Express中的路由器将此请求路由到queryMysqlData函数处理。
对于一个使用restful风格的应用, 让我们想一想在不使用Express的时候如何在Node.js中处理rest请求,我们常常会写下如下示例代码:
var server = http.createServer(function (request, response) { switch (request.url) { case 'uri1' handleUri1 (request, response); break; case ' uri2' handleU ri2(request, response); break; case ' uri3' handleU ri3 (request, response); break; case ' uri4' handleU ri4 (request, response); break; ... default: logToConsole('unknown path:' + path); response.writeHead(404); response.end("404 Not found"); break; } }
Express将上面代码中对每个rest资源的操作(switch分支)转换为路由,路由中的路径为rest资源的URI,处理端点为function(request, response, next)形式、对rest资源的操作函数。
常使用app.route函数实现一个完整的restful接口,如下示例代码:
app.route('/uri1') .get(function(req, res) { handleGetUri1(); }) .post(function(req, res) { handlePost Uri1 (); }) .put(function(req, res) { handlePut Uri 1(); }) .delete(function(req, res) { handleDeleteUri 1(); });
在处理不同路径的HTTP请求时,常常需要在请求处理前和处理后做一些通用操作,这种应用需求是一个典型的AOP应用要求。
Express中允许定义一个具有通配路径的路由,在调用其它路径的路由前会先调用该通配路径路由。此通配路径路由也成为其它路径路由切面的一个注入点,考察如下示例代码:
router.use(function timelog(req,res,next){ console.log("receive report request time is:",Date.now()); next(); //注意next函数的使用,必须声明该函数才能调用后继映射函数 }); router.get('/chart1', proxy({ target: 'http://127.0.0.1:8082', changeOrigin: true, pathRewrite: { '^/reports/chart1': '/loadChart1' } })); router.get('/querymysql/:id', queryMysqlData, handleQueryData);
上述代码中,在执行router的'/chart1'路由和'/querymysql/:id'路由之前都会执行timelog函数,在日志中记录当前路由执行时间。
在Node.js中,由于多使用异步函数,常会出现异步回调函数中嵌套异步回调函数的情形。当出现多重异步回调时,则代码会变得混乱和难以维护。
考察一个应用场景:应用需要在数据库中进行多次查询,并对多次查询的结果综合处理。若使用数据库提供的异步查询接口,则需要在前一个查询操作的回调函数中进行下一个查询操作,若写在一个回调函数中,代码显臃肿。
Express的一个路由可定义多个处理函数,这些处理函数可设计为链式调用,实现了责任链模式,考察如下代码:
app.get('/test',function(req,res,next){ handle1(); next(); },function(req,res,next){ handle2(); next(); },function(req,res,next){ handle3(); });
上述代码中:handle1, handle2, handle3构成了一个处理责任链“handle1->handle2->handle3”,通过next函数指引链式调用。
Express中路由的责任链应用特性使得多重异步嵌套的代码变得清晰和优雅。
针对本节开始提到的数据库查询应用场景,下面的示例代码展示了责任链模式的应用特点。
router.get('/querymysql/:id', queryMysqlData, handleQueryData); //查询mysql表中的数据 function queryMysqlData(req, res, next) { if ("id" in req.params) { dbpool.query(" select * from articles where id=?" ,[req.params.id], function (err, rows, fields) { if (err) { res.send(err.stack); } else { if (rows && rows.length > 0) { console.log('The queried rows is: ', rows.length); res.articles = rows; next(); } else { res.send("no query results!"); } } }); } else { res.send("invalid query params!"); } } //处理查询mysql后得到的数据 function handleQueryData(req, res) { if ("articles" in res) { res.send("id:" + req.params.id + ";title:" + res.articles[0].title); }else{ res.send("no query data handled!"); } }
上节提到的责任链模式本质上是一个逐级调用模型。在分布式服务架构(微服务架构)中,深度调用常常需要考虑调用可达性问题,即需要考虑某级调用会否一直不响应。调用可达性问题常使用熔断器模式,即在调用端设置一个熔断器,熔断条件产生时,熔断器发生熔断,返回给调用方调用失败信息。
考虑这样的应用场景:对于一些有处理时间要求的请求,当在指定时间内没有完成处理,需要向请求方返回处理失败信息。针对此应用场景,可在Express路由中设置超时熔断器,当处理超时,开启熔断器,通知请求方本次处理请求失败。
上述应用场景可使用如下示例代码应对:
app.get('/circuit',function(req, res, next){ var bt=setTimeout(function () { next('route'); //触发熔断 },3000); //设置熔断时间为3秒 res.breakTimer = bt; next(); },function(req,res,next){ handle2(); next(); },function(req,res,next){ handle3(); clearTimeout(res.breakTimer);//正常执行完毕,取消熔断定时器 }); app.get('/circuit ',function(req,res,next){ if(!res.finished){ //如果还没有响应,启动熔断 //返回给调用者熔断信息 res.send("breakCondition is true, notify the invoker."); } });
本文介绍了Express框架中路由和路由器的概念、结构和特点,并针对典型应用场景归纳了REST、AOP、责任链、熔断器四种应用模式,可用于应用开发中的一些常用场景。
http://expressjs.com/
http://www.runoob.com/
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。