Express學(xué)習(xí) - 路由

Express學(xué)習(xí) - hello world,知道了Express是在Node.js的基礎(chǔ)之上對(duì)Node.js的http模塊更方便的封裝,利用express創(chuàng)建一個(gè)server,在響應(yīng)各種http/https請(qǐng)求上,代碼實(shí)現(xiàn)相對(duì)精簡(jiǎn)了很多,看起來(lái)確是也直觀了許多。
下面是剛開始學(xué)習(xí)express時(shí),實(shí)現(xiàn)的一個(gè)hello world:

var express = require('express') ;
var app = express() ; //得到express實(shí)例,類似 new express()

app.get('/',function(req,res) {
    res.send('hello world!') ;
}) ;

var server = app.listen(8081,function() {
    var host = server.address().address ;
    var port = server.address().port ;
    console.log('server has started at http:// %s:%s', host,port) ; 
}) ;

上面的代碼,只能攔截/請(qǐng)求,其他請(qǐng)求都不能得到處理,比如/start等,所以這個(gè)servlet功能還不完善,可以使用express的中間件注冊(cè)函數(shù)use來(lái)對(duì)缺失的handle進(jìn)行處理。如:

var express = require('express') ;
var app = express() ;

app.get('/',function(req,res) {
    res.send('hello world!') ;
}) ;

//默認(rèn)handle
app.use(function(req,res) {
    console.log(req,res) ;
    res.send('default hander.') ;
}) ;

var server = app.listen(8081,function() {
    var host = server.address().address ;
    var port = server.address().port ;
    console.log('server has started at http://%s:%s', host,port) ; 
}) ;

這樣,如果沒(méi)有一個(gè)handle能處理這個(gè)請(qǐng)求,則交給默認(rèn)handle來(lái)處理。這是一個(gè)兜底的handle 。這樣就不會(huì)出現(xiàn)Cannot GET /start 錯(cuò)誤了。

中間件注冊(cè)函數(shù)use()
express的中間件的作用更像是spring的aop,類似一個(gè)切面,在我理解,express的中間件(Middleware) 也有前置和后置的區(qū)分,比如上面的例子就可以算作是一個(gè)后置通知。

在express中,中間件是一個(gè)處理函數(shù),例如:

app.use(function(req,res,next) {
    res.send('404,unknown request') ;
    // next() ;
}) ;

中間件函數(shù)接收三個(gè)參數(shù):

  • request對(duì)象 ;
  • response對(duì)象 ;
  • next回調(diào)函數(shù); 它具有傳遞性,一個(gè)中間件執(zhí)行完畢,可以將參數(shù)(req,res)傳遞給下一個(gè)中間件執(zhí)行,它們是串行的。直到調(diào)用結(jié)束,否則會(huì)一直調(diào)用下去。這是express/lib/router/route.js 中關(guān)于next實(shí)現(xiàn)的一段代碼:
//express router next 源碼實(shí)現(xiàn)
/**
 * dispatch req, res into this route
 * @private
 */

Route.prototype.dispatch = function dispatch(req, res, done) {
  var idx = 0;
  var stack = this.stack;
  if (stack.length === 0) {
    return done();
  }

  var method = req.method.toLowerCase();
  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = this;

  next(); 

  function next(err) {
    if (err && err === 'route') {
      return done();
    }

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (layer.method && layer.method !== method) {
      return next(err);
    }

    if (err) {
      layer.handle_error(err, req, res, next);
    } else {
      layer.handle_request(req, res, next);
    }
  }
};

使用use注冊(cè)的中間件都會(huì)被保存到當(dāng)前路由對(duì)象的stack然后順序取出執(zhí)行,如:

/**

 * Initialize `Route` with the given `path`,

 *

 * @param {String} path

 * @public

 */

function Route(path) {

this.path = path;

this.stack = [];

debug('new %s', path);

// route handlers for various http methods

this.methods = {};

}

next函數(shù)中,參數(shù)用于來(lái)handle異常,如果傳遞給next函數(shù)一個(gè)有效參數(shù)('route'除外),則標(biāo)識(shí)這是一個(gè)錯(cuò)誤handle,從而進(jìn)入錯(cuò)誤處理流程,而且該錯(cuò)誤會(huì)一直傳遞下去,一直拋給最后一個(gè)中間件,如果沒(méi)有手動(dòng)捕獲該錯(cuò)誤的情況下。
如果沒(méi)有顯式的將參數(shù)傳遞給next() 函數(shù),則表示調(diào)用下一個(gè)中間件。

var express = require('express') ;

var app = express() ;


app.get('/',function(req,res) {
    res.send('Helo, World') ;
}) ;

app.use(function(req,res,next) {
    console.log('Time : ',Date.now()) ;
    next() ;// continue
}) ;

app.use(function(req,res) {
    res.send('404,unknown request') ;
    // next() ;
}) ;


app.listen(8081,function() {
    console.log('server has started.') ;
}) ;

每次請(qǐng)求都會(huì)首先經(jīng)過(guò)
app.use(function(req,res,next) { console.log('Time : ',Date.now()) ; next() ;// continue }) ; 中間件打印當(dāng)前調(diào)用時(shí)間,然后進(jìn)入
app.get('/',function(req,res) { res.send('Helo, World') ; }) ;

app.use(function(req,res) { res.send('404,unknown request') ; // next() ; }) ;
這跟Filter很像。命令行窗口輸出:

server has started.
Time :  1477445808204
Time :  1477445877547
Time :  1477445877764

這些中間件的執(zhí)行順序和代碼的順序是一致的,例如:

var express = require('express') ;

var app = express() ;


app.get('/',function(req,res) {
    res.send('Helo, World') ;
}) ;

app.use(function(req,res,next) {
    console.log('pre fun1') ;
    next() ;// continue
}) ;

app.use(function(req,res,next) {
    console.log('Time : ',Date.now()) ;
    next() ;// continue
}) ;

app.use(function(req,res) {
    res.send('404,unknown request') ;
    // next() ;
}) ;


app.listen(8081,function() {
    console.log('server has started.') ;
}) ;

輸出:

server has started.
pre fun1
Time :  1477446103655
pre fun1
Time :  1477446149579
pre fun1
Time :  1477446149797

下一個(gè)中間件的調(diào)用完全依賴next,如果沒(méi)有next調(diào)用,則request和response對(duì)象將不會(huì)繼續(xù)傳遞下去。這個(gè)next 有點(diǎn)像java linklist的next[Entry] 對(duì)象,指向下一個(gè)Entry 對(duì)象。
之前使用get掛載中間件的方式用use也可以,使用use掛載中間件函數(shù)然后在use函數(shù)內(nèi)部對(duì)資源請(qǐng)求path進(jìn)行判斷,這適合針對(duì)某一類資源的綜合處理,如:

app.use(function(req,res,next) {
    if (req.url === '/') {
        console.log('/') ;
        res.send('welcom visit xxx ') ;
    }else {
        next() ;
    }
}) ;

app.use(function(req,res,next) {
    //
    if (req.url === '/about') {
        console.log('/about') ;
        res.send('i\\'m a cc') ;
    }else {
        next() ;
    }
}) ;
...........
app.use(function(req,res) {
    res.send('404,unknown request') ;
    // next() ;
}) ;

這樣,也可以對(duì)//about 不同的請(qǐng)求進(jìn)行有效的處理。use函數(shù)也可以像get/post..等http方法一樣使用,如:

app.use('/',function(req,res,next) {
    if (req.url === '/') {
        console.log('/') ;
        res.send('welcom visit xxx ') ;
    }else {
        next() ;
    }
}) ;



app.use('/about',function(req,res,next) {
    //
    if (req.url === '/about') {
        console.log('/about') ;
        res.send('i\\'m a cc') ;
    }else {
        next() ;
    }
}) ;

下面的中間件永遠(yuǎn)也執(zhí)行不到,因?yàn)檎?qǐng)求路徑不能匹配到/about,只能匹配/about/about。可以使用use有針對(duì)性的對(duì)某一個(gè)路由進(jìn)行切面處理。例如:

app.use('/blog',function(req,res,next){
   //do something
  next() ;
  });

另外,express的all函數(shù)也可以做到use的功能,它是use的別名實(shí)現(xiàn)。在/route.js中實(shí)現(xiàn)是這樣的:

Route.prototype.all = function all() {

var handles = flatten(slice.call(arguments));

for (var i = 0; i < handles.length; i++) {

var handle = handles[i];

if (typeof handle !== 'function') {

var type = toString.call(handle);

var msg = 'Route.all() requires callback functions but got a ' + type;

throw new TypeError(msg);

}

var layer = Layer('/', {}, handle);

layer.method = undefined;

this.methods._all = true;

this.stack.push(layer);

}

return this;

};

use都一樣的操作,都是將中間件函數(shù)push到 Router對(duì)象的 stack數(shù)組中,方便next函數(shù) 分發(fā)。

express 的use函數(shù)在沒(méi)有指定path的情況會(huì)默認(rèn)將中間件函數(shù)掛載到/ ,利用這個(gè)特性解決了默認(rèn)請(qǐng)求攔截的問(wèn)題。

express 路由支持
路由, 由一個(gè)資源path和一個(gè)或若干個(gè)資源處理函數(shù)組成。在express中它的結(jié)構(gòu)定義為:

app.METHOD(path, [callback...], callback)

這里的app是express的一個(gè)實(shí)例,METHODHTTP的METHOD含義是一致的。

  • path 為URI. -- 統(tǒng)一資源標(biāo)識(shí),例如: /blog/about

URIURL

  • URI(Universal Resource Identifier/統(tǒng)一資源標(biāo)識(shí)) 它是指一個(gè)資源域或者某一確定的資源, 它著重強(qiáng)調(diào)資源。例如: /example/blog/page1/
  • URL(Uniform Resource Locator/統(tǒng)一資源定位器) 它完整的描述了整個(gè)資源的絕對(duì)路徑,它包含以下三方面內(nèi)容:
  1. scheme: http:// 、https://、ftp://等
  2. address: 例如: www.lxweimin.com
  3. 資源定位: /notebooks/6732245

從這個(gè)意義上來(lái)說(shuō),URL是URI的一個(gè)子類,URI更像是URL的一個(gè)規(guī)范,URL更具體更準(zhǔn)確的指定某一資源位置。在java中,通過(guò)Request對(duì)象獲取URL信息和URI信息是這樣的,例如這樣一個(gè)請(qǐng)求: http:localhost:8080/webapp/xx/abc.do?type=42 :

request.getRequestURI() ; ///webapp/xx/abc.do
request.getRequestURL() ;//http:localhost:8080/webapp/xx/abc.do

request.getQueryString() ;// 返回請(qǐng)求參數(shù)鍵值對(duì)。type = 42
  • callback 為處理請(qǐng)求資源的回調(diào)函數(shù),可以是一個(gè)中間件組,也可以組合使用。

express路由針對(duì) getpost等http行為有不同的函數(shù)支持如:

router.get('/',function(req,res) {
    res.send('hello welcom') ;
}) ;


router.post('/',function(req,res) {
    res.send(' post : hello welcom') ;
}) ;

Express 定義了如下和 HTTP 請(qǐng)求對(duì)應(yīng)的路由方法:

get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify
, unsubscribe, patch, search, 和 connect 。

上面類是m-searchhttp方法需要使用括號(hào)記法,如:

app['m-search']('/',function(req,res) {
    res.send('xxxxxxxxxx') ;
}) ;

因?yàn)樵趈avascript語(yǔ)法中,讀寫不符合javascript變量命名規(guī)則[1]的屬性時(shí),需要使用類似數(shù)組訪問(wèn)方式,如:

var obj = {} ;
obj['mid-school'] = 'xxx中學(xué)' ;

//訪問(wèn)也需要如此
console.log(obj['mid-school']) ;

express中一個(gè)路由可以被多個(gè)回調(diào)函數(shù)所處理,如:

app.use('/',[f1,f2,f3],function(req,res,next) {
    //
    if (req.url === '/about') {
        console.log('/about') ;
        res.send('well ...') ;
    }else {
        // console.log('req.url',req.url) ;
        next() ;
    }
}) ;


function f1(eq,res,next) {
    //  
    console.log('----f1') ;
    next() ;
}


function f2(eq,res,next) {
    //  
    console.log('----f2') ;
    next() ;
}

function f3(eq,res,next) {
    //  
    console.log('----f3') ;
    next() ;
}

不過(guò)需要注意next 關(guān)聯(lián),業(yè)務(wù)沒(méi)有處理完畢之前不能缺失next回調(diào)函數(shù),否則,業(yè)務(wù)流將中斷。例如, f3函數(shù)不將req,res傳遞下去,則請(qǐng)求一直停在該函數(shù)中,既不能結(jié)束請(qǐng)求,也不能響應(yīng)該請(qǐng)求,這是很致命的。

一般地,請(qǐng)求不同的資源和具體的業(yè)務(wù)息息相關(guān),所以,需要把這種業(yè)務(wù)邏輯盡可能的抽象出來(lái),就像最開始學(xué)習(xí)編寫路由支持的 dispatcher.js, 在express中,可以使用use注冊(cè)一個(gè)路由對(duì)象(Router)到某一資源域上 ,利用這個(gè)可以把業(yè)務(wù)放在這個(gè)對(duì)象中,然后使用use 將不同的分類的業(yè)務(wù)操作掛載到不同的資源虛擬目錄,如:

//blog.js
var express = require('express') ;

var route = express.Router() ;

route.use(function(req,res,next) {
    console.log('path ',req.url) ;
    next() ;
}) ;

route.get('/',function(req,res) {
    console.log(' blog/') ;
    res.send(req.url) ;
}) ;

route.get('/about',function(req,res){
    console.log(' blog/about') ;
    res.send('/about') ;
}) ;

module.exports = route ;
//index.js
var blog = require('./blog') ;

var express = require('express') ;

var app = express() ;


app.use('/blog',blog) ;

app.listen(8081,function(){
    console.log('server has started.') ;
}) ;

如此將blog.js中所有的請(qǐng)求都映射到/blog域之下了。

express.Router實(shí)例是一個(gè)完整的中間件和路由系統(tǒng), 相對(duì)index.js來(lái)說(shuō),blog.js已經(jīng)變成了一個(gè)路由模塊。在index.js中,將這個(gè)路由模塊包含的所有路由掛載到/blog上,中間件的調(diào)用模式照舊。

使用app.route()還可以完成一個(gè)鏈接路由調(diào)用,像這樣:

app.route('/blog').get(
    function(req,res,next){
        res.send('/blog get method.') ;
    }).post(function(req,res,next){
        res.send('/blog post method.') ;
    }).put(function(req,res,next){
        res.send('/blog put method.') ;
    }) ;

這可以使同一資源響應(yīng)不同的http方法。而不用重復(fù)地將一類資源掛載到不同http方法上。這個(gè)route函數(shù)和use函數(shù)功能差不多,都是向express中間件數(shù)組容器push掛載中間件函數(shù),然后等待next 調(diào)用。

在express路由支持中,支持更復(fù)雜的模式匹配,可以使用javascript正則表達(dá)式對(duì)象來(lái)匹配資源請(qǐng)求路由。

express使用use對(duì)錯(cuò)誤路由的支持
通過(guò)對(duì)函數(shù)的簽名來(lái)約定錯(cuò)誤路由中間件,以此捕獲錯(cuò)誤并處理,將blog.js修改如下:

var express = require('express') ;

var route = express.Router() ;

route.use(function(req,res,next) {
    console.log('path ',req.url) ;
    next() ;
}) ;

//訪問(wèn)/blog 主動(dòng)拋一個(gè)error實(shí)例,然后express會(huì)自動(dòng)將路由轉(zhuǎn)到error處理中間件
route.get('/',function(req,res) {
    console.log(' blog/') ;
    throw Error('error') ;
    // res.send(req.url) ;
}) ;

route.get('/about',function(req,res){
    console.log(' blog/about') ;
    res.send('/about') ;
}) ;


//express error 中間件
route.use(function(error,req,res,next) {
    if (error) { //如果有錯(cuò)誤,輸出錯(cuò)誤。
        res.send('error...' + error) ;
    }else { //沒(méi)有錯(cuò)誤執(zhí)行下一個(gè)中間件
        next() ;
    }
}) ;
module.exports = route ;

express的set函數(shù)
使用express#set函數(shù)可以進(jìn)行一些項(xiàng)目資源設(shè)置,完成諸如setting name to value的工作,如指定前端頁(yè)面模版渲染引擎、指定html模板文件目錄:

app.set('view engine','jade') ; //渲染引擎
app.set('views', __dirname + '/views') ; //模板目錄

也可以設(shè)置某些環(huán)境變量的值, 如:

app.set('foo', true) ;

上面的操作等同于app.enable('foo') 。更多set操作,在這里 ;

express 也是 class: Events 的實(shí)現(xiàn)類,默認(rèn)掛載中間件的時(shí)候會(huì)被mount事件函數(shù)監(jiān)聽到 ,如: app.on('mount', callback(parent))

app.on('mount',function(parent) {
    console.log('mount call',parent) ;
}) ;

END


  1. 一個(gè) JavaScript 標(biāo)識(shí)符必須以字母、下劃線(_)或者美元符號(hào)($)開頭;后續(xù)的字符也可以是數(shù)字(0-9)。因?yàn)?JavaScript 語(yǔ)言是區(qū)分大小寫的,這里所指的字母可以是“A”到“Z”(大寫的)和“a”到“z”(小寫的)。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容