nodejs开发——express路由与中间件
路由
通常HTTP URL的格式是这样的:
http://host[:port][path]
http表示协议。
host表示主机。
port为端口,可选字段,不提供时默认为80。
path指定请求资源的URI(Uniform Resource Identifier,统一资源定位符),如果URL中没有给出path,一般会默认成“/”(通常由浏览器或其它HTTP客户端完成补充上)。
所谓路由,就是如何处理HTTP请求中的路径部分。比如“http://xxx.com/users/profile”这个URL,路由将决定怎么处理/users/profile这个路径。
来回顾我们在Node.js开发入门——Express安装与使用中提供的express版本的HelloWorld代码:
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); }); app.listen(8000, function () { console.log('Hello World is listening at port 8000'); });
上面代码里的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"htmlcode">// 匹配任何路径中含有 a 的路径: app.get(/a/, function(req, res) { res.send('/a/'); }); // 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等 app.get(/.*fly$/, function(req, res) { res.send('/.*fly$/'); });路由句柄
可以为请求处理提供多个回调函数,其行为类似 中间件。唯一的区别是这些回调函数有可能调用 next('route') 方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合,如下所示.
使用一个回调函数处理路由:
app.get('/example/a', function (req, res) { res.send('Hello from A!'); });使用多个回调函数处理路由(记得指定 next 对象):
app.get('/example/b', function (req, res, next) { console.log('response will be sent by the next function ...'); next(); }, function (req, res) { res.send('Hello from B!'); });使用回调函数数组处理路由:
var cb0 = function (req, res, next) { console.log('CB0'); next(); } var cb1 = function (req, res, next) { console.log('CB1'); next(); } var cb2 = function (req, res) { res.send('Hello from C!'); } app.get('/example/c', [cb0, cb1, cb2]);混合使用函数和函数数组处理路由:
var cb0 = function (req, res, next) { console.log('CB0'); next(); } var cb1 = function (req, res, next) { console.log('CB1'); next(); } app.get('/example/d', [cb0, cb1], function (req, res, next) { console.log('response will be sent by the next function ...'); next(); }, function (req, res) { res.send('Hello from D!');METHOD可以是GET、POST等HTTP方法的小写,例如app.get,app.post。path部分呢,既可以是字符串字面量,也可以是正则表达式。最简单的例子,把前面代码里的app.get()调用的一个参数'/'修改为'*',含义就不一样。改动之前,只有访问“http://localhost:8000”或“http://localhost:8000/”这种形式的访问才会返回“Hello World!”,而改之后呢,像“http://localhost:8000/xxx/yyyy.zz”这种访问也会返回“Hello World!”。
使用express构建Web服务器时,很重要的一部分工作就是决定怎么响应针对某个路径的请求,也即路由处理。
最直接的路由配置方法,就是调用app.get()、app.post()一条一条的配置,不过对于需要处理大量路由的网站来讲,这会搞出人命来的。所以呢,我们实际开发中需要结合路由参数(query string、正则表达式、自定义的参数、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应用如下:
var options = { 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中间件来处理,对应的HTTP URI为“/”。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实例:
var router = express.Router([options]);然后你就可以像使用app一样使用router:
// invoked for any requests passed to this router router.use(function(req, res, next) { // .. some logic here .. like any other middleware next(); }); // will handle any request that ends in /events // depends on where the router is "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的内容:
var express = require('express'); var router = express.Router(); /* GET home page. */ 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的代码:
var routes = 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){ //some code } exports.login = function(req, res){ //some code }上面的代码(假定在users.js文件里)直接使用exports来导出。当使用exports来导出时,你设置给exports的属性和方法,实际上都是module.exports的。这个模块最终导出的是module.exports对象,你使用类似“exports.signup”这种形式设置的方法或属性,调用方在require后都可以直接使用。
使用users模块的代码可能是这样的:
var express = require('express'); var app = express(); ... var users = 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"htmlcode">
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('hello world'); }); app.post('/', function(req, res){ res.send('hello world'); });4. Express的Router对象
当express实例的路由越来越多的时候,最好把路由分类独立出去,express的实例(app) 能更好的处理其他逻辑流程。Express的Router对象是一个简化的 app实例,只具有路由相关的功能,包括use, http verbs等等。最后这个Router再通过app的use挂载到app的相关路径下。
var express = require('express'); var app = express(); var router = express.Router(); // simple logger for this router's requests // all requests to this router will first hit this middleware router.use(function(req, res, next) { console.log('%s %s %s', req.method, req.url, req.path); next(); }); // this will only be invoked if the path ends in /bar router.use('/bar', function(req, res, next) { // ... maybe some additional /bar logging ... next(); }); // always invoked router.use(function(req, res, next) { res.send('Hello World'); }); app.use('/foo', router); app.listen(3000);router的路由必须通过app.use和app.verbs 挂载到app上才能被响应。所以上述代码,只有在app捕捉到 /foo路径上的路由时,才能router中定义的路由,虽然router中有针对 '/' 的路由,但是被app中的路由给覆盖了。
附:app.verbs和app.use的路由路径区别:
先看一段测试代码:
var express = require('express'); var app = express(); var router = 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访问的方法。以下说明:Mounting a middleware at a path will cause the middleware function to be executed whenever the base of the requested path matches the path.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇:jQuery插件FusionCharts实现的Marimekko图效果示例【附demo源码】