node-html-pdf icon indicating copy to clipboard operation
node-html-pdf copied to clipboard

Error deploying to Vercel / AWS Lambda

Open JuanM04 opened this issue 3 years ago • 27 comments

In my PC (Kubuntu 20.04) it works perfectly. The problem appears when I deploy to Vercel. To begin with, I need to set phantomPath, otherwise I get a "write EPIPE" error:

pdf.create(html, {
  phantomPath: path.resolve(
    process.cwd(),
    "node_modules/phantomjs-prebuilt/bin/phantomjs"
  ),
})

Now, if I run it, it throws:

html-pdf: Received the exit code '1'
Error executing phantom at /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
Error: spawn /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT
    at Process.ChildProcess._handle.onexit (internal/child_process.js:267:19)
    at onErrorNT (internal/child_process.js:469:16)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
events.js:287
      throw er; // Unhandled 'error' event
      ^

Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
    at doWrite (_stream_writable.js:399:19)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:695:22)
    at Socket.emit (events.js:310:20)
    at addChunk (_stream_readable.js:286:12)
    at readableAddChunk (_stream_readable.js:268:9)
    at Socket.Readable.push (_stream_readable.js:209:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:186:23)
Emitted 'error' event on Socket instance at:
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at Socket.onerror (_stream_readable.js:729:7)
    at Socket.emit (events.js:310:20)
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at onwriteError (_stream_writable.js:418:5)
    at onwrite (_stream_writable.js:445:5)
    at doWrite (_stream_writable.js:399:11)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:695:22) {
  code: 'ERR_STREAM_DESTROYED'
}

JuanM04 avatar Aug 04 '20 03:08 JuanM04

if you do:

pdf.create(html, {
  phantomPath: path.resolve(
    process.cwd(),
    "node_modules/phantomjs-prebuilt/bin/phantomjs"
  ),
})

it works?

matiastucci avatar Aug 13 '20 20:08 matiastucci

I already did that... It's in the first codeblock of the issue

JuanM04 avatar Aug 13 '20 21:08 JuanM04

yeah, I know. I'm asking if that worked for you.

matiastucci avatar Aug 13 '20 21:08 matiastucci

I forgot to mention it, but I found a (hacky) solution:

First, you add a fonts.conf wherever you have your fonts (remember to change the dir):

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <dir>/var/task/my-font-directory/</dir>
  <cachedir>/tmp/fonts-cache/</cachedir>
  <config></config>
</fontconfig>

Then, you download these libs (the same from here, but in a ZIP) and put them in a folder. I put them in /bins.

Finally, before you run pdf.create(), set these env vars:

process.env.FONTCONFIG_PATH = path.join(process.cwd(), "my-font-directory");
process.env.LD_LIBRARY_PATH = path.join(process.cwd(), "bins");
Dev Mode

If your PDF throws some error in Dev mode, you can set the env vars only in production mode. Like this:

if (process.env.NODE_ENV === "production") {
  process.env.FONTCONFIG_PATH = path.join(process.cwd(), "my-font-directory");
  process.env.LD_LIBRARY_PATH = path.join(process.cwd(), "bins");
}

Also, remember to change the phantomPath!!!

pdf.create(html, {
  phantomPath: path.resolve(
    process.cwd(),
    "node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs"
  ),
})

JuanM04 avatar Aug 13 '20 21:08 JuanM04

Thanks for the details! I just tried it and still didn't work 😢

Btw, how are you creating the PDF? toStream, toBuffer or toFile?

matiastucci avatar Aug 14 '20 09:08 matiastucci

I'm using toBuffer. Could you share the error?

JuanM04 avatar Aug 14 '20 12:08 JuanM04

Same :(

wn /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT
    at Process.ChildProcess._handle.onexit (internal/child_process.js:267:19)
    at onErrorNT (internal/child_process.js:469:16)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
events.js:292
      throw er; // Unhandled 'error' event
      ^
Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
    at doWrite (_stream_writable.js:399:19)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:717:22)
    at Socket.emit (events.js:315:20)
    at addChunk (_stream_readable.js:295:12)
    at readableAddChunk (_stream_readable.js:271:9)
    at Socket.Readable.push (_stream_readable.js:212:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:186:23)
Emitted 'error' event on Socket instance at:
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at Socket.onerror (_stream_readable.js:753:7)
    at Socket.emit (events.js:315:20)
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at onwriteError (_stream_writable.js:418:5)
    at onwrite (_stream_writable.js:445:5)
    at doWrite (_stream_writable.js:399:11)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:717:22) {
  code: 'ERR_STREAM_DESTROYED'
}
    at ChildProcess.respond (/var/task/node_modules/html-pdf/lib/pdf.js:121:31)
    at ChildProcess.emit (events.js:315:20)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12)

I'm doing:

    process.env.LD_LIBRARY_PATH = path.join(process.cwd(), 'bins')
    process.env.FONTCONFIG_PATH = path.join(process.cwd(), 'fonts')

    pdf
      .create(html, {
        phantomPath: path.resolve(
          process.cwd(),
          'node_modules/phantomjs-prebuilt/bin/phantomjs'
        ),
      })
      .toBuffer(...)

and I have all the so files in my root /bins, same with fonts in /fonts and my /fonts/fonts.conf looks like:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <dir>/var/task/fonts/</dir>
  <cachedir>/tmp/fonts-cache/</cachedir>
  <config></config>
</fontconfig>

matiastucci avatar Aug 14 '20 12:08 matiastucci

Oh, wait. I think I was wrong about the phantomPath. Try this: node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs

(sorry for no answering sooner, my PC crashed)

JuanM04 avatar Aug 16 '20 13:08 JuanM04

It works 💪 😄 Thanks!

matiastucci avatar Aug 16 '20 15:08 matiastucci

Perfect! I edited the comment for new people. Now it should be right

JuanM04 avatar Aug 16 '20 15:08 JuanM04

H @JuanM04 i know this is closed, im using this library in a Nextjs app, im rendering a certificate in pdf, locally it works just fine, but when deployed in vercel is giving me the following error:

2020-10-05T22:05:45.416Z	a88c28c9-9c68-451d-9255-1e49d7ebb1cd	ERROR	Unhandled error during request: Error: write EPIPE
    at afterWriteDispatched (internal/stream_base_commons.js:154:25)
    at writeGeneric (internal/stream_base_commons.js:145:3)
    at Socket._writeGeneric (net.js:786:11)
    at Socket._write (net.js:798:8)
    at doWrite (_stream_writable.js:403:12)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at PDF.PdfExec [as exec] (/var/task/node_modules/html-pdf/lib/pdf.js:141:15)
    at PDF.PdfToBuffer [as toBuffer] (/var/task/node_modules/html-pdf/lib/pdf.js:44:8)
    at /var/task/.next/serverless/pages/pdf/[token].js:414:70 {
  errno: 'EPIPE',
  code: 'EPIPE',
  syscall: 'write'
}

i did everything you mentioned to fix the problem with phantomjs, this is what i did

const componentToPDFBuffer = (component) => {
  return new Promise((resolve, reject) => {
    const html = renderToStaticMarkup(component);
    const path = require('path');
    process.env.FONTCONFIG_PATH = path.join(process.cwd(), "fonts");
    process.env.LD_LIBRARY_PATH = path.join(process.cwd(), "bins"); 
    const options = {
      format: 'A4',
      orientation: 'landscape',
      type: 'pdf',
      phantomPath: path.resolve(
          process.cwd(),
          'node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs'
        ),
      timeout: 30000,
    };

    pdf.create(html, options).toBuffer((err, buffer) => {
      if (err) {
        return reject(err);
        }
    
        return resolve(buffer);
    });
  });
}

I dont know if you have faced this issue with the same library. Thnks if you can help

Omonroy36 avatar Oct 05 '20 22:10 Omonroy36

Are you trying to use it in the client?

JuanM04 avatar Oct 05 '20 22:10 JuanM04

Is being render on the server side using Next, this is the code that displays the pdf.

export const getServerSideProps = async (context) => {
    const { res, query } = context;
    const token = query.token;
    const response = await fetch(`${process.env.BC_HOST}/${token}`);
    const data = await response.json();
    if (token !== "" && !data.status_code) {
        const buffer = await pdfHelper.componentToPDFBuffer(
            <PDFLayout lang={query.lang} token={token}>
                {query.style === "modern" ? <ModernCertificate data={{
                    ...data,
                    token: token,
                    lang: query.lang || "en",
                    strings: strings[query.lang || "en"]
                }}
                /> : <DefaultCertificate data={{
                    ...data,
                    token: token,
                    lang: query.lang || "en",
                    strings: strings[query.lang || "en"]
                }}
                    />}
            </PDFLayout>
        );
        // with this header,the browser will open the pdf directly      
        res.setHeader('Content-Type', 'application/pdf');
        // output the pdf buffer. once res.end is triggered, it won't trigger the render method
        res.end(buffer);
    }
    return {
        props: {
            data: data
        }
    };
}

Omonroy36 avatar Oct 05 '20 22:10 Omonroy36

Hmm... I don't know why it doesn't work, but try generating the PDF inside pages/api

JuanM04 avatar Oct 06 '20 00:10 JuanM04

Thanks ill try it, another thing fonts folder its only suppose to have the content you suggest or other files?

Omonroy36 avatar Oct 06 '20 01:10 Omonroy36

Is the same as the .local/share/fonts folder. You can have anything in it, but you should only have fonts

JuanM04 avatar Oct 06 '20 01:10 JuanM04

Thank you, ill try it.

Omonroy36 avatar Oct 06 '20 23:10 Omonroy36

@Omonroy36 Did you succeed?

quitequinn avatar Oct 20 '21 02:10 quitequinn

@Omonroy36 Did you succeed?

I did yes

Omonroy36 avatar Oct 20 '21 04:10 Omonroy36

@Omonroy36 Have you had any luck using a stream? I'm really stuck here, also the fonts I'm using are all base64 in css.

quitequinn avatar Oct 20 '21 19:10 quitequinn

I ended up using puppeteer-- was really straight forward.

let puppeteer;

if (process.env.AWS_LAMBDA_FUNCTION_VERSION) {
  // running on the Vercel platform.
  chrome = require('chrome-aws-lambda');
  puppeteer = require('puppeteer-core');
} else {
  // running locally.
  puppeteer = require('puppeteer');
}

const handler = async (req, res) => {

    // https://github.com/vercel/vercel/discussions/4903#discussioncomment-234166
    const browser = await puppeteer.launch({
        args: [...chrome.args, '--hide-scrollbars', '--disable-web-security'],
        defaultViewport: chrome.defaultViewport,
        executablePath: await chrome.executablePath,
        headless: true,
        ignoreHTTPSErrors: true,
      });
    const page = await browser.newPage();
    await page.goto('https://www.example.com');
    const pdf = await page.pdf({
        format: 'A4',
        orientation: 'portrait',
        margin: {
            top: "10mm",
            right: "10mm",
            bottom: "10mm",
            left: "10mm",
        },
        timeout: 30000,
    })
    await browser.close();
    res.setHeader('Content-disposition', `attachment; filename="test.pdf`)
    res.setHeader('Content-Type', 'application/pdf')
    res.end(pdf)
}

export default handler

Borrowed a little from here https://github.com/vercel/vercel/discussions/4903#discussioncomment-234166

stubbzilla8 avatar Dec 14 '21 06:12 stubbzilla8

Hi,

I'm still getting the below error:

{
    "errorType": "Error",
    "errorMessage": "write EPIPE",
    "code": "EPIPE",
    "errno": -32,
    "syscall": "write",
    "stack": [
        "Error: write EPIPE",
        "    at afterWriteDispatched (internal/stream_base_commons.js:156:25)",
        "    at writeGeneric (internal/stream_base_commons.js:147:3)",
        "    at Socket._writeGeneric (net.js:798:11)",
        "    at Socket._write (net.js:810:8)",
        "    at writeOrBuffer (internal/streams/writable.js:358:12)",
        "    at Socket.Writable.write (internal/streams/writable.js:303:10)",
        "    at PDF.PdfExec [as exec] (/var/task/node_modules/html-pdf/lib/pdf.js:156:15)",
        "    at PDF.PdfToBuffer [as toBuffer] (/var/task/node_modules/html-pdf/lib/pdf.js:46:8)",
        "    at /var/task/node_modules/@yorck/booking-client/dist/booking.js:214:42",
        "    at new Promise (<anonymous>)"
    ]
}

See below the phantom path and ENV vars. Please note I'm using my own npm package where I have the PDF generation code, as I need to reuse it across several repos.

phantomPath= /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
FONTCONFIG_PATH: '/var/task/node_modules/@yorck/booking-client/fonts'
LD_LIBRARY_PATH: '/var/task/node_modules/@yorck/booking-client/bin'

I'm deploying it to AWS Lambda (Node.js 14.x) through the serverless framework.

This error is driving me crazy, I've been stuck with it for a while!

Thanks!

cgonzalezsan avatar Dec 26 '21 23:12 cgonzalezsan

@cgonzalezsan I'm having the same issue when using serverless-plugin-include-dependencies and serverless-plugin-common-excludes they both have the effect to remove phantomjs-prebuilt

you can try

option1: remove serverless-plugin-include-dependencies and serverless-plugin-common-excludes from serverless.yml

option2: add

package:
  patterns:
    - node_modules/html-pdf/**
    - node_modules/phantomjs-prebuilt/** 

this will force add html-pdf and phantomjs-prebuilt in artifact bundle (this works in my simple setup lambda but doesn't work for lambda with lots of dependencies somehow)

option3: only remove serverless-plugin-common-excludes from serverless.yml

option4:

  1. manually include all the patterns from https://github.com/dougmoscrop/serverless-plugin-common-excludes/blob/master/common-excludes.js
  2. add custom pattern in option2
  3. remove serverless-plugin-common-excludes

option 4 gives me the optimized bundle size and makes sure html-pdf and phantomjs-prebuilt included in artifact bundle

Emergencyq avatar Feb 25 '22 04:02 Emergencyq

Hi,

I'm still getting the below error:

{
    "errorType": "Error",
    "errorMessage": "write EPIPE",
    "code": "EPIPE",
    "errno": -32,
    "syscall": "write",
    "stack": [
        "Error: write EPIPE",
        "    at afterWriteDispatched (internal/stream_base_commons.js:156:25)",
        "    at writeGeneric (internal/stream_base_commons.js:147:3)",
        "    at Socket._writeGeneric (net.js:798:11)",
        "    at Socket._write (net.js:810:8)",
        "    at writeOrBuffer (internal/streams/writable.js:358:12)",
        "    at Socket.Writable.write (internal/streams/writable.js:303:10)",
        "    at PDF.PdfExec [as exec] (/var/task/node_modules/html-pdf/lib/pdf.js:156:15)",
        "    at PDF.PdfToBuffer [as toBuffer] (/var/task/node_modules/html-pdf/lib/pdf.js:46:8)",
        "    at /var/task/node_modules/@yorck/booking-client/dist/booking.js:214:42",
        "    at new Promise (<anonymous>)"
    ]
}

See below the phantom path and ENV vars. Please note I'm using my own npm package where I have the PDF generation code, as I need to reuse it across several repos.

phantomPath= /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
FONTCONFIG_PATH: '/var/task/node_modules/@yorck/booking-client/fonts'
LD_LIBRARY_PATH: '/var/task/node_modules/@yorck/booking-client/bin'

I'm deploying it to AWS Lambda (Node.js 14.x) through the serverless framework.

This error is driving me crazy, I've been stuck with it for a while!

Thanks!

Hey, Any update on this I'm getting the same for the 12 and 14 node versions on AWS-lambda.

harshmalvi avatar Mar 03 '22 07:03 harshmalvi

I forgot to mention it, but I found a (hacky) solution:

First, you add a fonts.conf wherever you have your fonts (remember to change the dir):

Hey bro. Where i could find the fonts.conf file?

BrunoMont2003 avatar Apr 20 '22 16:04 BrunoMont2003

I forgot to mention it, but I found a (hacky) solution: First, you add a fonts.conf wherever you have your fonts (remember to change the dir):

Hey bro. Where i could find the fonts.conf file?

https://github.com/naeemshaikh27/phantom-lambda-fontconfig-pack

Kihoshi avatar Jun 15 '22 09:06 Kihoshi

Following solved it for me on Vercel with node v18, and latest puppeteer-core. Credits stefanjudis.

Somewhere in your helpers.js

const chromium = require('@sparticuz/chromium-min');
const puppeteer = require('puppeteer-core');
let _page;

exports.getBrowser =  async function () {
  return puppeteer.launch({
    args: [...chromium.args, '--hide-scrollbars', '--disable-web-security'],
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(
      `https://github.com/Sparticuz/chromium/releases/download/v119.0.2/chromium-v119.0.2-pack.tar`
    ),
    headless: chromium.headless,
    ignoreHTTPSErrors: true,
  });
}

exports.getPage = async function getPage() {
  if (_page) return _page;

  const browser = await module.exports.getBrowser();
  _page = await browser.newPage();
  return _page;
}

Using it like so:

  const page = await getPage();
  await page.emulateMediaType('screen');        // To reflect CSS used for screens instead of print
  const pdf = await page.pdf({                 
    format: info.format || 'A4',
    orientation: 'portrait',
    timeout: 30000,
  })

ceduth avatar Dec 31 '23 16:12 ceduth