流式传输文件 Streaming files

导读

注意

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

有时您可能希望将文件从 REST API 发送回客户端。要使用 Nest 执行此操作,通常您需要执行以下操作:

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

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

Streamable File 类

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

hint

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

跨平台支持

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

示例

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

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

@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。如果您需要自定义此值,可以使用 StreamableFile 中的 type 选项,或者使用 res.set 方法或 @Header() 装饰器,如下所示:

ts
import { createReadStream } from 'node:fs'
import { join } from 'node:path'
import { Controller, Get, Res, StreamableFile } from '@nestjs/common'
import type { Response } from 'express' // Assuming that we are using the ExpressJS HTTP Adapter

@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"',
      // If you want to define the Content-Length value to another value instead of file's length:
      // length: 123,
    })
  }

  // Or even:
  @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)
  }

  // Or even:
  @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)
  }
}