1. 程式人生 > 其它 >express 為所有路由新增 405 method not allowd 響應

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 的實際效果: