简体   繁体   中英

Blob from buffer returns corrupted pdf

Trying to make a pdf with Puppeteer on Firebase Functions: send in html snippet, get a pdf back.

But the pdf is corrupted. The problem is, I think, with returning the file / buffer.

// Server:

// setup
const func = require('firebase-functions');
const pptr = require('puppeteer');
const opts = { memory: '1GB', regions: ['europe-west3'] };
const call = func.runWith(opts).https.onCall
let browser = pptr.launch({ args: ['--no-sandbox'] });

// onCall
exports.makePdf = call(async (data) => {
  // this works, does open the page testing with { headless: false }
  const brws = await (await browser).createIncognitoBrowserContext();
  const page = await brws.newPage();    
  await page.setContent(data, { waitUntil: 'domcontentloaded' });
  const file = await page.pdf({ format: 'A4' });
  await brws.close();
  // the problem is returning the file
  return file
});

When file is logged on the server it's a buffer <Buffer 25 50 44 46 2d 31 2e ... 11150 more bytes> , but when logged on the client it's an object, in dec and not hex { data: {0: 37, 1: 80, 2: 68, 3: 70, ... }} .

Convert that back to buffer? Convert back to hex? Which buffer?

// Client:

// send html and receive the file
let html = '<div><h1>Hi</h1></div>';
let file = ((await fns.makePdf(html))).data;
// also tried buffer = new Buffer.from(JSON.stringify(file));
let blob = new Blob(file, { type: 'application/pdf' });

// download the file
let a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = `${name}.pdf`;
a.click();

Or is the pdf corrupted because I'm downloading it wrong ( createObjectURL )? Or onCall functions can't be used this way?

Point is, don't know why it's not working. Thanks for your help

Ok. /insert cursing the gods/

Server:

So it was ok, you CAN use onCall instead of onRequest, because you CAN create a pdf in the end from the json response. The reason onCall is a bit better is because you don't need to bother with cors or headers, Google does it for you.

// setup
const func = require('firebase-functions');
const pptr = require('puppeteer');
const opts = { memory: '1GB', regions: ['europe-west3'] };
const call = func.runWith(opts).https.onCall

// this runs outside of the function because it might not be killed off
// if the function is called quickly in succession (lookup details on 
// google cloud functions docs)
let browser = pptr.launch({ args: ['--no-sandbox'] });

// the main function
exports.makePdf = call(async (data) => {
  // prep puppeteer so it can print the pdf
  const brws = await (await browser).createIncognitoBrowserContext();
  const page = await brws.newPage();    
  await page.setContent(data);
  const file = await page.pdf({
    format: 'A4',
    printBackground: false,
    margin: { left: 0, top: 0, right: 0, bottom: 0 }
  });
  brws.close();
  // and just return the stream
  return file
});

Client:

The trick was on the client. I'll note with comments.

// so this is inside some function, has to be because it needs to be async
var a, blob, buffer, d, file, html, i, result;
// this html is for testing, it would obviously be gathered in another way
html = '<div><h1>Hi</h1></div>';

// we call the function from the client
// fns is my shorthand object defined on Google Functions initialization
// it is literally: f.httpsCallable('makePdf') where f is the functions object
// you get from initialization
file = ((await fns.makePdf(html))).data;
// additional .data is needed because onCall automatically wraps

// now the point

// 1. convert object to array
result = []; for (i in file) {
  d = file[i]; result.push(d);
}
// 2. convert that to a Uint8Array
buffer = new Uint8Array(result);
// 3. create a blob, BUT the buffer needs to go inside another array
blob = new Blob([buffer], { type: 'application/pdf' });

// finally, download it
a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = `${path}.pdf`;
a.click();

I found this project similar than yours and I found a difference, in the buffer return you need to specify the headers and the HTTP code, the client browser possibly is misinterpreting the object from the server.

This is the return segment of the other project

const pdfBuffer = await page.pdf({ printBackground: true });

res.set("Content-Type", "application/pdf");
res.status(200).send(pdfBuffer);

Everything in your code looks right and if transforming the PDF from dec to hex return the same string, I think that could be a good workarround.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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