咱們已經基於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;
在看上面的代碼,應該沒有壓力了。只有一處,配置模板引擎的兩行代碼,後面隱藏了神祕的故事,下一回咱再說了。
其它文章: