NestJS/Express如何在响应中返回从外部URL获取的PDF文件

3

我正在调用一个返回PDF文件的外部API,并且我想在我的控制器函数响应中返回这个PDF文件。

在我的控制器类中:

  @Get(':id/pdf')
  async findPdf(@Param('id') id: string, @Res() res: Response) {
    const response = await this.documentsService.findPdf(id);

    console.log(response.data); 
    // this prints the following:
    // %PDF-1.5
    // %����
    // 2 0 obj
    // << /Type /XObject /Subtype /Image /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter // /DCTDecode /Width 626 /Height
    //  76 /Length 14780>>
    //  stream
    // and go on...

    return res
      .status(200)
      .header('Content-Type', 'application/pdf')
      .header('Content-Disposition', response.headers['content-disposition'])
      .send(response.data);
  }

在我的服务类中:

  findPdf(id: string): Promise<any> {
    return firstValueFrom(
      this.httpService
        .get(`/docs/${id}/pdf`)
        .pipe(map((response) => response))
        .pipe(
          catchError((e) => {
            throw new BadRequestException('Failed to get PDF.');
          }),
        ),
    );
  }

但是我在响应中得到了一个空的PDF文件。

内部API调用没有问题,我已经从Postman测试过了,PDF文件是正确的。

我做错了什么?


你为什么说“我在响应中得到了一个空白的PDF文件”? - hoangdv
@hoangdv,这是因为我的NestJS端点生成的PDF文件是一个空白的PDF文件,没有任何内容。 - Fantasmic
1个回答

2

我已经测试并重现了您所描述的问题。

原因是您的外部服务器以流的形式响应PDF文件,而您的解决方案无法处理它。

首先,由于响应是流,您需要通过更改Axios(HTTP服务)来告知它:

.get(`/docs/${id}/pdf`)

to:

.get(`/docs/${id}/pdf`, { responseType: "stream" })

接下来,你有两种方法(取决于你的需求):

  1. 你可以将该流传输到主要响应中(从而将流传递给服务的调用者)。

  2. 你可以从文档服务器收集整个流数据,然后将最终缓冲区数据传递给调用者。

希望这可以帮助你。

完整的源代码和示例在此处:

import { HttpService } from "@nestjs/axios";
import { BadRequestException, Controller, Get, Res } from "@nestjs/common";
import { catchError, firstValueFrom, map, Observable } from "rxjs";
import { createReadStream } from "fs";

@Controller('pdf-from-external-url')
export class PdfFromExternalUrlController {

  constructor(
    private httpService: HttpService
  ) {
  }

  // Simulated external document server that responds as stream!
  @Get()
  async getPDF(@Res() res) {
    const file = createReadStream(process.cwd() + '/files/test.pdf');
    return file.pipe(res);
  }

  @Get('indirect-pdf')
  async findPdf(@Res() res) {
    const pdfResponse = await firstValueFrom(this.httpService
      .get(`http://localhost:3000/pdf-from-external-url`, { responseType: "stream" })
      .pipe(map((response) => response))
      .pipe(
        catchError((e) => {
          throw new BadRequestException('Failed to get PDF.');
        }),
      ));
    
    // APPROACH (1) - deliver your PDF as stream to your caller
    // pdfResponse.data.pipe(res);
    // END OF APPROACH (1)
    
    // APPROACH (2) - read whole stream content on server and then deliver it
    const streamReadPromise = new Promise<Buffer>((resolve) => {
      const chunks = [];
      pdfResponse.data.on('data', chunk => {
        chunks.push(Buffer.from(chunk));
      });
      pdfResponse.data.on('end', () => {
        resolve(Buffer.concat(chunks));
      });
    });

    const pdfData = await streamReadPromise;

    res.header('Content-Type', 'application/pdf')
    res.send(pdfData);
    // END OF APPROACH (2)
  }
}

我在pdfResponse.data.on('data', chunk => {这一行遇到了错误。 - S. Varner
错误消息为:"pdfResponse.data.on 不是一个函数",级别为 "error"。 - S. Varner
我有一个要传递的令牌,所以我必须在responseType中包含headers。axios调用返回了一个良好的响应,但是我不明白在执行const streamReadPromise时应该发生什么,它并没有起作用。 - S. Varner
哦,让我猜一下可能性很小的情况:你的服务器没有用流式响应,而是用完整的 PDF 文档二进制响应。这个答案是关于处理流响应的(即文档的部分内容以块的形式传输)。 - Gradient
但是,为了避免冒险尝试,您能否构建SO问题或分享您正在尝试创建的代码示例(我将很乐意尝试重现问题并检查为什么流上的'on'不是函数错误不存在)。 - Gradient
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接