使用 Next.js 和 CDN 集成进行文件下载和流处理

问题描述 投票:0回答:1

我正在尝试为我的网站上的大文件创建一个下载系统。我正在使用 Next.js,我的大文件托管在 CDN 上。我需要的是从我的 CDN 下载几个文件,创建一个 zip 文件,然后将此存档文件发送给客户端。我已经使用以下结构进行了此操作:

/api/download.ts:

export const getS3Object = async (bucketParams: any) => {
  try {
    const response = await s3Client.send(new GetObjectCommand(bucketParams))
    return response
  } catch (err) {
    console.log('Error', err)
  }
}

async function downloadRoute(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    res.setHeader('Content-Disposition', 'attachment; filename=pixel_capture_HDR.zip')
    res.setHeader('Content-Type', 'application/zip')

    const fileKeys = req.body.files
    const archive = archiver('zip', { zlib: { level: 9 } })

    archive.on('error', (err: Error) => {
      console.error('Error creating ZIP file:', err)
      res.status(500).json({ error: 'Error creating ZIP file' })
    })

    archive.pipe(res)

    try {
      for (const fileKey of (fileKeys as string[])) {
        const params = { Bucket: 'three-assets', Key: fileKey }
        const s3Response = await getS3Object(params) as Buffer
        archive.append(s3Response.Body, { name: fileKey.substring(fileKey.lastIndexOf('/') + 1) })
      }

      archive.finalize()
    } catch (error) {
      console.error('Error retrieving files from S3:', error)
      res.status(500).json({ error: 'Error retrieving files from S3' })
    }
  } else {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` })
  }
}

export const config = { api: { responseLimit: false } }

export default withIronSessionApiRoute(downloadRoute, sessionOptions)

我的下载功能在pages/download.tsx中:

  const handleDownload = async() => {
    setLoading(true)

    const files = [files_to_download]

    const response = await fetch('/api/download', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ files: files })
    })

    if (response.ok) {
      const blob = await response.blob()
      const url = URL.createObjectURL(blob)

      const link = document.createElement('a')
      link.href = url
      link.download = 'pixel_capture_HDR.zip'
      link.click()

      URL.revokeObjectURL(url)
    }

    setLoading(false)
  }

通过上述结构,当客户端开始获取文件时,我会在客户端上显示加载 UI。一旦一切准备就绪并由服务器压缩,zip 文件几乎会立即下载到用户的下载文件夹中。

这种结构的问题是,如果用户关闭浏览器选项卡,下载就会停止并失败。我希望实现的是将下载和压缩直接流式/管道传输到浏览器的下载队列中,如果这有意义的话。

我尝试在客户端使用 Readable Streams,这似乎很奇怪,因为当我记录推送函数时,我可以看到正在记录的值,并且当所有内容都下载完毕后,“done”变量从 false 变为 true。但是,当一切准备就绪时,仍然会触发 zip 文件的实际浏览器下载,而不是用户单击下载按钮后立即“流式传输”下载。

  if (response.ok) {
    const contentDispositionHeader = response.headers.get('Content-Disposition')
    
    const stream = response.body
    const reader = stream.getReader()

    const readableStream = new ReadableStream({
      start(controller) {
        async function push() {
          const { done, value } = await reader.read()
          if (done) {
            controller.close()
            return
          }
          controller.enqueue(value)
          push()
        }
        push()
      }
    })

    const blob = new Blob([readableStream], { type: 'application/octet-stream' })
    const url = URL.createObjectURL(blob)
  }

我意识到解释我想要什么是相当具有挑战性的,我希望它是可以理解和实现的。

我将非常感谢有关如何实现此类解决方案的任何提示。

预先感谢您的帮助!

javascript typescript next.js download
1个回答
0
投票

如果我理解正确的话,您的目标是启用即时生成的 ZIP 文件的流式下载,即使 ZIP 创建过程尚未完成,也允许用户开始下载。

我看到,在原始代码中,S3 中的每个文件都是使用

await getS3Object(params) as Buffer
完全提取到内存中的。 并且“所有”文件在添加到 ZIP 存档之前都存储在内存中。对于大文件或大量文件来说,这可能会成为问题。 ZIP 文件一次性创建并发送到客户端,直到所有文件都已获取并压缩后才会启动。

在服务器端,我建议将每个 S3 响应流直接通过管道传输到 ZIP 存档流。这样,一旦服务器开始从 S3 接收文件,它就会立即开始将其发送到客户端。

您的

/api/download.ts

将是:

import { PassThrough } from 'stream';

async function downloadRoute(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    res.setHeader('Content-Disposition', 'attachment; filename=pixel_capture_HDR.zip');
    res.setHeader('Content-Type', 'application/zip');
    
    const fileKeys = req.body.files;
    const archive = archiver('zip', { zlib: { level: 9 } });

    archive.on('error', (err: Error) => {
      console.error('Error creating ZIP file:', err);
      res.status(500).json({ error: 'Error creating ZIP file' });
    });

    archive.pipe(res);

    try {
      for (const fileKey of (fileKeys as string[])) {
        const params = { Bucket: 'three-assets', Key: fileKey };
        const s3Response = await getS3Object(params);
        
        const pass = new PassThrough();
        pass.end(s3Response.Body);
        
        archive.append(pass, { name: fileKey.substring(fileKey.lastIndexOf('/') + 1) });
      }

      archive.finalize();
    } catch (error) {
      console.error('Error retrieving files from S3:', error);
      res.status(500).json({ error: 'Error retrieving files from S3' });
    }
  } else {
    res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
  }
}

在客户端,客户端等待接收整个 ZIP 文件,然后再开始下载。
它将每个接收到的块附加到一个数组中,最后创建一个用于下载的 Blob 对象,从而提供更实时的下载体验。


相反,您可以从

Blob

创建一个新的

ReadableStream
对象,并随着流的进行创建一个对象 URL。以下是如何在
pages/download.tsx
中修改客户端代码:
  if (response.ok) {
    const contentDispositionHeader = response.headers.get('Content-Disposition');
    
    const stream = response.body;
    const reader = stream.getReader();
    
    const chunks = [];
    const pump = async () => {
      const { done, value } = await reader.read();
      if (done) {
        const blob = new Blob(chunks, { type: 'application/zip' });
        const url = URL.createObjectURL(blob);
        // Your download logic
        return;
      }
      chunks.push(value);
      pump();
    };
    pump();
  }

通过使用这些调整,ZIP 文件应在用户启动下载后立即开始流式传输。这样,即使用户关闭浏览器选项卡,下载也应该继续到用户关闭选项卡的位置。

© www.soinside.com 2019 - 2024. All rights reserved.