Node.js開發入門—Express裏的路由和中間件

咱們已經基於Express寫了HelloWorld示例,還使用express generator工具建立了一個HelloExpress項目,但有一些代碼一直沒有好好解釋,這是由於它們牽涉到路由和中間件等概念,三言兩語說不清楚,因此我專門用一篇文章來說路由和中間件。javascript

路由

一般HTTP URL的格式是這樣的:css

http://host[:port][path]html

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();
})

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(代碼來自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);
...

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 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;

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

其它文章:

相關文章
相關標籤/搜索