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

文件上传代理端点无法完整转发文件的技术求助

问题分析与修复方案

你的核心问题出在代理端点提前终止了响应,同时数据流的结束逻辑没有正确处理,导致接收端永远收不到完整数据,end事件无法触发。

代理端点的关键错误与修复

原代码里的res.send({ status: 200 })会直接结束客户端响应,此时req.pipe(proxyReq)的数据流还没完成传输,被强制截断,这就是文件写不完整的根本原因。另外pipe设置了{ end: false }但没有手动处理结束,导致代理请求无法正常关闭。

修改后的代理端点代码:

@Post('/upload-proxy')
@Consumes('application/octet-stream')
public async uploadProxy(@Request() req: express.Request): Promise<void> {
  this.setHeader('Content-Type', 'application/octet-stream');

  const res = req.res;
  if (!res) {
    throw new Error('Response object is undefined');
  }

  const agent = new http.Agent({
    keepAlive: true,
    maxSockets: 2,
  });
  const options = {
    agent: agent,
    hostname: 'localhost',
    port: 8001,
    path: '/upload',
    method: 'POST',
    headers: {
      'Content-Type': 'application/octet-stream',
      Connection: 'keep-alive',
      // 转发原请求的Content-Length头,让接收端知道预期的文件大小
      ...(req.headers['content-length'] ? { 'Content-Length': req.headers['content-length'] } : {})
    },
  };

  const proxyReq = http.request(options, (proxyRes) => {
    // 接收端响应后,把响应内容转发给客户端,并且自动结束响应
    proxyRes.pipe(res);
  });

  // 处理原请求的错误,避免崩溃
  req.on('error', (err) => {
    console.error('Request error:', err);
    proxyReq.destroy(err);
    res.status(500).send({ error: 'Request failed' });
  });

  // 处理代理请求的错误
  proxyReq.on('error', (err) => {
    console.error('Proxy request error:', err);
    res.status(500).send({ error: 'Proxy failed' });
  });

  // 把原请求数据流管道到代理请求,自动结束代理请求
  req.pipe(proxyReq);

  // 不要提前调用res.send,等待数据流完成后由proxyRes.pipe(res)自动结束响应
}

代理端点修改说明:

  • 移除了res.send({ status: 200 }),避免提前终止响应。
  • req.pipe(proxyReq)去掉{ end: false },让数据流完成后自动结束代理请求。
  • proxyRes.pipe(res)去掉{ end: false },让接收端的响应返回后自动结束客户端响应。
  • 添加了错误处理,避免请求出错导致进程崩溃。
  • 转发Content-Length头,帮助接收端验证文件完整性。

接收端点的优化

原接收端代码没有处理数据流的错误,也没有监听写入流的finish事件,另外如果用pipe就不需要手动监听data事件。修改后的接收端代码:

@Post('/')
@Consumes('application/octet-stream')
public async upload(@Request() req: express.Request): Promise<void> {
  this.setHeader('Content-Type', 'application/octet-stream');
  const res = req.res;
  if (!res) {
    throw new Error('Response object is undefined');
  }

  const filePath = path.join('/home/sfalk/Desktop/', `uploaded-file.mp3`);

  // 用fs.promises异步操作,避免阻塞
  if (await fs.promises.access(filePath).then(() => true).catch(() => false)) {
    await fs.promises.unlink(filePath);
  }

  const writeStream = fs.createWriteStream(filePath);

  // 处理请求错误
  req.on('error', (err) => {
    console.error('Upload request error:', err);
    writeStream.destroy(err);
    res.status(500).send({ error: 'Upload failed' });
  });

  // 处理写入流错误
  writeStream.on('error', (err) => {
    console.error('File write error:', err);
    res.status(500).send({ error: 'File write failed' });
  });

  // 监听写入流的finish事件,确认文件写入完成
  writeStream.on('finish', () => {
    console.log('File uploaded completely');
    res.send({ msg: 'done' });
  });

  // 管道请求数据流到写入流,自动处理结束
  req.pipe(writeStream);
}

接收端点修改说明:

  • 改用异步的fs.promises方法,避免同步操作阻塞事件循环。
  • 添加了请求流和写入流的错误处理,及时捕获异常。
  • 监听写入流的finish事件(而不是请求的end事件),因为pipe完成后会触发finish,更可靠。
  • 移除了手动监听data事件的逻辑,pipe会自动处理数据写入。

关于http.Agent与连接终止的说明

  • 你使用keepAlive: true的http.Agent是合理的,特别是在频繁代理请求的场景下,能复用连接提升性能。关闭keepAlive出现EPIPE错误,是因为连接被提前关闭,而数据流还在传输,所以保持keepAlive是正确的选择。
  • 连接会在以下情况自动终止:
    • 代理请求的数据流传输完成后,req.pipe(proxyReq)会自动调用proxyReq.end()。
    • 接收端处理完请求并发送响应后,代理请求的响应流会结束,进而结束客户端的响应流。
    • 如果出现错误,通过destroy方法主动终止连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:04:53