1. 概念
1.什么是中間件
? ? ? ? 中間件(Middleware),特指業務流程的中間處理環節
2. Express 中間件的調用流程
? ? ? ? 當一個請求到達 Express 的服務器后,可以連續調用多個中間件,從而對這次請求進行預處理
3. Express 中間件格式
? ? ? ?Express 的中間件,本質上是一個 function 處理函數,Express 中間件格式如下
? ? ? ? 中間件函數的形參列表中,必須包含 next 參數,而路由處理函數中只包含 req 和 res
4. next 函數的作用
? ? ? ? next 函數是實現多個中間件連續調用的關鍵,它表示把流轉關系轉交給下一個中間件或者路由
2. 中間件函數
1.?定義
// 常量 mv 所指向的就是一個中間件函數
const mv = function ( req, res, next) {console.log('中間件demo')// 注意: 在當前中間件的業務處理完畢后,必須調用 next() 函數// 表示把流轉關系轉交給下一個中間件或路由next()
}
2. 全局生效的中間件
? ? ? ? 客戶端發起的任何請求,到達服務器后,都會觸發的中間件,叫全局生效的中間件
? ? ? ? 通過調用 app.use(中間件函數),即可定義一個全局生效的中間件
// 常量 mv 所指向的就是一個中間件函數
const mv = function ( req, res, next) {console.log('中間件demo')next()
}// 全局生效的中間件
app.use(mv)
3. 定義全局中間件的簡化形式
// 全局生效的中間件
app.use(function (req, res, next){console.log('demo')next()
})
4. 中間件的作用
? ? ? ? 多個中間件之間,共享同一份 req 和 res,基于這樣的特性,我們可以在上游的中間件中,統一為 req 或 res 對象添加自定義的屬性或方法,供下游的中間件或路由進行使用
5. 定義多個全局中間件
? ? ? ? 可以使用 app.use() 連續定義多個全局中間件,客戶端請求到達服務器后,會按照中間件定義的先后順序依次進行調用
6. 局部生效的中間件
? ? ? ? 不使用?app.use() 定義的中間件,即局部生效的中間件
? ? ? ? 只在當前路由生效
const mv1 = function(req, res, next) {console.log('中間件函數')next()
}// mv1 中間件只在當前路由生效
app.get('/', mv1, function(req, res) {res,send('get')
})// 此路由不生效
app.get('/user', function(req, res) {res,send('get1')
})
7. 定義多個局部中間件
? ? ? ? 兩種等價方式定義
app.get('/', mv1, mv2, function(req, res) { res,send('get') })
app.get('/', [mv1, mv2], function(req, res) { res,send('get') })
8. 中間件的5個使用注意事項
? ? ? ? ①? 一定要在路由之前注冊中間件
? ? ? ? ②? 客戶端發送的請求,可以連續調用多個中間件進行處理
? ? ? ? ③? 執行完中間件的業務代碼,需要調用 next() 函數
? ? ? ? ④? 防止代碼邏輯混亂,調用完?next() 函數后不要再寫額外代碼?
? ? ? ? ⑤? 連續多個中間件時,多個中間件之間,共享 req 和 res 對象
3. 中間件的分類
? ? ? ? Express 官方把常見的中間件用法,分為了五大類
? ? ????????? ? ①? 應用級別的中間件
? ? ????????? ? ②? 路由級別的中間件
? ? ????????? ? ③? 錯誤級別的中間件
? ? ? ????????? ④? Express 內置的中間件
? ? ????????? ? ⑤? 第三方的中間件
1.?應用級別的中間件
? ? ? ? 通過 app.use() 或 app.get() 或 app.post(),綁定到app實例上的中間件,叫做應用級別的中間件? ? ? ? 全局中間件和局部中間件
// 應用級別的中間件 (全局中間件)
app.use((req, res, next) => {next()
})// 應用級別的中間件 (局部中間件)
app.get('/', mv1, (req, res, next) => {res.send('局部')
})
2. 路由級別的中間件?
? ? ? ? 綁定到 express.Router() 實例上的中間件,叫做路由級別的中間件,它的用法和應用級別中間件沒有區別,區別在于,應用級別中間件是綁定在 app 實例上的,路由級別中間件綁定到 router 實例上的
const app = express()
const router = express.Router()// 路由級別的中間件
router.use(function (req, res, next) {console.log('路由級別中間件')next()
})app.use('/', router)
3. 錯誤級別的中間件
? ? ? ? 作用:專門用來捕獲整個項目中發生的異常錯誤,從而防止項目異常崩潰的問題
????????格式:必須要有 4 個形參,從前到后分別是 (err, req, res, next)
app.get('/', function(req, res) { // 路由throw new Error('錯誤!') // 拋出自定義錯誤res.send('get')
})
app.use(function (err, req, res, next) { // 錯誤級別的中間件 console.log('發生了錯誤' + err.message) // 服務器打印錯誤消息res,send('Error' + err.message) // 向客戶端響應錯誤信息相關內容
})
? ? ? ? 注意:錯誤級別的中間件,必須注冊在所有路由之后
4. Express內置的中間件
? ? ? ? Express 4.16.0版本后,Express 內置了3個常用的中間件
? ? ? ? ①? express.static 快速托管靜態資源的內置中間件,例如:HTML文件,圖片,css樣式(無兼容性)
? ? ? ? ②? express.json 解析JSON格式的請求體數據(有兼容性,在4.16.0+版本可用)
? ? ? ? ③? express.urlencoded 解析 URL-encoded 格式的請求體數據(有兼容性,在4.16.0+版本可用)
// 配置解析 application/json 格式數據的內置中間件
app.use(express.json())
// 配置解析 application/x-www-form-urlencode 格式數據的內置中間件
app.use(express.urlencoded({ extend: false }))
5. 第三方的中間件
? ? ? ? 非 Express 官方內置的,是第三方開發出來的中間件,叫第三方中間件,項目中可以按需下載并配置第三方中間件
? ? ? ? 在 express@4.16.0 之前的版本中,經常使用 body-parser 第三方中間件來解析請求體數據:
? ? ? ? ①? 運行 npm i body-parser 安裝
? ? ? ? ②??使用 require 導入中間件
? ? ? ? ③? 調用 app.use() 注冊并使用中間件
? ? ? ? 注意:Express 內置的?express.urlencoded 中間件,就是基于?body-parser 進一步封裝的
4.? 自定義中間件
1. 需求描述與實現步驟
????????需求:手動模擬類似于?express.urlencoded 的中間件,解析POST提交到服務器的表單數據
? ? ? ? 實現步驟:
? ? ? ? ? ? ? ? ①? ?定義中間件
? ? ? ? ? ? ? ? ②? ?監聽 req 的 data 事件
? ? ? ? ? ? ? ? ③? ?監聽 req 的 end 事件
? ? ? ? ? ? ? ? ④? ?使用 queryString 模塊解析請求體數據
? ? ? ? ? ? ? ? ⑤? ?將解析出來的數據對象掛載為 req.body
? ? ? ? ? ? ? ? ⑥? ?將自定義中間件封裝為模塊
2. 定義中間件
? ? ? ? 使用 app.use() 定義全局生效的中間件
app.use(function(req, res, next){// 中間件的業務邏輯
})
3.?監聽 req 的 data 事件
? ? ? ? 獲取客戶端發送到服務器的數據
? ? ? ? 如果數據量比較大,無法一次性發送完畢,則客戶端會把數據切割后,分批發送到服務器。所以data事件可能會觸發多次,每次觸發。獲取到數據只是完整數據的一部分,需要手動對接收到的數據進行拼接
// 定義變量 儲存客戶端發來的請求體數據
let str = ''
// 監聽 req 對象的 data 事件
req.on('data', (chunk) => {// 拼接請求體數據,隱式轉換為字符串str += chunk
})
4.?監聽 req 的 end 事件
? ? ? ? 請求體數據接收完畢之后會自動觸發 req 的 end 事件
? ? ? ? 可以在 req 的 end 事件中,拿到并處理完整的請求體數據
// 監聽 req 對象的 end 事件
req.on('end', () => {console.log(str) // 打印完整請求體數據// TOOD: 把字符串的請求體數據,解析成對象格式
})
5.?使用 queryString 模塊解析請求體數據
? ? ? ? Node.js 內置了一個 querystring 模塊,專門用來處理查詢字符串。通過這個模塊提供的 parse(), 可以把查詢字符串解析成對象的格式
// 導入處理 querystring 的 Node.js 內置模塊
const qs = require('querystring')// 調用 qs.parse() 方法 把查詢字符串解析為對象
const body = qs.parse(str)
? ? ? ? 注意:qs被棄用
6.?將解析出來的數據對象掛載為 req.body
? ? ? ? 上游中間件和下游中間件及路由之間,共享同一份 req 和 res。可以將解析出來的數據,掛載為 req 的自定義屬性,命名為 req.body,供下游使用
req.on('end', () => {const body = qs.parse(srt) // 調用 qs.parse() 方法把查詢字符串解析成對象req.body = body // 解析出來的請求體對象瓜子next() // 調用 next(),執行后續邏輯
})
7.?將自定義中間件封裝為模塊
? ? ? ? 優化結構,封裝為獨立模塊
// custom-body-parser.js 模塊代碼
const qs = require('querystring')
function bodyParser(req, res, next){ /* 省略 */ }
module.export = bodyParser // 向外導出解析請求體孫書記的中間件函數---------------------------------------------// 1. 導入自定義的中間件模塊
const myBodyParser = require('custom-body-parse')
// 2. 注冊自定義的中間件模塊
app.use(myBodyParser )