├── .eslintrc.json ├── .gitignore ├── .npmignore ├── README.md ├── bin └── website-to-pdf.js ├── cdk.json ├── functions ├── .dockerignore ├── Dockerfile ├── package-lock.json ├── package.json └── website-to-pdf-function.js ├── jest.config.js ├── lib └── website-to-pdf-stack.js ├── package-lock.json ├── package.json └── test └── lambda-screenshot.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "import", 5 | "json-format" 6 | ], 7 | "rules": { 8 | "import/no-unresolved": [ 9 | "error", 10 | { 11 | "ignore": [ "config/", "generators/", "managers/", "settings/"] 12 | } 13 | ], 14 | "import/no-extraneous-dependencies": ["off", {"packageDir": "./publication"}], 15 | "prefer-destructuring": ["error", {"object": false, "array": false}], 16 | "function-paren-newline": ["error", "consistent"], 17 | "max-len": ["error", { "code": 150 }], 18 | "no-new": "off" 19 | }, 20 | "env": { 21 | "mocha": true 22 | }, 23 | "overrides": [ 24 | { 25 | "files": [ 26 | "**/__tests__/**" 27 | ], 28 | "env": { 29 | "jest": true 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # CDK asset staging directory 4 | .cdk.staging 5 | cdk.out 6 | 7 | .idea 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # CDK asset staging directory 2 | .cdk.staging 3 | cdk.out 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Website to PDF using AWS Lambda 2 | 3 | This is a project that converts a website to PDF using AWS Lambda. The infrastructure is defined using AWS CDK. 4 | This uses the [Function URL](https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/) feature of Lambda functions. 5 | 6 | ## Useful commands 7 | 8 | * `cdk deploy` deploy this stack to your default AWS account/region 9 | * `cdk diff` compare deployed stack with current state 10 | * `cdk synth` emits the synthesized CloudFormation template 11 | 12 | ## Execution 13 | 14 | Check the CDK deployment output to find the value of `WebsiteToPDFStack.websiteToPDFFunctionURL`. 15 | Use the `url` query parameter to provide the URL of the website to be converted to PDF. 16 | 17 | For example: `https://youruniqueid.lambda-url.ca-central-1.on.aws/?url=https://jobinbasani.com/` 18 | -------------------------------------------------------------------------------- /bin/website-to-pdf.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const cdk = require('aws-cdk-lib'); 4 | const { WebsiteToPDFStack } = require('../lib/website-to-pdf-stack'); 5 | 6 | const app = new cdk.App(); 7 | new WebsiteToPDFStack(app, 'WebsiteToPDFStack', { 8 | /* If you don't specify 'env', this stack will be environment-agnostic. 9 | * Account/Region-dependent features and context lookups will not work, 10 | * but a single synthesized template can be deployed anywhere. */ 11 | 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | 16 | /* Uncomment the next line if you know exactly what Account and Region you 17 | * want to deploy the stack to. */ 18 | // env: { account: '123456789012', region: 'us-east-1' }, 19 | 20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 21 | }); 22 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "node bin/website-to-pdf.js", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "jest.config.js", 11 | "package*.json", 12 | "yarn.lock", 13 | "node_modules", 14 | "test" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 19 | "@aws-cdk/core:stackRelativeExports": true, 20 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 21 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 22 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 23 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 24 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 25 | "@aws-cdk/core:checkSecretUsage": true, 26 | "@aws-cdk/aws-iam:minimizePolicies": true, 27 | "@aws-cdk/core:target-partitions": [ 28 | "aws", 29 | "aws-cn" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /functions/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /functions/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/nodejs:14 2 | 3 | COPY website-to-pdf-function.js package.json package-lock.json ${LAMBDA_TASK_ROOT} 4 | 5 | RUN npm install 6 | 7 | CMD [ "website-to-pdf-function.handler" ] 8 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-to-pdf-function", 3 | "version": "1.0.0", 4 | "description": "Lambda to convert a website to PDF", 5 | "main": "website-to-pdf-function.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "jobinbasani", 10 | "license": "ISC", 11 | "dependencies": { 12 | "chrome-aws-lambda": "^10.1.0", 13 | "puppeteer": "^13.7.0" 14 | }, 15 | "devDependencies": { 16 | "eslint": "8.14.0", 17 | "eslint-config-airbnb-base": "15.0.0", 18 | "eslint-plugin-import": "2.26.0", 19 | "eslint-plugin-jest": "26.1.5", 20 | "eslint-plugin-json-format": "2.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /functions/website-to-pdf-function.js: -------------------------------------------------------------------------------- 1 | const chromium = require('chrome-aws-lambda'); 2 | 3 | exports.handler = async (event) => { 4 | const url = event.queryStringParameters.url; 5 | 6 | const browser = await chromium.puppeteer.launch({ 7 | args: chromium.args, 8 | headless: true, 9 | ignoreHTTPSErrors: true, 10 | defaultViewport: chromium.defaultViewport, 11 | executablePath: await chromium.executablePath, 12 | }); 13 | 14 | const page = await browser.newPage(); 15 | await page.goto(url, { waitUntil: 'networkidle0' }); 16 | 17 | const buffer = await page.pdf({ 18 | scale: 1, 19 | displayHeaderFooter: false, 20 | }); 21 | 22 | return { 23 | statusCode: 200, 24 | headers: { 25 | 'Content-type': 'application/pdf', 26 | }, 27 | body: buffer.toString('base64'), 28 | isBase64Encoded: true, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node' 3 | } 4 | -------------------------------------------------------------------------------- /lib/website-to-pdf-stack.js: -------------------------------------------------------------------------------- 1 | const cdk = require('aws-cdk-lib'); 2 | const lambda = require('aws-cdk-lib/aws-lambda'); 3 | const path = require('path'); 4 | 5 | class WebsiteToPDFStack extends cdk.Stack { 6 | /** 7 | * 8 | * @param {Construct} scope 9 | * @param {string} id 10 | * @param {StackProps=} props 11 | */ 12 | constructor(scope, id, props) { 13 | super(scope, id, props); 14 | 15 | const websiteToPDFFunction = new lambda.DockerImageFunction(this, 'websiteToPDFFunction', { 16 | functionName: 'websiteToPDFFunction', 17 | timeout: cdk.Duration.minutes(4), 18 | memorySize: 512, 19 | code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../functions')), 20 | }); 21 | 22 | const websiteToPDFFunctionURL = websiteToPDFFunction.addFunctionUrl({ 23 | authType: lambda.FunctionUrlAuthType.NONE, 24 | }); 25 | 26 | new cdk.CfnOutput(this, 'websiteToPDFFunctionURL', { 27 | value: websiteToPDFFunctionURL.url, 28 | description: 'Website to PDF Function URL', 29 | }); 30 | } 31 | } 32 | 33 | module.exports = { WebsiteToPDFStack }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-to-pdf", 3 | "version": "0.1.0", 4 | "bin": { 5 | "lambda-screenshot": "bin/website-to-pdf.js" 6 | }, 7 | "scripts": { 8 | "build": "echo \"The build step is not required when using JavaScript!\" && exit 0", 9 | "cdk": "cdk", 10 | "test": "jest" 11 | }, 12 | "devDependencies": { 13 | "aws-cdk": "2.22.0", 14 | "esbuild": "^0.14.38", 15 | "eslint": "8.14.0", 16 | "eslint-config-airbnb-base": "15.0.0", 17 | "eslint-plugin-import": "2.26.0", 18 | "eslint-plugin-jest": "26.1.5", 19 | "eslint-plugin-json-format": "2.0.1", 20 | "jest": "^26.4.2" 21 | }, 22 | "dependencies": { 23 | "aws-cdk-lib": "2.22.0", 24 | "constructs": "^10.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/lambda-screenshot.test.js: -------------------------------------------------------------------------------- 1 | // const cdk = require('aws-cdk-lib'); 2 | // const { Template } = require('aws-cdk-lib/assertions'); 3 | // const LambdaScreenshot = require('../lib/lambda-screenshot-stack'); 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/lambda-screenshot-stack.js 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new LambdaScreenshot.LambdaScreenshotStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | --------------------------------------------------------------------------------