【问题标题】:Cloud function error one time in two calls : 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client'两次调用中出现一次云函数错误:“错误 [ERR_HTTP_HEADERS_SENT]:在将标头发送到客户端后无法设置标头”
【发布时间】:2021-08-27 12:13:44
【问题描述】:

我有一个调用复杂任务的云函数(具有许多不同的异步任务)。在这些任务结束时,将发送一个事件“完成”并由云函数捕获,该函数将终止,例如:

exports.myFunction = functions.https.onRequest(async (request: any, response: any) => {
  cors(request, response, async () => {
    doSomethingComplex();                          // no returned value possible here, but many asynchronous tasks done
    eventEmitter.on('finished', (data) => {        // finished is properly sent at the end of the asynchronous tasks
       functions.logger.debug('done : ', data);    // is always clean with only 1 occurence at each function call
       response.send({something: data.something}); // <--- THIS IS WHERE THE ERROR OCCURE
    }
  });
});

目前,每个异步任务在每次调用时都能完美完成。但恰好有两次,response.send() 出错并出现以下错误堆栈:

at ServerResponse.send (/layers/google.nodejs.functions-framework/functions-framework/node_modules/express/lib/response.js:170:12) 

at ServerResponse.json (/layers/google.nodejs.functions-framework/functions-framework/node_modules/express/lib/response.js:267:15) 

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client 

at ServerResponse.setHeader (_http_outgoing.js:530:11)

at ServerResponse.send (/layers/google.nodejs.functions-framework/functions-framework/node_modules/express/lib/response.js:158:21) 

at ServerResponse.header (/layers/google.nodejs.functions-framework/functions-framework/node_modules/express/lib/response.js:771:10) 

at EventEmitter.<anonymous> (/workspace/lib/src/index.js:235:37) 
Function execution took 3354 ms, finished with status: 'crash'

我尝试使用response.end(),我尝试返回response.send(),但一切都失败了。

我还记录了每个异步任务,以确保它们在发送“完成”事件之前全部完成,并且该部分的一切都是干净的。

我不明白 response.send() 如何可以在任何时候调用云函数时只调用一次,但仍会触发Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client 每两次调用一次(1 次成功,1 次错误,1 次成功,1 次错误) .

这对我来说没有意义,就像在 response.send() 之后没有关闭云功能一样(根据this answer 这是不可能的)

有人有想法吗?将不胜感激!

【问题讨论】:

    标签: javascript node.js typescript firebase google-cloud-functions


    【解决方案1】:

    answer you linked 谈论云函数生命周期。

    当您向函数发出请求时,Cloud Functions 网关会检查是否有任何“Cloud Function Executors”处于空闲/陈旧/非活动状态。如果没有一个是空闲的,一个新的执行器被冷启动并被发送请求。如果一个是空闲的,则向该执行程序发送请求,而不是不必冷启动一个新的执行程序。处理请求时,您通过发回结果告诉网关“我完成了”(对于 HTTPS 事件云函数,这是通过使用 res.end() 或其变体之一完成的;对于大多数其他云函数,这是通过解析返回的Promise 链来完成)。

    在您发回结果后,“Cloud Function Executor”将被标记为空闲。空闲时,任何网络请求都会被阻止,并且该执行程序可能会随时关闭(终止)。如上所述,如果空闲函数处理另一个请求,则可能会再次将其标记为活动状态,如果您没有正确处理生命周期流,可能会导致奇怪的副作用。

    因此,要正确处理流程,您需要按照以下步骤操作:

    1. 验证请求(检查重试、语法错误、缺少身份验证)并在必要时取消它。
    2. 完成所有您需要完成的工作。
    3. 发回适当的结果。

    如果您需要做的工作预计需要一段时间,请将作业描述写入您的数据库(Firestore/RTDB),以便由写入数据库触发的不同云函数处理它,然后将结果作为在哪里查找该作业更新的描述发回。


    查看您的代码,eventEmitter 似乎被定义为一个全局对象,当它完成doSomethingComplex() 中的工作时会吐出"finished" 事件。第一次触发myFunction 时,一切正常。但是如果myFunction 被重用,第二个"finished" 事件将被触发。这适用于当前请求,但之前的请求仍订阅 eventEmitter 并尝试发送第二个响应,从而导致您看到的错误。

    您有三个选择:

    • 仅订阅一个“已完成”事件。 (解决方案评分:差)(想想是否失败,但后来的请求成功)
    • doSomethingComplex() 返回一个私有版本的eventEmitter。 (解决方案评级:好的)
    • doSomethingComplex() 转换为Promise-返回函数。 (解决方案评级:最佳)

    【讨论】:

    • 非常感谢您的帮助。问题确实是接听前一个电话的发射器。我目前正在重构一些代码以将 doSomethingComplex 转换为 Promise-returning 函数。同时,我正在使用的拼凑(非常糟糕的评级解决方案?)是使用 eventEmitter.once 而不是 .on 再次,非常感谢您提供的那些非常有用的见解,这个问题在生产中是一个真正的痛苦。你真的拯救了这一天!
    • @Aion 关于为什么只听一个“已完成”事件仍然是一个糟糕的解决方案,想想这种情况:用户请求您的功能,但如果失败并返回一个适当的“错误”事件错误响应。当下一个请求进入并成功时,该请求将返回其成功的响应,但“finished”事件也会为第一个返回错误的请求触发,从而导致另一个“headers sent”错误。
    • 关于您现在删除的关于使用私有事件发射器的评论,我的意思是:当doSomethingComplex 启动时,它会创建一个新的事件发射器,然后在函数结束时,它返回它,以便您可以使用const eventEmitter = doSomethingComplex()。您还可以在处理程序中创建新的事件发射器,并使用doSomethingComplex(eventEmitter) 或类似方法将其传递给doSomethingComplex
    • 现在非常有意义。非常感谢!
    猜你喜欢
    • 2020-09-22
    • 2020-05-26
    • 2018-09-19
    • 2023-01-19
    • 2021-02-26
    • 2021-08-20
    • 2019-09-28
    • 2019-12-25
    相关资源
    最近更新 更多