我应该如何从 Express 中间件中调用异步函数?

How should I call an async function from within Express middleware?

提问人:Daniel Kesner 提问时间:10/27/2023 最后编辑:Daniel Kesner 更新时间:11/15/2023 访问量:82

问:

我已经能够将这个问题简化为一个单文件示例,该示例应该可以在 Express 5.0.0-beta.1 中轻松重现。

我正在编写一个实现基本 JWT 身份验证的 Express 中间件函数。逻辑非常简单:从标头中提取令牌(如果不存在/格式不正确,则返回 401),然后将令牌插入我们将调用的内部函数中,该函数返回 Promise。注意:我相信这是 Stack Overflow 上的一个独特问题,这个问题是关于从端点处理程序中进行异步调用,但我需要从中间件函数中进行异步调用。AuthorizationvalidateToken

我尝试通过以下方式实现这一点:

const express = require('express');
const app = express();

class HttpError {
    constructor(status, message) {
        this.status = status
        this.message = message
    }
}

// Auth middleware
const authMiddleware = (req, res, next) => {

        const token = req.header('Authorization');
        if (!token) {
            console.info("No JWT in request")
            return next(new HttpError(401, 'Unauthorized'))
        }
    
        const regex = /^Bearer (.+)$/;
        const jwt = regex.exec(token)[1];
    
        if (!jwt) {
            console.info("Malformed JWT")
            return next(new HttpError(401, 'Unauthorized'))
        }

        return validateToken({jwt: jwt, path: '/secured'})
            .then((response) => next())
            .catch(err => next(new HttpError(403, 'Forbidden')))   
}
app.use(authMiddleware)

// Endpoint definitions
app.get('/secured', (req, res) => {
    console.info('endpoint handler invoked')
    res.send('Valid token')
})

// Error handlers
function errorMiddleware(error, req, res, next) {
    console.info(`error middleware hit: ${JSON.stringify(error)}`)
    if (!error.status || error.status >= 500) {
        return res.status(error.status || 500).send();
    }
            
    res.status(error.status).json({ message: error.message });
}
app.use(errorMiddleware)

app.listen(8080);

当我发送一个没有标头的请求并且触发了前两个块之一(返回)时,应用程序会立即向客户端发出 401 Unauthorized 响应。但是,当我发送标头中带有令牌的请求时,应用程序会无限期地挂在中间件函数的最后一行。Authorizationifnext(new HttpError(...)return validateToken(...)

有人告诉我,Express 5.x 会自动处理中间件函数中被拒绝的承诺,但即使在升级到 Express 5 之后,应用程序在调用异步函数时仍然无限期挂起。validateToken

节点.js Express 异步

评论

1赞 Konrad 10/27/2023
这是一个已知的错误,表示忽略承诺中抛出的错误。你必须自己处理它们。看一看:处理 express 异步中间件中的错误
0赞 Daniel Kesner 10/27/2023
我尝试在答案中使用代码片段(尝试/捕获异步函数调用),并得到完全相同的行为:当我发送无效令牌时,请求无限期挂起。
2赞 Yousaf 10/28/2023
@DanielKesner 如果您在块内部调用,则还要提供错误处理程序。请参见: 默认错误处理程序next(error)catch
1赞 Yousaf 10/28/2023
代码中的主要问题是中间件未处理的承诺拒绝。如果返回被拒绝的 promise,则会导致异步中间件函数返回的 promise 被拒绝,但代码中不会处理该 promise 拒绝。validateToken
1赞 jfriend00 10/28/2023
@konrad - 这不是一个已知的错误。这是 Express v4 中缺少的功能。Express 的架构早在 promise 出现之前就已经存在了,并且 Express 中根本没有使用请求处理程序的返回值。为了让它看到来自处理程序的拒绝承诺,Express 必须注意返回值,查看它是否是承诺,如果是,则将拒绝处理程序挂接到它。这不是 Express v4 实现的。此新功能位于 Express v5 中。async

答:

0赞 Daniel Kesner 11/15/2023 #1

事实证明,我调用的函数有一些非常奇怪的行为 - 在验证令牌之前,它等待对 AWS 元数据服务的 HTTP 调用以获取有关系统的一些信息。我以为我的异步 Express 代码犯了一个错误,但实际上对 AWS 的调用需要 75 秒才能超时,超时后,处理程序的功能完全符合预期。validateToken

下面是一个 Express 服务器的单文件示例,它等待中间件中的异步操作,希望这对将来的人有所帮助:

const express = require('express');
const app = express();

class HttpError {
    constructor(status, message) {
        this.status = status
        this.message = message
    }
}

// Auth middleware
const authMiddleware = (req, res, next) => {

        const token = req.header('Authorization');
        if (!token) {
            console.info("No JWT in request")
            return next(new HttpError(401, 'Unauthorized'))
        }
    
        const regex = /^Bearer (.+)$/;
        const jwt = regex.exec(token)[1];
    
        if (!jwt) {
            console.info("Malformed JWT")
            return next(new HttpError(401, 'Unauthorized'))
        }

        // Assume that `validateToken` is a function returning a promise with either the token's claims or an error
        return this.validateToken({jwt: jwt, path: '/secured'})
            .then((response) => next())
            .catch(err => next(new HttpError(403, 'Forbidden')))   
}

// Invoke authMiddleware function for all endpoints that begin with '/secured'
app.use('/secured', authMiddleware)

// Endpoint definitions
app.get('/secured', (req, res) => {
    res.send('Valid token')
})

app.get('/unsecured', (req, res) => res.send('Unsecured endpoint'))

// Error handlers
function errorMiddleware(error, req, res, next) {
    console.debug(`Error caught by Express error handler: ${JSON.stringify(error)}`)
    if (!error.status || error.status >= 500) {
        return res.status(error.status || 500).send();
    }
    
    res.status(error.status).json({ message: error.message });
}
app.use(errorMiddleware)

app.listen(8080);