Reputation: 939
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
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
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