Heroku H27错误:服务器推送事件(SSE)GET请求被中断,导致客户端请求失败。

3
我有一个使用SSE向已连接客户端发送更新的node.js服务器。偶尔,我会收到服务器错误H27。同时,其他客户端请求可能会丢失,可能是因为客户端正在重新注册到SSE事件服务。
客户端GET /event请求和服务器H27错误之间经过的时间在13秒到19:35分钟之间(在30个不同的出现中)。但是,GET /event请求的时间和相应的H27错误之间有完全的相关性。 我从服务器每50秒发送一条保持活动状态的消息,以解决Heroku 55秒超时限制的问题。
以下是我在Heroku日志中得到的完整警告示例: 2020-10-17T08:49:04.525770+00:00 heroku[router]: sock=client at=warning code=H27 desc="Client Request Interrupted" method=GET path="/event" host=appname.herokuapp.com request_id=c4c4e2fd-16ca-4292-a27b-2a70b12b16fa fwd="77.138.167.76" dyno=web.1 connect=1ms service=9499ms status=499 bytes= protocol=https
它是由以下GET请求导致的: 2020-10-17T08:48:55.027638+00:00 app[web.1]: Client 8 registered
你有什么想法可以克服这个问题?我的问题是我的应用程序严重依赖于SSE,如果我现在必须切换到另一种机制(例如套接字),那将需要相当大的努力。
编辑: 进一步调查发现,这似乎是由于客户端无法在Heroku服务器上保持连接到Server Sent Events路由。虽然它可以建立第一个连接,但它无法保持连接。我怀疑这与Heroku请求超时和Heroku处理路由的方式有关。
我仍然没有找到解决这个问题的方法,所以每个人都可以随意评论。

你能解决这个问题吗?我遇到了完全相同的问题。有趣的是,当我第一次启动服务器时它可以工作,但在重新加载新页面后(可能只能工作1秒钟),它就会停止工作。我得到了code=H27 desc="Client Request Interrupted"的错误。我整天都在为此问题苦恼,因为我以为是我的代码有问题,但我认为这是一个Heroku特定的问题。你有没有碰巧使用任何Heroku附加组件? - philosopher
很遗憾,问题仍然存在。好消息是,自上次以来,H27似乎没有出现任何明显的问题。应用程序似乎运行良好,后端也没有丢失消息的情况。保持连接的消息配置为50秒,符合Heruko的55秒要求,所以我仍然没有找到原因是什么... - Arik Mimran
你说它不会引起问题是什么意思?在断开连接之前,你能够持续地从服务器实时接收到通知吗?在我的情况下,由于我依赖于事件处理程序(根据客户端的连接添加和删除)来发送消息,当H27请求中断发生时,我的服务器会移除用于向客户端发送这些消息的事件处理程序,导致客户端无法再接收到这些消息。 - philosopher
你能告诉我你是如何配置服务器以在不检测到客户端断开连接的情况下工作的吗?谢谢 @Arik Mirman - philosopher
1个回答

3

我已经找到了一个非常 hacky 的解决方案,对我来说很有效,因为我是该应用程序的唯一用户。

首先,先提供一些背景。问题在于,由于某些原因,在 Heroku 上部署的应用程序虽然能够成功连接到 SSE 路由,但无法检测到客户端连接的状态。在客户端连接成功之后,服务器在短时间内无法检测到客户端的状态,会在服务器上发布一个 code=H27 desc="Client Request Interrupted" 错误,这被视为客户端连接已关闭的标志。由于服务器认为客户端连接已关闭,自然会触发一个 close 事件,导致在 SSE 路由中的 req.close 块中的代码被执行。

在我的开发环境中,我的 SSE 路由配置如下:

router.get('/updates', (req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    })

    res.write('\n')

    // eventEmitted listeners are added for each connected client.
    const eventEmitted = (data) => {
        res.write(`data: ${JSON.stringify(data)}\n\n`)
    }

    eventEmitter.addListener('event', eventEmitted)

    // eventEmitted listeners are removed when each client disconnects.
    req.on('close', () => {
        eventEmitter.removeAllListeners()
    })
})

然而,正如前面提到的,由于Heroku无法检测客户端连接的状态,因此很快就会触发close事件,导致执行eventEmitter.removeAllListeners()。自然地,我的客户端此后将不会收到任何通知。我已经更改了我的路由,以便在Heroku上正常工作:

// Server Sent Events route for receiving realtime notifications when events are emitted.
router.get('/updates', (req, res) => {
    // eventEmitted listeners are added for the most recently connected client.
    const eventEmitted = (data) => {
        res.write(`data: ${JSON.stringify(data)}\n\n`)
    }

    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    })

    res.write('\n')

    eventEmitter.removeAllListeners()
    eventEmitter.addListener('event', eventEmitted)
})

结果是,由于删除了req.on('close'),在Heroku上不会出现H27错误,因为它不会尝试检测客户端状态。缺点是,只有最近连接的客户端可以从服务器接收实时更新。之前连接的所有客户端都将没有事件侦听器附加以向它们传递更新。这对我来说很有效,因为我是应用程序的唯一用户,但如果你有多个客户端或多个应用程序实例运行,则无法使用。
另一个选择是完全删除req.on('close'),并永远不删除任何添加的侦听器。这允许所有客户端接收更新,代价是您的服务器会有大量孤立的事件侦听器,用于每个已断开连接的客户端,最终导致内存泄漏。
这对于大多数人来说不是一个可接受的解决方案,但这是我能想到的唯一解决方案。如果有更好的解决方案,请随时在此处发布。

感谢@philosopher的回复。不幸的是,我的情况涉及到多个用户同时连接。 - Arik Mimran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接