如何从控制器返回PDF文件

时间:2018-11-27 17:00:22

标签: javascript node.js nestjs

我尝试使用NestJs从Controller端点返回PDF文件。如果不设置Content-type标头,则getDocumentFile返回的数据可以很好地返回给用户。但是,当我添加标题时,返回的值似乎是GUID的某种奇怪形式,响应始终如下所示:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,其中x是小写的十六进制字符。它似乎也与处理函数的实际返回值完全无关,因为我什至在根本不返回任何东西时得到了这种奇怪的GUID东西。

当不设置Content-type: application/pdf时,该函数返回缓冲区的数据就很好,但是我需要设置标题,以使浏览器将响应识别为PDF文件,这对我的使用很重要情况。

控制器看起来像这样:

@Controller('documents')
export class DocumentsController {
  constructor(private documentsService: DocumentsService) {}

  @Get(':id/file')
  @Header('Content-type', 'application/pdf')
  async getDocumentFile(@Param('id') id: string): Promise<Buffer> {
    const document = await this.documentsService.byId(id)
    const pdf = await this.documentsService.getFile(document)

    // using ReadableStreamBuffer as suggested by contributor
    const stream = new ReadableStreamBuffer({
      frequency: 10,
      chunkSize: 2048,
    })
    stream.put(pdf)
    return stream
  }
}

和我的DocumentsService这样:

@Injectable()
export class DocumentsService {
  async getAll(): Promise<Array<DocumentDocument>> {
    return DocumentModel.find({})
  }

  async byId(id: string): Promise<DocumentDocument> {
    return DocumentModel.findOne({ _id: id })
  }

  async getFile(document: DocumentDocument): Promise<Buffer> {
    const filename = document.filename
    const filepath = path.join(__dirname, '..', '..', '..', '..', '..', 'pdf-generator', 'dist', filename)

    const pdf = await new Promise<Buffer>((resolve, reject) => {
      fs.readFile(filepath, {}, (err, data) => {
        if (err) reject(err)
        else resolve(data)
      })
    })
    return pdf
  }
}

我最初只是返回了缓冲区(return pdf),但结果与上面的尝试相同。在NestJs的存储库中,用户建议使用上述方法,这显然对我也不起作用。请参阅GitHub线程here

3 个答案:

答案 0 :(得分:3)

我知道这个老话题。但这可能会帮助某人。 类似于@Victor

  @Get('pdf')
  @HttpCode(201)
  @Header('Content-Type', 'image/pdf')
  @Header('Content-Disposition', 'attachment; filename=test.pdf')
  public pdf() {
    return fs.createReadStream('./test.pdf');
  }

答案 1 :(得分:0)

对我有用。

@Get('pdf')
@HttpCode(HttpStatus.OK)
@Header('Content-Type', 'application/pdf')
@Header('Content-Disposition', 'attachment; filename=test.pdf')
pdf() {
    return createReadStream('./nodejs.pdf');
}

顺便说一句,我认为最好使用Stream而不是readFile。因为它会将文件的所有内容加载到RAM中。

答案 2 :(得分:0)

您可以使用现成的装饰器@Res,这是我的工作解决方案:

Controller(NestJs):

async getNewsPdfById(@Param() getNewsParams: GetNewsPdfParams, @Req() request: Request, @Res() response: Response): Promise<void> {
  const stream = await this.newsService.getNewsPdfById(getNewsParams.newsId, request.user.ownerId);

  response.set({
    'Content-Type': 'image/pdf',
  });

  stream.pipe(response);
}

在我的情况下,流变量只是由html-pdf库创建的就绪流,因为我是通过html https://www.npmjs.com/package/html-pdf创建pdf的,但是如何创建流并不重要。事实是,您应该使用@Res装饰器并将其通过管道传递,因为它具有本机的NestJs解决方案。

这也是代码如何在客户端声明文件: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743

无论如何,您可以尝试以下一种方法:

@Controller('documents')
export class DocumentsController {
  constructor(private documentsService: DocumentsService) {}

  @Get(':id/file')
  async getDocumentFile(@Param('id') id: string, @Res res: Response): Promise<Buffer> {
    const document = await this.documentsService.byId(id)
    const pdf = await this.documentsService.getFile(document)


    const stream = new ReadableStreamBuffer({
      frequency: 10,
      chunkSize: 2048,
    })

    res.set({
      'Content-Type': 'image/pdf',
    });

    stream.pipe(res);
  }
}