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

如何在Node.js+Fastify中正确返回AWS SdkStream?

解决方案

问题出在AWS SDK v3返回的SdkStream并非标准Node.js可读流,Fastify直接处理时会引发头信息与流传输的时序冲突,导致报错。解决步骤如下:

  1. 安装依赖(如果未安装):
npm install @aws-sdk/util-stream-node
  1. 转换SdkStream为标准Node流:
    导入sdkStreamMixin工具,将S3返回的Body转换为Fastify可正确处理的Node.js可读流。

  2. 编码文件名:
    用encodeURIComponent处理文件名,避免特殊字符破坏响应头格式。

修改后的核心代码片段:

import { sdkStreamMixin } from "@aws-sdk/util-stream-node";

// ... 其余代码保持不变

const { Body } = await s3Client.send(command);
// 转换为标准Node.js可读流
const nodeReadableStream = sdkStreamMixin(Body);

return reply
  .header(`Content-disposition`, `attachment; filename="${encodeURIComponent(filename)}"`)
  .type(mimetype)
  .send(nodeReadableStream);

完整的API处理函数:

server.get(
  `${basePath}/download/:file_uuid`,
  {
    preHandler: (request, reply, done) => BasicAuthMiddleware(server, request, reply, done)
  },
  async (request, reply) => {
    try {
      const fileUUID = request.params.file_uuid;
      const bucket = process.env.aws_bucket_name;
      const accessKeyId = process.env.accessKeyId;
      const secretAccessKey = process.env.secretAccessKey;

      if(!accessKeyId) 
        throw new MissingParameter('AWS accessKeyId is missing', 500)
      if(!secretAccessKey) 
        throw new MissingParameter("AWS secretAccessKey is missing", 500);    
      if(!bucket) 
        throw new MissingParameter("Bucket name is missing from configs", 500)
      if(!fileUUID) {
        throw new MissingParameter("File UUID is missing", 500)
      }

      const s3Configs: S3ClientConfig = {
        region: process.env.aws_region,
        credentials: {
          accessKeyId: process.env.accessKeyId!,
          secretAccessKey: process.env.secretAccessKey!
        }
      }

      // 获取文件元信息
      const storedFileInfos = await server.pool_db.query(`
        SELECT * 
        FROM ${schema}.files 
        WHERE s3_filename = $1`, [ fileUUID ]);

      if(!storedFileInfos?.rows?.length) 
        throw new Error("File not found in saved uploads")
    
      const storedFile = storedFileInfos.rows[0];
      const { filename, mimetype } = storedFile;

      // 从S3获取文件并转换流
      const s3Client = new S3Client(s3Configs) as NodeJsClient<S3Client>;
      const command = new GetObjectCommand({ Bucket: bucket, Key: fileUUID })
      const { Body } = await s3Client.send(command);
      const nodeReadableStream = sdkStreamMixin(Body);

      return reply
        .header(`Content-disposition`, `attachment; filename="${encodeURIComponent(filename)}"`)
        .type(mimetype)
        .send(nodeReadableStream);
    } catch(error: any) {
      console.error(error);
      return reply.code(error.status ?? 500).send({
        statusCode: error.status ?? 500,
        message: error.message
      })
    }
  }
)

错误原因说明:

Fastify在处理流响应时,依赖标准Node.js可读流的事件机制来同步头信息写入与流传输。AWS SDK v3的SdkStream是自定义的异步迭代器实现,未兼容Node流的完整接口,导致Fastify无法正确处理流的生命周期,触发"stream closed prematurely"和头信息写入冲突的错误。转换为标准Node流后,Fastify就能按预期处理响应流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:53:11