Node.js 中間件是怎樣工做的?

做者:thirdrock team

翻譯:瘋狂的技術宅javascript

原文:https://www.thirdrocktechkno....前端

未經容許嚴禁轉載java

什麼是 Express 中間件?

  • 中間件在字面上的意思是你在軟件的一層和另外一層中間放置的任何東西。
  • Express 中間件是在對 Express 服務器請求的生命週期內所執行的函數。
  • 每一箇中間件均可以訪問其被附加到的全部路由的 HTTP 請求和響應。
  • 另外,中間件能夠終止 HTTP 請求,也能夠用 next 將其傳遞給另外一箇中間件函數。中間件的這種「鏈」使你能夠對代碼進行劃分並建立可重用的中間件。

編寫 Express 中間件的要求

你須要安裝一些東西來建立、使用和測試 Express 中間件。首先須要 Node 和 NPM。爲確保已經安裝,能夠運行:node

npm -v && node -v

你應該看到已安裝的 Node 和 NPM 版本。若是出現錯誤,則須要安裝 Node。全部例子都應在 Node ver 8+ 和NPM ver 5+ 下使用。程序員

本文使用了 Express 4.x 版。這很重要,由於從 3.x 版到 4.x 版有重大的更改。web

Express中間件:基礎

首先咱們使用 Express 最基本的內置中間件。建立一個新項目並 npm 初始化它…面試

npm init
npm install express --save

Create server.js and paste the following code:

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

app.get('/', (req, res, next) => {
  res.send('Welcome Home');
});

app.listen(3000);

中間件解決什麼問題?爲何要用它?

假設你在 web 網絡服務器上正在使用 Node.js 和 Express 運行Web應用程序。在此應用中,你須要登陸的某些頁面。數據庫

當 Web 服務器收到數據請求時,Express 將爲你提供一個請求對象,其中包含有關用戶及其所請求數據的信息。 Express 還使你能夠訪問響應對象,能夠在Web服務器響應用戶以前對其進行修改。這些對象一般縮短爲 reqresexpress

中間件函數是使用相關信息修改 reqres 對象的理想場所。例如用戶登陸後,你能夠從數據庫中獲取其用戶詳細信息,而後將這些詳細信息存儲在 res.user 中。npm

中間件函數是什麼樣的?

async function userMiddleware (req, res, next) {
    try {
        const userData = await getUserData(req.params.id);  //see app.get below

        if(userData) {
                req.user = userData;
                next();
        }
    } catch(error)  {
        res.status(500).send(error.message); //replace with proper error handling
    }
}

若是出現錯誤,而且你不想執行其餘代碼,則不要調用該函數。請記住在這種狀況下要發送響應,不然客戶端將會等待響應直到超時。

var app = express();

//your normal route Handlers
app.get('/user/:id', userMiddleware, userController);

中間件鏈

你能夠在中間件數組中或着經過使用多個 app.use 調用來連接中間件:

app.use(middlewareA);
app.use(middlewareB);
app.get('/', [middlewareC, middlewareD], handler);

Express 收到請求後,與請求相匹配的每一箇中間件都將會按照初始化的順序運行,直到有終止操做爲止。

image.png

所以,若是發生錯誤,則將按順序調用全部用於處理錯誤的中間件,直到其中一個再也不調用 next() 函數調用爲止。

Express中間件的類型

  • 路由器級中間件,例如:router.use
  • 內置中間件,例如:express.static,express.json,express.urlencoded
  • 錯誤處理中間件,例如:app.use(err,req,res,next)
  • 第三方中間件,例如:bodyparser、cookieparser
  • 路由器級中間件
  • express.Router 使用 express.Router 類建立模塊化的、可安裝的路由處理。路由實例是一個完整的中間件和路由系統。

    • 你能夠用中間件進行日誌記錄、身份驗證等操做。以下所示,以記錄用戶的最新活動並解析身份驗證標頭,用它肯定當前登陸的用戶並將其添加到 Request 對象。
    • 該函數在程序每次收到請求時執行。若是有錯誤,它會僅結束響應,而不會調用後續的中間件或路由處理。
var router = express.Router()
Load router-level middleware by using the router.use() and router.METHOD() functions.
The following example creates a router as a module, loads a middleware function in it, defines some routes, and mounts the router module on a path in the main app.
var express = require(‘express’);
var router = express.Router();

// a middleware function with no mount path. This code is executed for every request to the router
// logging
async function logMiddleware (req, res, next) {
    try {
         console.log(req.user.id, new Date());
     next();
    } catch() {
        res.status(500).send(error.message);
    }
}
// authentication
    async function checkAuthentication(req, res, next) => {
// check header or url parameters or post parameters for token
const token = req.body.token || req.query.token || req.headers['x-access-token'] || req.headers['authorization'];
      if (token) {
        try {
            // verifies secret
            req.decoded = await jwt.verify(token, config.secret)

            let checkUser = await authenticateTokenHelper.getUserDetail(req);

            // if everything is good, save to request for use in other routes
                if (checkUser) {
                        req.user = req.decoded
                        next()
                } else {
                    return res.status(403).json({ 
                    message: responseMessage.noAuthorized 
                    })
                }
        } catch (err) {
            return res.status(401).json({ message: responseMessage.invalidToken })
        }
  } else {
    // if there is no token
    return res.status(400).json({ message: responseMessage.invalidRequest })
  }
}
router.use(logMiddleware);
    router.get('/user, checkAuthentication, handler);

內置中間件

Express 有如下內置的中間件功能:

  • express.static 提供靜態資源,例如 HTML 文件,圖像等。
  • express.json 負載解析用 JSON 傳入的請求。
  • express.urlencoded 解析傳入的用 URL 編碼的有效載荷請求。

錯誤處理中間件

錯誤處理中間件始終採用四個參數(err,req,res,next)。你必須經過提供四個參數來將其標識爲錯誤處理中間件函數。即便你不須要使用 next 對象,也必須指定。不然 next 對象將被解釋爲常規中間件,並將會沒法處理錯誤。基本簽名以下所示:

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

例1:

app.get('/users', (req, res, next) => {
  next(new Error('I am passing you an error!'));
});
app.use((err, req, res, next) => {
  console.log(err);    
  if(!res.headersSent){
    res.status(500).send(err.message);
  }
});

在這種狀況下,管道末端的錯誤處理中間件將會處理該錯誤。你可能還會注意到,我檢查了 res.headersSent 屬性。這只是檢查響應是否已經將標頭髮送到客戶端。若是尚未,它將向客戶端發送 HTTP 500 狀態和錯誤消息。

例2:

你還能夠連接錯誤處理中間件。一般以不一樣的方式處理不一樣類型的錯誤:

app.get('/users, (req, res, next) => {
  let err = new Error('I couldn\'t find it.');
  err.httpStatusCode = 404;
  next(err);
});

app.get('/user, (req, res, next) => {
  let err = new Error('I\'m sorry, you can\'t do that, Dave.');
  err.httpStatusCode = 304;
  next(err);
});

app.use((err, req, res, next) => {
   // handles not found errors
  if (err.httpStatusCode === 404) {
    res.status(400).render('NotFound');
  }
   // handles unauthorized errors 
  else if(err.httpStatusCode === 304){
    res.status(304).render('Unauthorized');
  }
    // catch all
   else if (!res.headersSent) {
     res.status(err.httpStatusCode || 500).render('UnknownError');
  }
  next(err);
});
  • 在這種狀況下,中間件檢查是否拋出了 404(not found)錯誤。若是是,它將渲染 「NotFound」 模板頁面,而後將錯誤傳遞到中間件中的下一項。
  • 下一個中間件檢查是否拋出了 304(unauthorized)錯誤。若是是,它將渲染「Unauthorized」頁面,並將錯誤傳遞到管道中的下一個中間件。
  • 最後,「catch all」 錯誤處理僅記錄錯誤,若是未發送響應,它將發送錯誤的 httpStatusCode(若是未提供則發送 HTTP 500 狀態)並渲染 「UnknownError」 模板。

第三方級別的中間件

在某些狀況下,咱們將向後端添加一些額外的功能。先安裝 Node.js 模塊獲取所需的功能,而後在應用級別或路由器級別將其加載到你的應用中。

示例:當 body-parser 處理 Content-Type 請求標頭時,全部中間件都將使用解析的正文填充 req.body 屬性。

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
app.post('/save',(req,res)=>{
    res.json({
        "status":true,
         "payload":req.body
    })
}
app.listen(3000,(req,res)=>{
    console.log('server running on port')
})

總結

中間件功能是一種很是好的方式,能夠對每一個請求或針對特定路由的每一個請求運行代碼,並對請求或響應數據採起措施。中間件是現代 Web 服務器的重要組成部分,而且很是有用。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索