You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中无法通过request.on事件关闭流请求的问题求助

问题:流式API端点无法在连接关闭时停止后台逻辑

我正在创建一个流式数据API端点,用于基于数据库事件实时更新组件。除清理逻辑外,其余功能基本正常:该端点应在连接关闭前持续流式传输数据,但关闭连接后,流式逻辑仍在运行,且无法通过request.on事件停止它。

模拟端点代码

// pages/api/example.ts

import { NextApiRequest, NextApiResponse } from 'next'

const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
  if (req.method === 'GET') {
    console.log('requested')
    // headers
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.setHeader('Content-Type', 'text/event-stream;charset=utf-8')
    res.setHeader('Cache-Control', 'no-cache, no-transform')
    res.setHeader('X-Accel-Buffering', 'no')

    setInterval(() => {
      console.log('ping')
      res.write('ping on res')
    }, 2000);

    let handleExit = () => {
      console.log('this will never run',)
      res.end()
    }

    req.on('close', () => {
      console.log('Close event triggered');
      handleExit();
    });
    req.on('finish', () => {
      console.log('Finish event triggered');
      handleExit();
    });
    req.on('end', () => {
      console.log('End event triggered');
      handleExit();
    });
    req.on('error', () => {
      console.log('Error event triggered');
      handleExit();
    });
    req.addListener('end', () => {
      console.log('End event triggered');
      handleExit();
    });
    req.on('readable', () => {
      console.log('Readable event triggered');
      handleExit();
    });
    console.log('all listeners set')
  }
}

export default handler

执行现象

执行curl -N localhost:3000/api/example时,终端每2秒会收到ping on res,NextJS控制台输出如下:

wait  - compiling /api/example (client and server)...
info  - Using external babel configuration from /home/node/src/.babelrc
event - compiled successfully in 1048 ms (96 modules)
requested
all listeners set
API resolved without sending a response for /api/example, this may result in stalled requests.
ping
ping
ping
ping
ping

但即使关闭请求(Ctrl+C),控制台仍持续打印“ping”,且close、finish等事件均未触发。


原因分析

  1. Next.js请求对象的事件封装限制:在Next.js的API路由中,NextApiRequest是对原生Node.js请求对象的封装,直接监听req的close/end等事件不会触发,实际连接关闭的事件应该监听res对象的close事件。
  2. 未清除定时器:setInterval创建的定时器没有被保存和清除,即使客户端断开连接,定时器仍会在后台持续运行。
  3. 响应未正确处理:初始代码中没有在连接关闭时正确结束响应,也没有处理响应写入失败的情况。

解决方案

修改代码,监听res的close事件,保存并清除定时器,同时添加响应写入的错误处理:

// pages/api/example.ts

import { NextApiRequest, NextApiResponse } from 'next'

const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
  if (req.method === 'GET') {
    console.log('requested')
    // 设置流式响应头部
    res.setHeader('Access-Control-Allow-Origin', '*')
    res.setHeader('Content-Type', 'text/event-stream;charset=utf-8')
    res.setHeader('Cache-Control', 'no-cache, no-transform')
    res.setHeader('X-Accel-Buffering', 'no')

    // 保存定时器实例,用于后续清除
    const interval = setInterval(() => {
      try {
        console.log('ping')
        // 写入标准SSE格式的数据
        res.write('data: ping on res\n\n')
        // 强制刷新响应流,确保数据立即发送
        res.flushHeaders?.()
      } catch (err) {
        // 写入失败时自动清理资源
        console.log('Failed to write response, cleaning up')
        clearInterval(interval)
        res.end()
      }
    }, 2000);

    // 监听响应关闭事件,处理客户端断开连接的情况
    res.on('close', () => {
      console.log('Connection closed, cleaning up')
      clearInterval(interval)
      res.end()
    })

    console.log('all listeners set')
  }
}

export default handler

关键改动说明

  • 监听res.close事件:Next.js中,res对象的close事件会在客户端断开连接时触发,这是检测连接关闭的正确方式。
  • 保存并清除定时器:将setInterval的返回值保存到变量中,在连接关闭时调用clearInterval停止定时任务,避免资源泄漏。
  • 标准SSE格式:修改响应内容为data: 内容\n\n的标准SSE格式,确保客户端能正确解析流式数据。
  • 错误处理:在res.write外层添加try/catch,避免连接关闭后写入响应抛出未捕获错误,同时自动清理资源。
  • 强制刷新响应:调用res.flushHeaders?.()确保数据立即发送到客户端,避免缓冲延迟。

内容的提问来源于stack exchange,提问作者chedieck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:43:26