2015-11-17 21 views
0

這裏是我的代碼相關的部分,簡化了縮小的問題:對於SENDFILE問題在快遞中間件控制流量

app.use(middleware1); 
app.use(middleware2); 

function middleware1(req,res,next) { 
    ...//get extension of request URL 
    switch (extension) 
    { 
    case 'js' : 
    .. 
    case 'html': res.sendFile(res.originalUrl,function(err) {}); 
       break; //break1 
    case 'njm' : break; //break2 
    default : console.log('default'); 
       break; 
    } 
} 

function middleware2(req,res,next) { 
    console.log("I am in middleware2"); 
} 

的問題是:如果擴展HTML,例如,我不會期待中間件2被調用,但它確實! 看來,sendFile啓動文件的發送和控制執行落在sendFile的回調被調用之前。如果我用next()或return next()替換break1,那麼它會有同樣的缺陷 - 在執行sendFile的回調之前,控件將轉到下一個中​​間件2。如何阻止中間件2被第一組擴展調用?另外,如果擴展是'njm',即使沒有下一個(),中間件2也會被調用。爲什麼?

請不要建議使用Express靜態中間件,因爲我有一些邏輯涉及提供不同的文件類型,這與上面提供的簡化場景相比更爲複雜。

回答

2

res.sendFile()是有點獨特。如果你沒有通過完成回調,那麼它會爲你打電話next()。在本答覆的後面看到詳細信息。


您所報告的內容與Express的運作方式相反,因此我認爲您的舉報方式一定不會發生。

Express中間件的重點在於,任何給定的中間件調用都有機會對請求進行調用,然後通過生成響應來處理請求,或者如果它希望中間件鏈繼續,則調用next()。如果沒有調用next(),那麼中間件鏈將停止,並且當前中間件鏈中不會調用其他任何內容。如果這是應用程序級別的中間件(使用app.use()),那麼如果您不從中間件中調用next(),則不應再有應用程序級中間件處理。

下面是來自Express middleware page報價:

如果當前中間件不會結束請求響應週期,它 必須調用next()來控制傳遞給下一個中間件,否則 請求將被留下。

這是一篇關於Express中間件的不錯文章:Express Middleware Demystified這有助於解釋更多細節。它還證實,如果你不叫next(),那麼在中間件鏈中將不會有更多的處理程序被調用。


有一個特例與res.sendFile()。如果你沒有通過完成回調,那麼它會自動調用next()。如果您通過它完成回調,那麼它不會撥打next()。這似乎沒有很好的記錄,但如果你看res.sendFile()代碼here,你可以看到它是如何工作的。


有一點需要注意,與您的調試是有時瀏覽器發出更多的請求比你可能意識到的。例如,當您第一次點擊某個網站的主頁時,瀏覽器可能會要求網站圖標,這會導致額外的請求打擊您的網絡服務器。所以,我想知道你的console.log()調試是否讓你感到困惑,因爲可能有多個請求進入,而不是經過兩個中間件的單個請求。此外,跨請求Ajax調用也可能在請求實際的Ajax調用之前請求AJAX選項。

您可以區分這樣的多個請求和更準確地看看它是否實際上是從middleware1middleware2上了同樣的要求:

var reqCntr = 1; 
app.use(middleware1); 
app.use(middleware2); 

function middleware1(req,res,next) { 
    if (!req.reqCntr) { 
     req.reqCntr = reqCntr++; 
    } 
    console.log("middleware1: " + req.reqCntr); 

    ...//get extension of request URL 
    switch (extension) 
    { 
    case 'js' : 
    .. 
    case 'html': res.sendFile(res.originalUrl,function(err) {}); 
       // return here because the request is now handled 
       return; 
    case 'njm' : break; //break2 
    default : console.log('default'); 
       break; 
    } 
    // the request was not handled so call the next link in the middleware chain 
    next(); 
} 

function middleware2(req,res,next) { 
    if (!req.reqCntr) { 
     req.reqCntr = reqCntr++; 
    } 
    console.log("middleware2: " + req.reqCntr); 
} 

而且,它似乎像middleware1案件,你不處理請求應該調用next(),所以我修改了上面的middleware1來做到這一點。如果您在switch語句中處理請求,則return。如果不是的話,它將通過致電next()

+0

你指出了一些非常有用的見解。我會仔細看看的。它按照文檔描述的方式工作,使用我自己的異步調用(以下是調用數據庫的方式),這與Vineet描述的方式相同:在未調用異步的下一個調用時,未明確調用下一個中間件。在這裏,當調用sendFile異步時,控制權直至下一個中間件。我已經調試了很多次,不過只使用console.log。我有一個簡單的解決方法,在sendFile()之前設置一個標誌,然後在繼續使用第二個中間件之前檢查它,但這很笨拙。 – Sam

+1

@Samir - 如果你看看'res.sendFile()'代碼[這裏](https://github.com/strongloop/express/blob/master/lib/response.js#L417),你可以看到'res.sendFile()'在某些情況下調用'next()'本身,儘管它看起來像是將一個回調傳遞給'sendFile()',那麼它不會這樣做。 – jfriend00

+0

你可以編輯你的答案,突出顯示並解釋。我會接受它。我要求你這樣做的原因是,可能有其他人可能面臨同樣的問題。在我將評論發佈到Vineet後,我閱讀了您的正義評論。 – Sam

1

一旦你寫app.use(middleware2)middleware2將用於app中的所有路由,一旦middleware1被完全執行。

當你想使用middleware2有條件,我建議您使用以下方法:

app.use(middleware1); 

function middleware1(req,res,next) { 
    ...//get extension of request URL 
    switch (extension) 
    { 
    case 'js' : middleware2(req, res, next); 
       break; 

    case 'html': res.sendFile(res.originalUrl,function(err) {}); 
       break; 

    case 'njm' : middleware2(req, res, next); 
       break; 

    default : middleware2(req, res, next); 
       break; 
    } 
} 

    function middleware2(req,res,next) { 
    console.log("I am in middleware2"); 
    } 
+1

我的理解是,當您調用next()時,Express只進入下一個中間件處理程序。它這樣做是爲了允許異步處理程序。如果你不調用'next()',那麼不應該調用更多的中間件處理程序。這就是OP所說的事情令人困惑。 – jfriend00

+0

@Vineet,請閱讀我下面的評論。你是對的。在另一種情況下,我使用的方式恰恰如您所述......我在有條件時會顯式地調用中間件,並且不會在app.use列表中聲明它。有用。我認爲這是錯誤的做法。你的回答給了我一些希望,也許這是正確的方法 - 但我的問題是爲什麼正確的(?)方式,或者至少在文檔中說明的方式失敗? – Sam

+0

@Vineet在再次閱讀您的響應之後:是否正確說明APP.use列表僅在中間中間件沒有進行可以結束請求的異步呼叫時才執行預防性執行流程工作?請澄清。如果沒有其他解釋,我會接受你的回答。 – Sam