Express.js 为所有路由添加 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
的实际效果: