1. 程式人生 > >Node js開發入門—Express裡的路由和中介軟體

Node js開發入門—Express裡的路由和中介軟體

                     

我們已經基於Express寫了HelloWorld示例,還使用express generator工具建立了一個HelloExpress專案,但有一些程式碼一直沒有好好解釋,這是因為它們牽涉到路由和中介軟體等概念,三言兩語說不清楚,所以我專門用一篇文章來講路由和中介軟體。

路由

通常HTTP URL的格式是這樣的:

http表示協議。

host表示主機。

port為埠,可選欄位,不提供時預設為80。

path指定請求資源的URI(Uniform Resource Identifier,統一資源定位符),如果URL中沒有給出path,一般會預設成“/”(通常由瀏覽器或其它HTTP客戶端完成補充上)。

所謂路由,就是如何處理HTTP請求中的路徑部分。比如“

http://xxx.com/users/profile”這個URL,路由將決定怎麼處理/users/profile這個路徑。

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

”這種訪問也會返回“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...])
  • 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

現在看上面的程式碼,應該沒有壓力了。只有一處,配置模板引擎的兩行程式碼,後面隱藏了神祕的故事,下一回咱再說了。

其它文章: