express 中間件原理

有時候面試,老是被問到一些自己使用過的技術(shù),但是僅僅限於使用,這個是真沒深入研究過,鄙人能力有限的緣故吧。但是呢,每每被問倒之後下來又會想為什麼不在深入一點呢?(邪惡??之笑)~~;完全可以再多了解一點嘛。

不過怎麼說呢,面試官需要的是一類人才,他也會問到旁系不相干或者在深層次的問題,這點我就覺得有點奇怪了,甲方需要的能力滿足,關(guān)於其他的不會反而會減分,難道不應(yīng)該是,不會不要緊,有時間多學(xué)習(xí)學(xué)習(xí)就好了嗎。非要搞得難倒面試者才甘心。既然這樣的面試官很多,所以能多學(xué)點是一點,高標(biāo)準(zhǔn)要求自己。

那天是阿里的面試官電話面試的,也僅限于溝通,額~~,被阿里電話面試了好多次,都沒結(jié)果,我想說的是遲早我都會進(jìn)來,浪費大家時間幹嘛。

前幾個問題還好,都是回答自己擅長的問題,還算OK。我的站點有一個是花夏集。他是看到這個聯(lián)繫到我的。一上來就問我是不是 花夏集 搞得有點一頭霧水。然後就解釋是怎麼看到我的,找到我的。瞬間很奇怪加驚動。對!就是驚動。居然有人因為看到一個技術(shù)人員的詩集站點從而打電話進(jìn)行面試的??赡苁怯伸哆@個站點時nodejs+express+mongodb做的吧。事實證明確實是因為這樣,他們剛好需要nodejs方面的工程師。

巴拉巴拉...聊了一些,最後聊到koa中間件,這個我不會,koa沒用過,用過express.僅限於參照文檔做的,然後就開始了......對於express深入的確實不了解,聞到中間件事一問三不知,只知道怎麼用,簡單的自己實現(xiàn)原理都不清楚,所以現(xiàn)在才開始寫一篇文章來說明下。也是看了網(wǎng)上的文章解釋,有了進(jìn)一步的了解。

廢話了這麼多,進(jìn)入正題:express中間件原理簡單剖析

學(xué)express首先得會點nodejs吧,先來一段nodejs最基礎(chǔ)的hello world

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');

上面這段代碼來自nodejs的官網(wǎng),非常簡單,就是來一個請求,就用傳給createServer的匿名函數(shù)來處理請求。

繼續(xù)看看Express的代碼

var app = express();
//...中間忽略
http.createServer(app).listen(app.get('port'), function(){
    console.log('Express server listening on port ' + app.get('port'));
});

對比可以看出,執(zhí)行express()后,會返回一個函數(shù),賦值給app,app應(yīng)該是:

function(req,res){//...}

然后請求都會被app這個函數(shù)處理(因為這個app是執(zhí)行express后的結(jié)果)

可以認(rèn)為,在express內(nèi)部,有一個函數(shù)的數(shù)組,暫時叫這個數(shù)組tasks,每來一個請求express內(nèi)部會依次執(zhí)行這個數(shù)組中的函數(shù)(這里說依次并不嚴(yán)謹(jǐn),每個函數(shù)必須滿足一定條件才行,這個后面說),應(yīng)該可以想到,在這個函數(shù)數(shù)組里,每個函數(shù)的簽名應(yīng)該像下面那樣實際上這個就是中間件的精髓所在...噓!

function(req, res){//...}

實際上應(yīng)該是:

function(req, res, next){//...}

這里的next,是指下一個函數(shù)。后面我們會寫一些試驗來體驗一下這個next.

1.導(dǎo)入相關(guān)模塊

2.執(zhí)行過 var app = express() 后,使用app.set 設(shè)置express內(nèi)部的一些參數(shù)(options)使用app.use 來注冊函數(shù),可以簡單的認(rèn)為是向那個tasks的數(shù)組進(jìn)行push操作

3.通過http.createServer 用app來處理請求

試驗1. 向express中注冊自定義函數(shù)

注冊進(jìn)express中的函數(shù):

  1. 長成下面這個樣子
function(req, res, next){
    //...我們自己的邏輯
    next();
}

或者:

app.use(function(err,req,res,next){
    if(err){
        //自己的處理錯誤的邏輯
        console.log(err.message);
        console.log(err.stack);
        res.end('404')  
    }
});
  1. app.use(customerFunc) 要寫在下面兩句的前面
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

一般情況下是這麼使用的,但是對於通用error處理可以放到最後,http.createServer之前

app.use(express.static(path.join(__dirname, '/web/dist')));
// catch 404 and forwarding to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});
// 啟動及端口
http.createServer(app).listen(app.get('port'), function(req, res){
    console.log('啟動成功,端口為' + app.get('port'));
    console.log('主頁地址:http://localhost:' + app.get('port'));
});

關(guān)于第2點,是因為路由后或請求靜態(tài)資源后,一次請求響應(yīng)的生命周期實質(zhì)上已經(jīng)結(jié)束,加在這后面進(jìn)行請求處理,沒有任何意義。

關(guān)于第1點,寫點代碼繼續(xù)進(jìn)行試驗:

app.use(function(req,res,next){
    console.log("111");
    next();
});

如果不寫next(),那么后面注冊的函數(shù)就不會執(zhí)行(一個大臉疑問),運行測試下不就知道了

app.use(function(req,res,next){
    console.log('111');
    next();
    console.log('222');
});

app.use(function(req,res,next){
    console.log("333");
    next();
});

那么控制臺的輸出的順序是:111 333 222

試驗二 next()的工作原理

整個處理請求的模型還是比較簡單的,在理解的上面的過程后,能不能不借助express,自己實現(xiàn)上面的過程呢,主要是怎么處理next()那一塊

var http = require('http');
function express(){
    var funcs = [];

    var expr = function(req,res){
        var i = 0;
        function next(){            
            var task = funcs[i++];
            if(!task) return;
            task(req,res,next);
        }
        next();
    }
    expr.use=function(f){
        funcs.push(f);
    }
    return expr;
}
var app = express();

app.use(function(req,res,next){
    console.log('haha');
    next();
});
app.use(function(req,res,next){
    console.log('hehe');
    next();
});
app.use(function(req,res){
    res.end("there is nothing happened");
});

http.createServer(app).listen('3000', function(){
  console.log('Express server listening on port 3000');
});

啟動服務(wù)后,每來一個請求,控制臺會依次輸出haha hehe,然后瀏覽器是there is nothing happened

當(dāng)然如果要更深一步了解到,可以去看源代碼,實際上這一部分的主要代碼是在connect中的,在connect/lib/proto.js 這個源文件中,主要是app.use,和app.handle 兩個函數(shù)中

後續(xù)會再次研究expressapp.use原理。

原文鏈接
參考原文來自 前端亂燉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容