Techniques

流式文件

学习如何在 NestJS HTTP 应用中流式传输文件,包括 StreamableFile 类的使用和跨平台支持

流式文件

注意 本章展示了如何从您的 HTTP 应用程序流式传输文件。下面提供的示例不适用于 GraphQL 或微服务应用程序。

有时您可能希望从 REST API 向客户端发送文件。要在 Nest 中执行此操作,通常您会执行以下操作:

@Controller('file')
export class FileController {
  @Get()
  getFile(@Res() res: Response) {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    file.pipe(res);
  }
}

但这样做会导致您失去对控制器后拦截器逻辑的访问权限。要处理这个问题,您可以返回一个 StreamableFile 实例,框架会在底层负责管道响应。

StreamableFile 类

StreamableFile 是一个保存要返回的流的类。要创建新的 StreamableFile,您可以将 BufferStream 传递给 StreamableFile 构造函数。

提示 StreamableFile 类可以从 @nestjs/common 导入。

跨平台支持

默认情况下,Fastify 可以支持发送文件而无需调用 stream.pipe(res),因此您根本不需要使用 StreamableFile 类。但是,Nest 支持在两种平台类型中使用 StreamableFile,因此如果您最终在 Express 和 Fastify 之间切换,则无需担心两个引擎之间的兼容性。

示例

您可以在下面找到一个简单的示例,将 package.json 作为文件而不是 JSON 返回,但这个想法自然地扩展到图像、文档和任何其他文件类型。

import { Controller, Get, StreamableFile } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('file')
export class FileController {
  @Get()
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file);
  }
}

默认内容类型(Content-Type HTTP 响应头的值)是 application/octet-stream。如果您需要自定义此值,可以使用 StreamableFiletype 选项,或使用 res.set 方法或 @Header() 装饰器,如下所示:

import { Controller, Get, StreamableFile, Res } from '@nestjs/common';
import { createReadStream } from 'fs';
import { join } from 'path';
import type { Response } from 'express'; // 假设我们使用 ExpressJS HTTP 适配器

@Controller('file')
export class FileController {
  @Get()
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file, {
      type: 'application/json',
      disposition: 'attachment; filename="package.json"',
      // 如果您想将 Content-Length 值定义为另一个值而不是文件的长度:
      // length: 123,
    });
  }

  // 或者甚至:
  @Get()
  getFileChangingResponseObjDirectly(@Res({ passthrough: true }) res: Response): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    res.set({
      'Content-Type': 'application/json',
      'Content-Disposition': 'attachment; filename="package.json"',
    });
    return new StreamableFile(file);
  }

  // 或者甚至:
  @Get()
  @Header('Content-Type', 'application/json')
  @Header('Content-Disposition', 'attachment; filename="package.json"')
  getFileUsingStaticValues(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'package.json'));
    return new StreamableFile(file);
  }  
}