Dean
Dean

Reputation: 939

Chrome Binary Not Found on AWS Lambda Using Serverless and chrome-aws-lambda Node package

I have created a simple application that accepts a URL and converts it to a PDF. It stores the resultant PDF in an S3 bucket and returns the URL of the PDF. It uses Chrome (running headless) to convert the URL to a PDF. I used the serverless framework, AWS Lambda, and the chrome-aws-lambda npm package. When I execute this setup locally using serverless it all works great. I can use postman to make a request with a URL and it returns the URL of the resultant PDF. When I deploy this setup to AWS Lambda, it returns a 502 internal server error response. When I look at the AWS logs for my application I see the following:

{
    "errorType": "Error",
    "errorMessage": "ENOENT: no such file or directory, open '//../bin/chromium.br'",
    "code": "ENOENT",
    "errno": -2,
    "syscall": "open",
    "path": "//../bin/chromium.br",
    "stack": [
        "Error: ENOENT: no such file or directory, open '//../bin/chromium.br'"
    ]
}

Here is the main handler for the application:

import AWS from 'aws-sdk'
import middy from 'middy'
import chromium from 'chrome-aws-lambda'
import {
  cors,
  doNotWaitForEmptyEventLoop,
  httpHeaderNormalizer,
  httpErrorHandler
} from 'middy/middlewares'

const handler = async (event) => {
  // Request body is passed in as a JSON encoded string in 'event.body'
  const data = JSON.parse(event.body)

  const executablePath = event.isOffline
    ? './node_modules/puppeteer/.local-chromium/linux-706915/chrome-linux/chrome'
    : await chromium.executablePath

  const browser = await chromium.puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: executablePath,
    headless: true
  })

  const page = await browser.newPage()

  await page.goto(data.url, {
    waitUntil: ['networkidle0', 'load', 'domcontentloaded']
  })

  const pdfStream = await page.pdf()

  var upload = new AWS.S3.ManagedUpload({
    params: {
      Bucket: 'bucketname',
      Body: pdfStream,
      Key: `${Date.now()}-result.pdf`,
      ACL: 'public-read'
    }
  })

  var promise = upload.promise()

  return promise.then(
    function (data) {
      console.log(data.Location)
      return {
        statusCode: 200,
        body: data.Location
      }
    },
    function (err) {
      console.log('Error', err)
      return {
        statusCode: 500,
        body: err
      }
    }
  )
}

export const generate = middy(handler)
  .use(httpHeaderNormalizer())
  .use(cors())
  .use(doNotWaitForEmptyEventLoop())
  .use(httpErrorHandler())

Here is the serverless framework configuration file:

service: print-pdf

package:
  individually: true

provider:
  name: aws
  runtime: nodejs12.x
  region: us-east-2
  stage: prod

plugins:
  - serverless-bundle # Package our functions with Webpack
  - serverless-offline

# Create our resources with separate CloudFormation templates
resources:
  # API Gateway Errors
  - ${file(resources/api-gateway-errors.yml)}
  # S3
  - ${file(resources/s3-bucket.yml)}

# 'iamRoleStatements' defines the permission policy for the Lambda function.
# In this case Lambda functions are granted with permissions to access S3.
iamRoleStatements:
  - Effect: Allow
    Action:
      - s3:GetObject
      - s3:PutObject
    Resource: "arn:aws:s3:us-east-2:*:*"

functions:
  give-me-the-pdf:
    handler: handler.generate
    events:
      - http:
          path: pdf
          method: post
          cors: true
          authorizer: aws_iam

Here is the package.json:

{
  "name": "print-pdf",
  "version": "1.0.0",
  "main": "handler.js",
  "author": "Dean Andreakis <[email protected]>",
  "license": "MIT",
  "private": true,
  "scripts": {
    "test": "serverless-bundle test"
  },
  "dependencies": {
    "chrome-aws-lambda": "^1.20.4",
    "middy": "^0.28.4",
    "puppeteer-core": "^1.20.0"
  },
  "devDependencies": {
    "aws-sdk": "^2.597.0",
    "jest": "^24.9.0",
    "puppeteer": "^2.0.0",
    "serverless": ">=1.48.1",
    "serverless-bundle": "^1.2.5",
    "serverless-dotenv-plugin": "^2.1.1",
    "serverless-offline": "^5.3.3"
  }
}

Why is Chrome not found when deployed to AWS versus running locally?

Upvotes: 5

Views: 7421

Answers (2)

j-petty
j-petty

Reputation: 3026

You could use serverless-webpack and configure chrome-aws-lamdba as an external.

There's a similar issue here.

Add this to your webpack config:

externals: ['aws-sdk', 'chrome-aws-lambda']

Upvotes: 7

Noel Llevares
Noel Llevares

Reputation: 16067

serverless-bundle only includes the JS code that you use in your handler and strips everything else to minimize your bundle. That means the chrome binaries are excluded.

To include those binaries, add the following to your serverless.yml:

custom:
  bundle:
    copyFiles:
      - from: 'node_modules/chrome-aws-lambda/bin/*'
        to: './'

Upvotes: 0

Related Questions