express 為所有路由新增 405 method not allowd 響應
背景知識
HTTP Status Code 405
405 Method not allowed
The resource was requested using a method that is not allowed. For example, requesting a resource via a POST method when the resource only supports the GET method.
405
響應意味著這個路由存在,但是請求的方法不支援。
HTTP Response Header Allow
Valid actions for a specified resource. To be used for a 405 Method not allowed
Allow: GET, HEAD
HTTP 響應頭 Allow
主要是配合 405
響應一起使用,用於告訴客戶端此路由支援的 HTTP 方法。
起因
最近在使用 Expressjs 開發 Restful API,發現其內建沒有對 HTTP 405 響應的支援,對於有路由但未定義的 相應 HTTP method 處理方法的請求其會響應 404 錯誤(express-generator 生成的程式碼預設行為是這樣),這顯然不利於我們排錯,也不符合 HTTP 狀態碼的語義,所以我們可以為其增加 HTTP 響應 405
支援。
針對單個 router 的快速實現
對於某一個 router,我們可以簡單地在路由處理方法最後增加一個方法來支援 405 響應,實現比較簡單:
import { Router } from 'express' const router = new Router() const getAll = (req, res, next) => {} const createNew = (req, res, next) => {} router.route('/resource') .get(getAll) .post(createNew) .all(function support405Response (req, res, next) { res.set('Allow', 'GEt, POST') res.status(405).send('Method Not Allowed') })
實現一個通用方案
router 的結構
為某一個 router 增加 405
響應支援很簡單,但是若有很多個 router,每個都去手動撰寫太麻煩了,最好是有個方法能自動包裝。
可以將 router 打印出來,分析一下 router 的結構,這裡以如下 router 定義為例:
// 省略無關程式碼...
const router = new Router()
router.route('/')
.get(getAll)
.post(createNew)
router.route('/:id')
.get(getOne)
.patch(updateOne)
.delete(deleteOne)
上面 router 的列印結果如下:
{
params: {},
_params: [],
caseSensitive: undefined,
mergeParams: undefined,
strict: undefined,
stack: [
Layer {
handle: [Function: bound dispatch],
name: 'bound dispatch',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i,
route: [Route]
},
Layer {
handle: [Function: bound dispatch],
name: 'bound dispatch',
params: undefined,
path: undefined,
keys: [Array],
regexp: /^\/(?:([^\/]+?))\/?$/i,
route: [Route]
}
]
}
router.stack 下 layer 的 route
觀察發現,stack
屬性陣列下每個 Layer 有不少資訊,但有幫助的還是太少。再把每個 Layer 的 route 打印出來看看:
[
Route {
path: '/',
stack: [ [Layer], [Layer] ],
methods: { get: true, post: true }
},
Route {
path: '/:id',
stack: [ [Layer], [Layer], [Layer], [Layer], [Layer] ],
methods: { _all: true, get: true, patch: true, delete: true }
}
]
嗯嗯,這下有不少有用的可以直接使用的資訊。開始著手實現一個通用的為所有 router 增加 405
響應的方法。
一個為所有 router 新增 405 響應的方法
export default function add405ResponseToRouter (router) {
const routes = router.stack.map(layer => layer.route)
for (const route of routes) {
const {
path,
methods
} = route
router.route(path)
.all(function methodNotAllowed (req, res, next) {
res.set('Allow', Object.keys(methods).filter(method => method !== '_all').map(method => method.toUpperCase()).join(', '))
res.status(405).send('Method Not Allowed')
})
}
return router
}
用上面實現的這個方法去包裝我們之前定義的 router,請求 /:id
的實際效果: