使用 NextJS API 发送文件作为响应

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

如标题所示,

在 NodeJs + Express 中,我可以使用以下行返回一个文件作为响应

res.sendFile(absolute_path_to_the_file)

假设我想从 NextJs 目录中的输出文件夹返回单个图像,如何使用 NextJs API 实现此目的?我只能将 res.send() 和 res.json() 视为返回响应的方式,并且我不确定如何利用它来将图像作为响应返回给调用者。

如果我喜欢这样

res.send(absolute_path_to_the_file)

它只会向我发送目录路径的字符串。我期望的是从目录路径表示的目录发送的图像。

为此需要帮助。

node.js express next.js
5个回答
36
投票

在这里为那些也想知道的人回答我自己的问题..

我在 NextJS 中发布了一个关于它的帖子,他们给了我一个很好的答案 - 这里

有 2 种方法可以使用 readStream

var filePath = path.join(__dirname, 'myfile.mp3');
var stat = fileSystem.statSync(filePath);

response.writeHead(200, {
    'Content-Type': 'audio/mpeg',
    'Content-Length': stat.size
});

var readStream = fileSystem.createReadStream(filePath);
// We replaced all the event handlers with a simple call to readStream.pipe()
readStream.pipe(response);

或者将对象更改为缓冲区并使用发送方法

/*
Project structure:
.
├── images_folder
│   └── next.jpg
├── package.json
├── pages
│   ├── api
│   │   └── image.js
│   └── index.js
├── README.md
└── yarn.lock
*/

// pages/api/image.js

import fs from 'fs'
import path from 'path'

const filePath = path.resolve('.', 'images_folder/next.jpg')
const imageBuffer = fs.readFileSync(filePath)

export default function(req, res) {
  res.setHeader('Content-Type', 'image/jpg')
  res.send(imageBuffer)
}

这两个答案都适用于我的情况。使用

process.cwd()
导航到需要作为响应发送的文件/图像。


23
投票

根据NextJS 13,当使用App Router时,您可以按照以下方式进行:

// before
res.status(200).setHeader('content-type', 'image/png').send(data);

// after
return new Response(data, { headers: { 'content-type': 'image/png' } });
// or
const response = new NextResponse(data)
response.headers.set('content-type', 'image/png');
return response;

注意:

data
可以是Blob/Stream/Buffer

有关更多信息,请参阅此 GitHub Issue


3
投票

这是一个真实的示例,更高级,使用 Sentry 进行调试,并返回具有动态 CSV 文件名的流。 (和 TypeScript 类型)

它可能不如其他答案那么有帮助(由于其复杂性),但有一个更完整的现实世界示例可能会很有趣。

请注意,我对流并不熟悉,我所做的并不是100%最有效的方法,但它确实有效。

src/pages/api/webhooks/downloadCSV.ts

import { logEvent } from '@/modules/core/amplitude/amplitudeServerClient';
import {
  AMPLITUDE_API_ENDPOINTS,
  AMPLITUDE_EVENTS,
} from '@/modules/core/amplitude/events';
import { createLogger } from '@/modules/core/logging/logger';
import { ALERT_TYPES } from '@/modules/core/sentry/config';
import { configureReq } from '@/modules/core/sentry/server';
import { flushSafe } from '@/modules/core/sentry/universal';
import * as Sentry from '@sentry/node';
import {
  NextApiRequest,
  NextApiResponse,
} from 'next';
import stream, { Readable } from 'stream';
import { promisify } from 'util';

const fileLabel = 'api/webhooks/downloadCSV';
const logger = createLogger({
  fileLabel,
});

const pipeline = promisify(stream.pipeline);

type EndpointRequestQuery = {
  /**
   * Comma-separated CSV string.
   *
   * Will be converted into an in-memory stream and sent back to the browser so it can be downloaded as an actual CSV file.
   */
  csvAsString: string;

  /**
   * Name of the file to be downloaded.
   *
   * @example john-doe.csv
   */
  downloadAs: string;
};

type EndpointRequest = NextApiRequest & {
  query: EndpointRequestQuery;
};

/**
 * Reads a CSV string and returns it as a CSV file that can be downloaded.
 *
 * @param req
 * @param res
 *
 * @method GET
 *
 * @example https://753f-80-215-115-17.ngrok.io/api/webhooks/downloadCSV?downloadAs=bulk-orders-for-student-ambroise-dhenain-27.csv&csvAsString=beneficiary_name%2Ciban%2Camount%2Ccurrency%2Creference%0AAmbroise%20Dhenain%2CFR76%204061%208802%208600%200404%208805%20373%2C400%2CEUR%2CBooster%20Unly%20%20septembre%0AAmbroise%20Dhenain%2CFR76%204061%208802%208600%200404%208805%20373%2C400%2CEUR%2CBooster%20Unly%20%20octobre%0AAmbroise%20Dhenain%2CFR76%204061%208802%208600%200404%208805%20373%2C400%2CEUR%2CBooster%20Unly%20%20novembre%0A
 */
export const downloadCSV = async (req: EndpointRequest, res: NextApiResponse): Promise<void> => {
  try {
    configureReq(req, { fileLabel });
    const {
      csvAsString,
      downloadAs = 'data.csv',
    } = req?.query as EndpointRequestQuery;

    await logEvent(AMPLITUDE_EVENTS.API_INVOKED, null, {
      apiEndpoint: AMPLITUDE_API_ENDPOINTS.WEBHOOK_DOWNLOAD_CSV,
    });

    Sentry.withScope((scope): void => {
      scope.setTag('alertType', ALERT_TYPES.WEBHOOK_DOWNLOAD_CSV);

      Sentry.captureEvent({
        message: `[downloadCSV] Received webhook callback.`,
        level: Sentry.Severity.Log,
      });
    });

    await flushSafe();
    res.setHeader('Content-Type', 'application/csv');
    res.setHeader('Content-Disposition', `attachment; filename=${downloadAs}`);

    res.status(200);
    await pipeline(Readable.from(new Buffer(csvAsString)), res);
  } catch (e) {
    Sentry.captureException(e);
    logger.error(e.message);

    await flushSafe();

    res.status(500);
    res.end();
  }
};

export default downloadCSV;

代码基于 Next Right Now 样板,如果您想深入了解配置(Sentry 等):https://github.com/UnlyEd/next-right-now/blob/v2-mst -aptd-at-lcz-sty/src/pages/api/webhooks/deploymentCompleted.ts


3
投票

除了 Fred A 的回答之外,如果您的代码遇到困难,这是最后一行。我通过他的回答中的链接找到了如何解决这个问题。我写了一条回复,但为了更好的可见性,我将完整的答案放在这里。

import fs from "fs";

export default function(req, res) {
  const filePath = path.join(__dirname, 'myfile.mp3');

  const { size } = fs.statSync(filePath);

  res.writeHead(200, {
    'Content-Type': 'audio/mpeg',
    'Content-Length': size,
  });

  const readStream = fs.createReadStream(filePath);

  await new Promise(function (resolve) {
    readStream.pipe(res);

    readStream.on("end", resolve);
  });
});

0
投票

假设您有

byteArray
形式的文件,那么您可以在 Next.js API 中执行此操作以将文件发送到浏览器:

res.setHeader('Content-Disposition', 'attachment; filename="filename.file-extension"');
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Length', byteArray.length);
res.status(200).send(Buffer.from(byteArray));
© www.soinside.com 2019 - 2024. All rights reserved.