nodejs开发——express路由与中间件
路由
通常HTTPURL的格式是这样的:
http://host[:port][path]
http表示协议。
host表示主机。
port为端口,可选字段,不提供时默认为80。
path指定请求资源的URI(UniformResourceIdentifier,统一资源定位符),如果URL中没有给出path,一般会默认成“/”(通常由浏览器或其它HTTP客户端完成补充上)。
所谓路由,就是如何处理HTTP请求中的路径部分。比如“http://xxx.com/users/profile”这个URL,路由将决定怎么处理/users/profile这个路径。
来回顾我们在Node.js开发入门——Express安装与使用中提供的express版本的HelloWorld代码:
varexpress=require('express'); varapp=express(); app.get('/',function(req,res){ res.send('HelloWorld!'); }); app.listen(8000,function(){ console.log('HelloWorldislisteningatport8000'); });
上面代码里的app.get()调用,实际上就为我们的网站添加了一条路由,指定“/”这个路径由get的第二个参数所代表的函数来处理。
express对象可以针对常见的HTTP方法指定路由,使用下面的方法:
app.METHOD(path,callback[,callback...])
路由路径
使用字符串的路由路径示例:
//匹配根路径的请求 app.get('/',function(req,res){ res.send('root'); }); //匹配/about路径的请求 app.get('/about',function(req,res){ res.send('about'); }); //匹配/random.text路径的请求 app.get('/random.text',function(req,res){ res.send('random.text'); }); 使用字符串模式的路由路径示例: //匹配acd和abcd app.get('/ab?cd',function(req,res){ res.send('ab?cd'); }); //匹配abcd、abbcd、abbbcd等 app.get('/ab+cd',function(req,res){ res.send('ab+cd'); }); //匹配abcd、abxcd、abRABDOMcd、ab123cd等 app.get('/ab*cd',function(req,res){ res.send('ab*cd'); }); //匹配/abe和/abcde app.get('/ab(cd)?e',function(req,res){ res.send('ab(cd)?e'); });
字符?、+、*和()是正则表达式的子集,-和.在基于字符串的路径中按照字面值解释。
使用正则表达式的路由路径示例:
//匹配任何路径中含有a的路径: app.get(/a/,function(req,res){ res.send('/a/'); }); //匹配butterfly、dragonfly,不匹配butterflyman、dragonflyman等 app.get(/.*fly$/,function(req,res){ res.send('/.*fly$/'); });
路由句柄
可以为请求处理提供多个回调函数,其行为类似中间件。唯一的区别是这些回调函数有可能调用next('route')方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合,如下所示.
使用一个回调函数处理路由:
app.get('/example/a',function(req,res){ res.send('HellofromA!'); });
使用多个回调函数处理路由(记得指定next对象):
app.get('/example/b',function(req,res,next){ console.log('responsewillbesentbythenextfunction...'); next(); },function(req,res){ res.send('HellofromB!'); });
使用回调函数数组处理路由:
varcb0=function(req,res,next){ console.log('CB0'); next(); } varcb1=function(req,res,next){ console.log('CB1'); next(); } varcb2=function(req,res){ res.send('HellofromC!'); } app.get('/example/c',[cb0,cb1,cb2]);
混合使用函数和函数数组处理路由:
varcb0=function(req,res,next){ console.log('CB0'); next(); } varcb1=function(req,res,next){ console.log('CB1'); next(); } app.get('/example/d',[cb0,cb1],function(req,res,next){ console.log('responsewillbesentbythenextfunction...'); next(); },function(req,res){ res.send('HellofromD!');
METHOD可以是GET、POST等HTTP方法的小写,例如app.get,app.post。path部分呢,既可以是字符串字面量,也可以是正则表达式。最简单的例子,把前面代码里的app.get()调用的一个参数'/'修改为'*',含义就不一样。改动之前,只有访问“http://localhost:8000”或“http://localhost:8000/”这种形式的访问才会返回“HelloWorld!”,而改之后呢,像“http://localhost:8000/xxx/yyyy.zz”这种访问也会返回“HelloWorld!”。
使用express构建Web服务器时,很重要的一部分工作就是决定怎么响应针对某个路径的请求,也即路由处理。
最直接的路由配置方法,就是调用app.get()、app.post()一条一条的配置,不过对于需要处理大量路由的网站来讲,这会搞出人命来的。所以呢,我们实际开发中需要结合路由参数(querystring、正则表达式、自定义的参数、post参数)来减小工作量提高可维护性。更详细的信息,参考http://expressjs.com/guide/routing.html。
中间件
Express里有个中间件(middleware)的概念。所谓中间件,就是在收到请求后和发送响应之前这个阶段执行的一些函数。
要在一条路由的处理链上插入中间件,可以使用express对象的use方法。该方法原型如下:
app.use([path,]function[,function...])
当app.use没有提供path参数时,路径默认为“/”。当你为某个路径安装了中间件,则当以该路径为基础的路径被访问时,都会应用该中间件。比如你为“/abcd”设置了中间件,那么“/abcd/xxx”被访问时也会应用该中间件。
中间件函数的原型如下:
function(req,res,next)
第一个参数是Request对象req。第二个参数是Response对象res。第三个则是用来驱动中间件调用链的函数next,如果你想让后面的中间件继续处理请求,就需要调用next方法。
给某个路径应用中间件函数的典型调用是这样的:
app.use('/abcd',function(req,res,next){ console.log(req.baseUrl); next(); })
app.static中间件
Express提供了一个static中间件,可以用来处理网站里的静态文件的GET请求,可以通过express.static访问。
express.static的用法如下:
express.static(root,[options])
第一个参数root,是要处理的静态资源的根目录,可以是绝对路径,也可以是相对路径。第二个可选参数用来指定一些选项,比如maxAge、lastModified等,更多选项的介绍看这里:http://expressjs.com/guide/using-middleware.html#middleware.built-in。
一个典型的express.static应用如下:
varoptions={ dotfiles:'ignore', etag:false, extensions:['htm','html'], index:false, maxAge:'1d', redirect:false, setHeaders:function(res,path,stat){ res.set('x-timestamp',Date.now()); } } app.use(express.static('public',options));
上面这段代码将当前路径下的public目录作为静态文件,并且为Cache-Control头部的max-age选项为1天。还有其它一些属性,请对照express.static的文档来理解。
使用express创建的HelloExpress项目的app.js文件里有这样一行代码:
app.use(express.static(path.join(__dirname,'public')));
这行代码将HelloExpress目录下的public目录作为静态文件交给static中间件来处理,对应的HTTPURI为“/”。path是一个Node.js模块,__dirname是Node.js的全局变量,指向当前运行的js脚本所在的目录。path.join()则用来拼接目录。
有了上面的代码,你就可以在浏览器里访问“http://localhost:3000/stylesheets/style.css”。我们做一点改动,把上面的代码修改成下面这样:
app.use('/static',express.static(path.join(__dirname,'public')));
上面的代码呢,针对/static路径使用static中间件处理public目录。这时你再用浏览器访问“http://localhost:3000/stylesheets/”就会看到一个404页面,将地址换成“http://localhost:3000/static/stylesheets/style.css”就可以了。
Router
Express还提供了一个叫做Router的对象,行为很像中间件,你可以把Router直接传递给app.use,像使用中间件那样使用Router。另外你还可以使用router来处理针对GET、POST等的路由,也可以用它来添加中间件,总之你可以将Router看作一个微缩版的app。
下面的代码创建一个Router实例:
varrouter=express.Router([options]);
然后你就可以像使用app一样使用router:
//invokedforanyrequestspassedtothisrouter router.use(function(req,res,next){ //..somelogichere..likeanyothermiddleware next(); }); //willhandleanyrequestthatendsin/events //dependsonwheretherouteris"use()'d" router.get('/events',function(req,res,next){ //.. });
定义了router后,也可以将其作为中间件传递给app.use:
app.use('/events',router);
上面这种用法,会针对URL中的“/events”路径应用router,你在router对象上配置的各种路由策略和中间件,都会被在合适的时候应用。
路由模块
express工具创建的应用,有一个routes目录,下面保存了应用到网站的Router模块,index.js和user.js。这两个模块基本一样,我们研究一下index.js。
下面是index.js的内容:
varexpress=require('express'); varrouter=express.Router(); /*GEThomepage.*/ router.get('/',function(req,res,next){ res.render('index',{title:'Express'}); }); module.exports=router;
index.js创建了一个Router实例,然后调用router.get为“/”路径应用了路由函数。最后呢使用module.exports将Router对象导出。
下面是app.js里引用到index.js的代码:
varroutes=require('./routes/index'); ... app.use('/',routes);
第一处,require(‘./routes/index')将其作为模块使用,这行代码导入了index.js,并且将index.js导出的router对象保存在变量routes里以供后续使用。注意,上面代码里的routes就是index.js里的router。
第二处代码,把routes作为一个中间件,挂载到了“/”路径上。
模块
前面分析index.js时看到了module.exports的用法。module.exports用来导出一个Node.js模块内的对象,调用者使用require加载模块时,就会获得导出的对象的实例。
我们的index.js导出了Router对象。app.js使用require(‘./routes/index')获取了一个Router实例。
module.exports还有一个辅助用法,即直接使用exports来导出。
exports.signup=function(req,res){ //somecode } exports.login=function(req,res){ //somecode }
上面的代码(假定在users.js文件里)直接使用exports来导出。当使用exports来导出时,你设置给exports的属性和方法,实际上都是module.exports的。这个模块最终导出的是module.exports对象,你使用类似“exports.signup”这种形式设置的方法或属性,调用方在require后都可以直接使用。
使用users模块的代码可能是这样的:
varexpress=require('express'); varapp=express(); ... varusers=require('./routes/users'); app.post('/signup',users.signup); app.post('/login',users.login); ...
1. 什么是router路径,什么是middleware?
我们输入www.baidu.com来访问百度的主页,浏览器会自动转换为http://www.baidu.com:80/(省略一些参数)。http://代表我们同服务器连接使用的是http协议,www.baidu.com代表的是服务器的主机地址,会被我们的pc通过DNS解析为IP地址。80是默认的应用层端口。/即为我们访问的服务器(www.baidu.com)的路径,服务器要对我们访问的这个路径做出响应,采取一定的动作。我们可以把这一过程看做一个路由。
访问的路径‘/'即为router的路径,服务器采取的动作即为middleware,即为一个个特殊的函数。
2.router路径
www.baidu.com/test:路径为/test
www.baidu.com/test?name=1&number=2:路径同样为/test,?后面会被服务器理解传给路径的参数。
3.Middleware
AnExpressapplicationisessentiallyastackofmiddlewarewhichareexecutedserially.(express应用其实就是由一系列顺序执行的Middleware组成。)
Amiddlewareisafunctionwithaccesstotherequestobject(req),theresponseobject(res),andthenextmiddlewareinlineintherequest-responsecycleofanExpressapplication.Itiscommonlydenotedbyavariablenamednext.Eachmiddlewarehasthecapacitytoexecuteanycode,makechangestotherequestandthereponseobject,endtherequest-responsecycle,andcallthenextmiddlewareinthestack.Sincemiddlewareareexecuteserially,theirorderofinclusionisimportant.(中间件其实就是一个访问express应用串入的req,res,nex参数的函数,这个函数可以访问任何通过req,res传入的资源。)
Ifthecurrentmiddlewareisnotendingtherequest-responsecycle,itisimportanttocallnext()topassonthecontroltothenextmiddleware,elsetherequestwillbelefthanging.(如果当前中间件没有完成对网页的res响应,还可以通过next把router留给下一个middleware继续执行)
Withanoptionalmountpath,middlewarecanbeloadedattheapplicationlevelorattherouterlevel.Also,aseriesofmiddlewarefunctionscanbeloadedtogether,creatingasub-stackofmiddlewaresystematamountpoint.
路由的产生是通过HTTP的各种方法(GET,POST)产生的,Middleware可以跟router路径跟特定的HTTP方法绑定,也可以跟所有的方法绑定。
3.1通过express应用的use(all),把Middleware同router路径上的所有HTTP方法绑定:
app.use(function(req,res,next){ console.log('Time:%d',Date.now()); next(); })
3.2通过express应用的http.verb,把Middleware同router路径上的特定的HTTP方法绑定:
app.get('/',function(req,res){ res.send('helloworld'); }); app.post('/',function(req,res){ res.send('helloworld'); });
4. Express的Router对象
当express实例的路由越来越多的时候,最好把路由分类独立出去,express的实例(app)能更好的处理其他逻辑流程。Express的Router对象是一个简化的app实例,只具有路由相关的功能,包括use,httpverbs等等。最后这个Router再通过app的use挂载到app的相关路径下。
varexpress=require('express'); varapp=express(); varrouter=express.Router(); //simpleloggerforthisrouter'srequests //allrequeststothisrouterwillfirsthitthismiddleware router.use(function(req,res,next){ console.log('%s%s%s',req.method,req.url,req.path); next(); }); //thiswillonlybeinvokedifthepathendsin/bar router.use('/bar',function(req,res,next){ //...maybesomeadditional/barlogging... next(); }); //alwaysinvoked router.use(function(req,res,next){ res.send('HelloWorld'); }); app.use('/foo',router); app.listen(3000);
router的路由必须通过app.use和app.verbs挂载到app上才能被响应。所以上述代码,只有在app捕捉到/foo路径上的路由时,才能router中定义的路由,虽然router中有针对'/'的路由,但是被app中的路由给覆盖了。
附:app.verbs和app.use的路由路径区别:
先看一段测试代码:
varexpress=require('express'); varapp=express(); varrouter=express.Router(); app.get('/',function(req,res){ console.log('test1'); }); app.use('/',function(req,res){ console.log('test2'); }); router.get('/',function(req,res){ console.log('test3'); }); app.listen(4000);
输入url:localhost:4000
输出结果:test1
输入url:localhost:4000/hello
输出结果:test2
结论:app.get挂载‘/'的路由只响应跟'/'精确匹配的GET请求。而app.use挂载的'/'的路由响应所有以'/'为起始路由的路由,且不限制HTTP访问的方法。以下说明:Mountingamiddlewareatapathwillcausethemiddlewarefunctiontobeexecutedwheneverthebaseoftherequestedpathmatchesthepath.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。