咱们已经基于Express写了HelloWorld示例,还使用express generator工具建立了一个HelloExpress项目,但有一些代码一直没有好好解释,这是由于它们牵涉到路由和中间件等概念,三言两语说不清楚,因此我专门用一篇文章来说路由和中间件。javascript
一般HTTP URL的格式是这样的:css
http表示协议。java
host表示主机。node
port为端口,可选字段,不提供时默认为80。正则表达式
path指定请求资源的URI(Uniform Resource Identifier,统一资源定位符),若是URL中没有给出path,通常会默认成“/”(一般由浏览器或其它HTTP客户端完成补充上)。express
所谓路由,就是如何处理HTTP请求中的路径部分。好比“http://xxx.com/users/profile”这个URL,路由将决定怎么处理/users/profile这个路径。npm
来回顾咱们在Node.js开发入门——Express安装与使用中提供的express版本的HelloWorld代码:json
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的第二个参数所表明的函数来处理。api
express对象能够针对常见的HTTP方法指定路由,使用下面的方法:
app.METHOD(path, callback [, callback ...])
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(); })
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”就能够了。
Express还提供了一个叫作Router的对象,行为很像中间件,你能够把Router直接传递给app.use,像使用中间件那样使用Router。另外你还可使用router来处理针对GET、POST等的路由,也能够用它来添加中间件,总之你能够将Router看做一个微缩版的app。
下面的代码建立一个Router实例:
var router = express.Router([options]);
而后你就能够像使用app同样使用router(代码来自http://expressjs.com/4x/api.html#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); ...
好啦,有了前面介绍的内容,再来看express生成的应用结构和代码,问题不大了。如今咱们总结一下HelloExpress应用。
express会按照特定的定目录结构建立应用。
app.js的代码以下,再回顾一下:
var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var routes = require('./routes/index'); var users = require('./routes/users'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', routes); app.use('/users', users); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handlers // development error handler // will print stacktrace if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); }); } // production error handler // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); module.exports = app;
在看上面的代码,应该没有压力了。只有一处,配置模板引擎的两行代码,后面隐藏了神秘的故事,下一回咱再说了。
其它文章: