Express中OPTIONS请求处理程序无法执行的路由结构问题
我定义了如下Express路由:
const getFooRouter = Router().get('/foo', (_, res) => { res.json({ method: 'get', path: 'foo' }) }) const putFooRouter = Router().put('/foo', (_, res) => { res.json({ method: 'put', path: 'foo' }) }) const postBarRouter = Router().post('/bar', (_, res) => { res.json({ method: 'post', path: 'bar' }) })
将它们组合为mainRouter:
const mainRouter = Router().use(getFooRouter, putFooRouter, postBarRouter)
创建应用并运行:
const expressApp = express() expressApp.use(mainRouter) expressApp.listen(3333)
我尝试添加一个处理各路径CORS的corsRouter:
const corsRouter = Router() .options('/foo', (_, res) => { res.json({ method: 'options', path: 'foo' }) }) .options('/bar', (_, res) => { res.json({ method: 'options', path: 'bar' }) })
将其添加到mainRouter之后:
const expressApp = express() expressApp.use(mainRouter) expressApp.use(corsRouter) expressApp.listen(3333)
问题是corsRouter的处理程序从未被触发,发送OPTIONS http://localhost:3333/foo时,返回的是Express自动生成的"GET,HEAD"响应。移除mainRouter后,corsRouter可正常工作。
疑问:我的路由结构哪里出错了?
注意:
- 请勿推荐现成的CORS中间件,我只想知道此结构下corsRouter未触发的原因及修正方法。
- 此结构由OpenAPI文档生成,需保留该结构特点。
问题原因
Express的路由是按挂载顺序匹配的,请求到达后会先经过mainRouter。虽然你在子路由里只定义了GET/PUT/POST方法,但Express会自动给已注册的路径生成默认的OPTIONS响应——因为/foo和/bar这些路径已经被mainRouter里的子路由占用了,Express检测到路径存在,直接返回允许的方法列表(比如GET、HEAD),然后就结束请求流程了,根本不会往后走到corsRouter的处理逻辑。
修正方法
方法1:调整挂载顺序,把corsRouter放在mainRouter前面
既然路由按顺序匹配,把自定义OPTIONS逻辑的路由提前,就能让请求先被corsRouter捕获:
const expressApp = express() expressApp.use(corsRouter) // 先挂载corsRouter expressApp.use(mainRouter) expressApp.listen(3333)
这样发送OPTIONS请求时,会先匹配到corsRouter里的自定义处理程序,执行完就结束请求,不会触发Express的默认OPTIONS响应。
方法2:给对应子路由添加OPTIONS处理(适配OpenAPI结构)
如果要保持mainRouter的挂载顺序不变,可以把OPTIONS逻辑合并到对应路径的子路由中,符合OpenAPI按路径+方法拆分的结构:
// 给getFooRouter添加OPTIONS处理 const getFooRouter = Router() .get('/foo', (_, res) => { res.json({ method: 'get', path: 'foo' }) }) .options('/foo', (_, res) => { res.json({ method: 'options', path: 'foo' }) }) // 给putFooRouter添加OPTIONS处理 const putFooRouter = Router() .put('/foo', (_, res) => { res.json({ method: 'put', path: 'foo' }) }) .options('/foo', (_, res) => { res.json({ method: 'options', path: 'foo' }) }) // 给postBarRouter添加OPTIONS处理 const postBarRouter = Router() .post('/bar', (_, res) => { res.json({ method: 'post', path: 'bar' }) }) .options('/bar', (_, res) => { res.json({ method: 'options', path: 'bar' }) }) // 正常组合mainRouter并挂载 const mainRouter = Router().use(getFooRouter, putFooRouter, postBarRouter) const expressApp = express() expressApp.use(mainRouter) expressApp.listen(3333)
这种方式让每个路径的所有方法(包括OPTIONS)都在对应子路由中定义,Express就不会生成默认的OPTIONS响应了。
内容的提问来源于stack exchange,提问作者Balázs Édes

