Arhn - архитектура программирования

ExpressJS и PDFKit — создание PDF-файла в памяти и отправка клиенту для загрузки

В моем маршрутизаторе api есть функция generatePDF, которая предназначена для использования модуля PDFKit для создания файла PDF в памяти и отправки клиенту для загрузки, а не только для отображения.

In api.js:

var express = require('express');
var router = express.Router();

const PDFDocument = require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
    var myDoc = new PDFDocument({bufferPages: true});
    myDoc.pipe(res);
    myDoc.font('Times-Roman')
         .fontSize(12)
         .text(`this is a test text`);
    myDoc.end();
    res.writeHead(200, {
        'Content-Type': 'application/pdf',
        'Content-disposition': 'attachment;filename=test.pdf',
        'Content-Length': 1111
    });
    res.send( myDoc.toString('base64'));
});

module.exports = router;

Это не работает. Сообщение об ошибке: (node:11444) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

Как я могу решить проблему и заставить ее работать?

Кроме того, уместным будет вопрос, как я могу отделить бизнес-логику генерации PDF от маршрутизатора и связать их в цепочку?


Ответы:


1

Полное решение.

var express = require('express');
var router = express.Router();

const PDFDocument =  require('pdfkit');

router.get('/generatePDF', async function(req, res, next) {
var myDoc = new PDFDocument({bufferPages: true});

let buffers = [];
myDoc.on('data', buffers.push.bind(buffers));
myDoc.on('end', () => {

    let pdfData = Buffer.concat(buffers);
    res.writeHead(200, {
    'Content-Length': Buffer.byteLength(pdfData),
    'Content-Type': 'application/pdf',
    'Content-disposition': 'attachment;filename=test.pdf',})
    .end(pdfData);

});

myDoc.font('Times-Roman')
     .fontSize(12)
     .text(`this is a test text`);
myDoc.end();
});

module.exports = router;
25.09.2019
  • Спасибо. На что ссылается тело? Это mydoc? 25.09.2019
  • mydoc выглядит как объект, поэтому узнайте, как получить из него фактические данные. 25.09.2019
  • Добавлено полное решение 25.09.2019
  • Спасибо за Ваш ответ. Это работает! Единственное, я все еще не понимаю, как привязать событие end к myDoc. Это нигде не упоминалось в документации PDFKit, и в документации упоминается использование doc.pipe(res) для записи pdf-документа в http-ответ. Но это не было использовано в вашем ответе. 25.09.2019
  • Еще одна вещь находится в конце моего первоначального вопроса. how I can separate the business logic of PDF generation from the router and chain them up? Я хотел бы взять параметр из req.query и передать его логике генерации PDF. Стоило ли это отдельного вопроса? 25.09.2019

  • 2

    Сначала рекомендую создать сервис для комплекта PDF. А затем контроллер на маршрут, который вы хотите.

    Я использовал get-stream, чтобы упростить задачу.

    Он также отвечает на ваш вопрос к принятому ответу:

    как я могу отделить бизнес-логику генерации PDF от маршрутизатора и связать их?

    Это мое профессиональное решение:

    import PDFDocument from 'pdfkit';
    import getStream from 'get-stream';
    import fs from 'fs';
    
    
    export default class PdfKitService {
      /**
       * Generate a PDF of the letter
       *
       * @returns {Buffer}
       */
      async generatePdf() {
        try {
          const doc = new PDFDocument();
    
          doc.fontSize(25).text('Some text with an embedded font!', 100, 100);
    
          if (process.env.NODE_ENV === 'development') {
            doc.pipe(fs.createWriteStream(`${__dirname}/../file.pdf`));
          }
    
          doc.end();
    
          const pdfStream = await getStream.buffer(doc);
    
          return pdfStream;
        } catch (error) {
          return null;
        }
      }
    }
    
    

    И затем метод контроллера:

    (...) 
    
      async show(req, res) {
        const pdfKitService = new PdfKitService();
        const pdfStream = await pdfKitService.generatePdf();
    
        res
          .writeHead(200, {
            'Content-Length': Buffer.byteLength(pdfStream),
            'Content-Type': 'application/pdf',
            'Content-disposition': 'attachment;filename=test.pdf',
          })
          .end(pdfStream);
    
     
      }
    

    И, наконец, маршрут:

    routes.get('/pdf', FileController.show);
    
    29.01.2021

    3

    Для тех, кто не хочет тратить оперативную память на буферизацию PDF-файлов и сразу отправлять куски клиенту:

        const filename = `Receipt_${invoice.number}.pdf`;
        const doc = new PDFDocument({ bufferPages: true });
        const stream = res.writeHead(200, {
          'Content-Type': 'application/pdf',
          'Content-disposition': `attachment;filename=${filename}.pdf`,
        });
        doc.on('data', (chunk) => stream.write(chunk));
        doc.on('end', () => stream.end());
    
        doc.font('Times-Roman')
          .fontSize(12)
          .text(`this is a test text`);
        doc.end();
    
    05.03.2021

    4

    Вы можете использовать поток больших двоичных объектов следующим образом.

    ссылка: https://pdfkit.org/index.html

    const PDFDocument = require('pdfkit');
    
    const blobStream  = require('blob-stream');
    
    // create a document the same way as above
    
    const doc = new PDFDocument;
    
    // pipe the document to a blob
    
    const stream = doc.pipe(blobStream());
    
    // add your content to the document here, as usual
    
    doc.font('fonts/PalatinoBold.ttf')
       .fontSize(25)
       .text('Some text with an embedded font!', 100, 100);
    
    // get a blob when you're done
    doc.end();
    stream.on('finish', function() {
      // get a blob you can do whatever you like with
      const blob = stream.toBlob('application/pdf');
    
      // or get a blob URL for display in the browser
      const url = stream.toBlobURL('application/pdf');
      iframe.src = url;
    });
    

    передайте все ваши данные в формате PDF в ваш большой двоичный объект, а затем запишите их в файл или URL-адрес. или вы можете сохранить PDF-файл непосредственно в облачном хранилище, таком как хранилище Firebase, и отправить ссылку для скачивания клиенту.

    Если вы хотите динамически генерировать PDF-файлы, вы также можете попробовать библиотеку html-pdf в узле, которая позволяет вам создавать PDF-файл из шаблона HTML и добавлять в него динамические данные. Также он более надежен, чем pdfkit https://www.npmjs.com/package/html-pdf Также обратитесь к этой ссылке Создать pdf-файл с помощью pdfkit и отправить его в браузер в nodejs-expressjs

    25.09.2019
  • Спасибо! Первоначальный вопрос был на стороне сервера узла, а не на стороне браузера. Я посмотрю на библиотеку html-pdf. 25.09.2019
  • да, решение на стороне браузера, но как только вы создадите большой двоичный объект, вы можете создать поток записи в своей файловой системе, записать непосредственно в файл и сохранить в локальном хранилище или напрямую записать поток в облачное хранилище. 25.09.2019
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..