5

I found a 4 year old project that's supposed to install phantomjs on lambda, but either I'm doing something wrong, or things have changed since the repo was created and it no longer works. When I clone and deploy this repository, I get this error trying to run phantomjs:

{
  "errorType": "Error",
  "errorMessage": "write EPIPE",
  "code": "EPIPE",
  "stack": [
    "Error: write EPIPE",
    "    at WriteWrap.afterWrite [as oncomplete] (net.js:779:14)"
  ],
  "errno": "EPIPE",
  "syscall": "write"
}

{
  "errorType": "Error",
  "errorMessage": "html-pdf: Received the exit code '127'\n./phantomjs_lambda/phantomjs_linux-x86_64: error while loading shared libraries: libfreetype.so.6: cannot open shared object file: No such file or directory\n",
  "stack": [
    "Error: html-pdf: Received the exit code '127'",
    "./phantomjs_lambda/phantomjs_linux-x86_64: error while loading shared libraries: libfreetype.so.6: cannot open shared object file: No such file or directory",
    "",
    "    at ChildProcess.respond (/var/task/node_modules/html-pdf/lib/pdf.js:121:31)",
    "    at ChildProcess.emit (events.js:189:13)",
    "    at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12)"
  ]
}

If I use the latest linux binary instead of the one that comes with this repo, I get a different but similar error about a different .so file missing.

For context, the reason I want to install phantomjs is because I want to use the node html-pdf library in my lambda.

I am on a Mac.

As an answer, I'm not looking to get past this specific error only to discover the next one. I'm looking for an answer that gets me a working phantomjs on aws lambda. If there is another node "html to pdf"/"html to png" library that doesn't require phantomjs to work and easily runs on AWS Lambda, that would be an acceptable solution, too.

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356
  • Seems like using docker maybe your best bet to use native applications – Luis Estevez Jun 27 '19 at 17:08
  • Have you tried installing phantom-prebuilt? – Luis Estevez Jun 27 '19 at 17:18
  • @LuisEstevez I'm on a mac. How would I install the phantom-prebuilt in a way that would work on the lambda linux OS? – Daniel Kaplan Jun 27 '19 at 17:43
  • Phantom is quite old and unstable. You probably try to use puppet-chrome instead, you can have a look at [this guide](https://itnext.io/html-to-pdf-using-a-chrome-puppet-in-the-cloud-de6e6a0dc6d7). – Kane Jun 28 '19 at 16:28
  • Thanks @Kane but I don't think that helps because my library is hardcoded to use phantomjs. – Daniel Kaplan Jun 28 '19 at 17:35
  • Did you look into using puppeteer to do the PDF conversion / screenshotting. As it's actively maintained, Lambda support might be better than the one of Phantom https://github.com/RafalWilinski/serverless-puppeteer-layers – m90 Jul 01 '19 at 19:24
  • @m90 I have not. We are generating an html file in our server and trying to convert that to a pdf. Not sure if that introduces a problem. – Daniel Kaplan Jul 01 '19 at 19:27

2 Answers2

39

One solution is to include the libraries required by PhantomJS in your Lambda function .zip file. I'll break this down into several steps.

Determine which libraries to include

Your error message states that a shared library is missing. Let's find exactly what PhantomJS is looking for by installing it in a Docker container and checking its runtime dependencies. The nodejs10.x AWS Lambda Runtime uses Amazon Linux 2, but the steps are similar for earlier runtimes.

On your host system, run an Amazon Linux 2 Docker container:

$ docker run -it --rm amazonlinux:2.0.20190508

In the container, install PhantomJS and inspect its dependencies:

bash-4.2$ yum install -y bzip2 tar
bash-4.2$ curl -LO https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
bash-4.2$ tar xf phantomjs-2.1.1-linux-x86_64.tar.bz2
bash-4.2# ldd ./phantomjs-2.1.1-linux-x86_64/bin/phantomjs 
        linux-vdso.so.1 (0x00007ffdd251f000)
        libz.so.1 => /lib64/libz.so.1 (0x00007f35d0439000)
        libfontconfig.so.1 => not found
        libfreetype.so.6 => not found
        ...

We see that libfontconfig and libfreetype are missing.

Install the libraries

Next, we'll download and extract the required libraries, then copy them to the host system. Note that libfontconfig depends on libexpat, so we'll install that as well.

In the container:

bash-4.2$ yum install -y yum-utils rpmdevtools
bash-4.2$ cd /tmp
bash-4.2$ yumdownloader fontconfig.x86_64 freetype.x86_64 expat.x86_64
bash-4.2$ rpmdev-extract *.rpm

Copy the libraries into a directory:

bash-4.2$ mkdir -p /deps
bash-4.2$ cp /tmp/*/usr/lib64/* /deps

Also copy the fontconfig configuration file:

bash-4.2$ cp /tmp/*/etc/fonts/fonts.conf /deps

Next, on the host, get the Docker container ID and copy the files from the container to the host. Below, lambda-node-phantom is the directory where you cloned the https://github.com/TylerPachal/lambda-node-phantom repository:

$ cd lambda-node-phantom
$ docker ps
$ docker cp <CONTAINER_ID>:/deps/ .
$ mv deps/* . && rmdir deps

Update the Lambda

Update the PhantomJS binary in your directory to the version downloaded in the Docker container above. Make sure that index.js refers to the correct name for the latest version; in version 2.1.1 it's named phantomjs.

Next, add the following line to index.js so that fontconfig finds fonts.conf in the Lambda root directory:

process.env['FONTCONFIG_PATH'] = process.env['LAMBDA_TASK_ROOT'];

Finally, re-create the Lambda function .zip file, including the native libraries and font.conf:

$ zip -y /path/to/lambda-node-phantom-dist.zip .

The -y option stores symbolic links as links instead of the referenced file. To save space, make sure that the .git directory is not included in the .zip file.

Test the Lambda

In the AWS Lambda console, upload the new Lambda function .zip file and test the Lambda function. PhantomJS should now run without errors, and your function should return "hello from phantom!" If not, check the stderr returned by the PhantomJS child process for information.

It's likely that you'll need to modify fonts.conf and/or include additional files for fonts to be rendered properly.

Max Smolens
  • 3,461
  • 26
  • 34
  • Nice! It turns out that this library uses phantomjs-prebuilt instead of just phantomjs. When I run `ldd phantomjs` on it in `/node_modules/phantomjs-prebuilt/bin`, it says, "not a dynamic executable." I don't know much about C, but do you know why executing that prebuilt (whatever that means) would look for a .so file? Will the steps you've outlined be significantly different? – Daniel Kaplan Jul 02 '19 at 01:10
  • `phantomjs-prebuilt/bin/phantomjs` is a script that runs the executable. The executable itself is actually `phantomjs-prebuilt/lib/phantom/bin/phantomjs`. – Max Smolens Jul 02 '19 at 13:23
  • That's interesting. I didn't know that. I'm going to *try* to try this today. Thanks for the help :) – Daniel Kaplan Jul 02 '19 at 16:04
  • Perhaps there's some context I don't understand with your answer, but I set `process.env['LD_LIBRARY_PATH'] = path.join(process.env['LAMBDA_TASK_ROOT'], '...');` as a step but you didn't mention it. – Daniel Kaplan Jul 02 '19 at 17:32
  • In my experience I haven't had to explicitly set `LD_LIBRARY_PATH` with the .so files in `LAMBDA_TASK_ROOT`, i.e. `/var/task`. If you see different behavior, maybe try moving the files to `lib` instead. I think `$LAMBDA_TASK_ROOT/lib` is in `LD_LIBRARY_PATH` by default. – Max Smolens Jul 02 '19 at 18:21
  • I read that to be the case for layers, but I wasn't sure if that applied to a non-layered lambda. – Daniel Kaplan Jul 02 '19 at 18:28
  • can you explain `process.env['FONTCONFIG_PATH'] = path.join(process.env['LAMBDA_TASK_ROOT'], 'fonts');`? As far as I can tell, you don't have a directory or a file named fonts. You have a file named fonts.conf. Is this looking for the conf file name without the extension? These docs say it's supposed to point to a directory: https://www.freedesktop.org/software/fontconfig/fontconfig-user.html – Daniel Kaplan Jul 02 '19 at 18:30
  • fonts.conf is from the "copy the fontconfig configuration file" step. It should end up in your function .zip file. Indeed, because there is no `fonts` directory, I updated the answer to set `FONTCONFIG_PATH` to `LAMBDA_TASK_ROOT` instead of `$LAMBDA_TASK_ROOT/fonts`. – Max Smolens Jul 02 '19 at 18:46
  • Nice. So you got us (what I hope is) 99% of the way there. The thing is, now when I generate the PDF on lambda (or in the docker container from your steps), the pdf has no text, only styling. But, your steps got me past the error. I think this means that phantomjs is expecting actual fonts to exist on the system that aren't there. If I put this in a debian docker container instead of an aws one, it renders with the text. Do you know how I can find/install/deploy the fonts that phantomjs uses into the lambda? Thanks for everything! – Daniel Kaplan Jul 02 '19 at 21:11
  • Glad you made progress! I think that phantomjs relies on fontconfig. I haven't worked through this myself, but this documentation looks promising: https://gist.github.com/nat-n/c3429d29f2478ccb3de243810bb12956#configuring--fontconfig. You can install fonts (perhaps copied from Debian) in the Amazon Linux Docker container, possibly update the font cache, package the fonts and perhaps the cache dir in your Lambda, then ensure that `dir` and `cachedir` in your packaged fonts.conf are correct. There may be more configuration, such as font name aliases, to make sure phantomjs renders correctly. – Max Smolens Jul 03 '19 at 14:02
  • @Diego, I have those files here https://github.com/naeemshaikh27/phantom-lambda-fontconfig-pack – Naeem Shaikh Jan 21 '20 at 10:26
  • @NaeemShaikh thanks it worked! but... i think i'm missing some fonts, my pdf is really simple just letters, but black squares show instead of each letter, I added your fonts i don't know which are missing. – Diego Jan 22 '20 at 16:20
  • @Diego just check your html which font it uses, or inspect your html to see computed font family, it may be times new roman if nothing was specified.. One more thing, if you are not using lambda layer, then change the font..conf file to correct the path, also the env variables should point correctly.. In my case I created a folder named nodejs, which is lambda layer and within that a folder named fonts and there I had everything I shared on git.. So my path was opt/nodejs/fonts – Naeem Shaikh Jan 23 '20 at 07:00
  • do you have the default font? because I don't use a font and i cannot check the pdf font. – Diego Jan 24 '20 at 05:04
  • It's solved a part of a problem, but I get this error after this config. `phantomjs: error while loading shared libraries: libuuid.so.1: cannot open shared object file: No such file or directory` Any idea why is happends ? – Julian Porras Apr 28 '20 at 18:47
  • @JulianPorras libuuid is a required library for phantomjs. You can include it with other libraries. `yumdownloader libuuid.x86_64` – 9years Jul 24 '20 at 06:30
  • After fixing dependencies and fonts, I can get correct images (png, jpg) but pdf doesn't work. Did someone experience with no pdf generated? My env: aws Lambda, node 12 (basically any node gives the same result). I did a global search in lambda container and could not find the file – Yurii Maksimov Mar 10 '21 at 16:38
2

According to your log error, it looks like libfreetype.so.6 is missing from your lambda execution runtime.

You may need to create a custom lambda layer embedding this shared library. Afterwards, you may need to update your LD_LIBRARY_PATH so it also points to the shared library's directory. You may also alternately include it in your lambda deployment package.

According to AWS official doc:

To include libraries in a layer, place them in one of the folders supported by your runtime.

All – bin (PATH), lib (LD_LIBRARY_PATH)

Link here: https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html

Make sure you embed a libfreetype.so.6 compiled for Amazon linux (or Amazon linux 2).

Execution runtimes here: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html

Good luck !

Community
  • 1
  • 1
greyside
  • 141
  • 1
  • 4
  • Thanks, this helps, I am able to generate PDF after setting `LD_LIBRARY_PATH`, but all I am getting is a pdf without any text, just the styles like border, etc are there – Naeem Shaikh Jan 21 '20 at 10:46
  • @NaeemShaikh Did you ever get around that issue? I'm at the same point where the PDF is being rendered but fonts don't show up. – Tom Nijs Mar 11 '20 at 16:05
  • 1
    @TomNijs You need to add fonts, I compiled a list of what needs to be done here: https://github.com/naeemshaikh27/phantom-lambda-fontconfig-pack – Naeem Shaikh Mar 12 '20 at 17:11