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

Posted

技术标签:

【中文标题】使用 NextJS API 发送文件作为响应【英文标题】:Send file as response using NextJS API 【发布时间】:2020-11-13 22:20:31 【问题描述】:

如题,

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

res.sendFile(absolute_path_to_the_file)

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

如果我喜欢这个

res.send(absolute_path_to_the_file)

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

在此需要帮助。

【问题讨论】:

aw 它没有回答...我被困在同一件事上,你有运气吗? @rakeshshrestha 我在 Vercel Github 上问了同样的问题,他们用这个回答了我 - github.com/vercel/next.js/discussions/… .. 尚未测试,但给出的答案看起来不错 【参考方案1】:

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

我在 NextJS 中发了一个帖子,他们给了我一个很好的答案 - here

两种方法是使用 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);

或者把对象改成buffer,使用send方法

/*
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() 导航到需要作为响应发送的文件/图像。

【讨论】:

如果要更改下载的文件名res.setHeader('Content-disposition', 'attachment; filename=filename.ext');,可以设置Content-disposition标题【参考方案2】:

这是一个真实世界的示例,它更高级,使用 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;

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

【讨论】:

以上是关于使用 NextJS API 发送文件作为响应的主要内容,如果未能解决你的问题,请参考以下文章

NEXTJS 将表单数据发送到 api 端点 - 无响应

如何从 API 发送 aspx 页面作为响应?

Nextjs 集中授权

如何使用改进获取响应作为对象将发布数据json发送到api

order-update.js 在事务 api actions-on-google 中发送“”作为响应

如何在 azure API 中发送文本作为输入并接收情绪或情绪作为响应?