Node js開發入門—Express裡的路由和中介軟體
我們已經基於Express寫了HelloWorld示例,還使用express generator工具建立了一個HelloExpress專案,但有一些程式碼一直沒有好好解釋,這是因為它們牽涉到路由和中介軟體等概念,三言兩語說不清楚,所以我專門用一篇文章來講路由和中介軟體。
路由
通常HTTP URL的格式是這樣的:
http表示協議。
host表示主機。
port為埠,可選欄位,不提供時預設為80。
path指定請求資源的URI(Uniform Resource Identifier,統一資源定位符),如果URL中沒有給出path,一般會預設成“/”(通常由瀏覽器或其它HTTP客戶端完成補充上)。
所謂路由,就是如何處理HTTP請求中的路徑部分。比如“
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');});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
上面程式碼裡的app.get()呼叫,實際上就為我們的網站添加了一條路由,指定“/”這個路徑由get的第二個引數所代表的函式來處理。
express物件可以針對常見的HTTP方法指定路由,使用下面的方法:
app.METHOD(path, callback [, callback ...])
- 1
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
使用express構建Web伺服器時,很重要的一部分工作就是決定怎麼響應針對某個路徑的請求,也即路由處理。
最直接的路由配置方法,就是呼叫app.get()、app.post()一條一條的配置,不過對於需要處理大量路由的網站來講,這會搞出人命來的。所以呢,我們實際開發中需要結合路由引數(query string、正則表示式、自定義的引數、post引數)來減小工作量提高可維護性。更詳細的資訊,參考http://expressjs.com/guide/routing.html。
中介軟體
Express裡有個中介軟體(middleware)的概念。所謂中介軟體,就是在收到請求後和傳送響應之前這個階段執行的一些函式。
要在一條路由的處理鏈上插入中介軟體,可以使用express物件的use方法。該方法原型如下:
app.use([path,] function [, function...])
- 1
當app.use沒有提供path引數時,路徑預設為“/”。當你為某個路徑安裝了中介軟體,則當以該路徑為基礎的路徑被訪問時,都會應用該中介軟體。比如你為“/abcd”設定了中介軟體,那麼“/abcd/xxx”被訪問時也會應用該中介軟體。
中介軟體函式的原型如下:
function (req, res, next)
- 1
第一個引數是Request物件req。第二個引數是Response物件res。第三個則是用來驅動中介軟體呼叫鏈的函式next,如果你想讓後面的中介軟體繼續處理請求,就需要呼叫next方法。
給某個路徑應用中介軟體函式的典型呼叫是這樣的:
app.use('/abcd', function (req, res, next) { console.log(req.baseUrl); next();})
- 1
- 2
- 3
- 4
app.static中介軟體
Express提供了一個static中介軟體,可以用來處理網站裡的靜態檔案的GET請求,可以通過express.static訪問。
express.static的用法如下:
express.static(root, [options])
- 1
一個典型的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));
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
上面這段程式碼將當前路徑下的public目錄作為靜態檔案,並且為Cache-Control頭部的max-age選項為1天。還有其它一些屬性,請對照express.static的文件來理解。
使用express建立的HelloExpress專案的app.js檔案裡有這樣一行程式碼:
app.use(express.static(path.join(__dirname, 'public')));
- 1
這行程式碼將HelloExpress目錄下的public目錄作為靜態檔案交給static中介軟體來處理,對應的HTTP URI為“/”。path是一個Node.js模組,__dirname是Node.js的全域性變數,指向當前執行的js指令碼所在的目錄。path.join()則用來拼接目錄。
app.use('/static', express.static(path.join(__dirname, 'public')));
- 1
Router
Express還提供了一個叫做Router的物件,行為很像中介軟體,你可以把Router直接傳遞給app.use,像使用中介軟體那樣使用Router。另外你還可以使用router來處理針對GET、POST等的路由,也可以用它來新增中介軟體,總之你可以將Router看作一個微縮版的app。
下面的程式碼建立一個Router例項:
var router = express.Router([options]);
- 1
// invoked for any requests passed to this routerrouter.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) { // ..});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
定義了router後,也可以將其作為中介軟體傳遞給app.use:
app.use('/events', router);
- 1
上面這種用法,會針對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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
index.js建立了一個Router例項,然後呼叫router.get為“/”路徑應用了路由函式。最後呢使用module.exports將Router物件匯出。
下面是app.js裡引用到index.js的程式碼:
var routes = require('./routes/index');...app.use('/', routes);
- 1
- 2
- 3
第一處,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}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面的程式碼(假定在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
- 2
- 3
- 4
- 5
- 6
- 7
HelloExpress再回首
好啦,有了前面介紹的內容,再來看express生成的應用結構和程式碼,問題不大了。現在我們總結一下HelloExpress應用。
目錄結構
express會按照特定的定目錄結構建立應用。
- bin目錄,裡面就一個www檔案,其實還是個js指令碼檔案,使用npm start啟動網站時會呼叫www。你可以看控制檯輸出來確認。
- public目錄,裡面放了一些靜態檔案,預設生成的專案,只有stylesheets下有一個style.css檔案。javascripts子目錄可以放js檔案,images子目錄放圖片。
- routes目錄內放的是路由模組,前面分析過了。你可以在這裡配置你網站的路由。
- views目錄放的HTML模板檔案。下次我們再聊它。
- node_modules目錄是根據package.json生成的,package.json由express生成
- app.js,應用入口
app.js
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 setupapp.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 handlerapp.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err);});// error handlers// development error handler// will print stacktraceif (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 userapp.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} });});module.exports = app;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
現在看上面的程式碼,應該沒有壓力了。只有一處,配置模板引擎的兩行程式碼,後面隱藏了神祕的故事,下一回咱再說了。
其它文章: