简体   繁体   English

NestJS/Express 从外部 URL 返回 PDF 文件作为响应

[英]NestJS/Express return PDF file from external URL in response

I'm calling an external API that returns a PDF file and I want to return this PDF file in my controller function response. I'm calling an external API that returns a PDF file and I want to return this PDF file in my controller function response.

In My controller class:在我的 controller class 中:

  @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);
  }

In My service class:在我的服务 class 中:

  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.');
          }),
        ),
    );
  }

However I'm getting a blank PDF file in my response.但是,在我的回复中,我得到了一个空白的 PDF 文件。

The internal API call works fine, I've tested it from Postman and the PDF file is correct.内部 API 调用工作正常,我已经从 Postman 测试它,并且 PDF 文件是正确的。

What am I doing wrong?我究竟做错了什么?

I have tested and reproduced the issue you are describing.我已经测试并重现了您描述的问题。

The reason is that your external server responds with PDF as a stream and your solution is not handling it.原因是您的外部服务器响应 PDF 作为 stream 而您的解决方案没有处理它。

First, since the response is the stream, you need to inform Axios about that (HTTP service) by changing:首先,由于响应是 stream,您需要通过更改通知 Axios 关于该(HTTP 服务):

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

to:至:

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

After that, you have two approaches (depending on what you need):之后,您有两种方法(取决于您的需要):

  1. You can pipe that stream to your main response (hence deliver to stream to the caller of your service).您可以 pipe 将 stream 发送到您的主要响应(因此将 stream 发送给您的服务的调用者)。

  2. You can collect whole stream data from the document server and then deliver the final buffer data to the caller.您可以从文档服务器收集整个 stream 数据,然后将最终缓冲区数据传递给调用者。

I hope this helps.我希望这有帮助。

Complete source code with example is here:带有示例的完整源代码在这里:

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)
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM