AWS Lambdaでpuppeteerを動かしスクリーンショットをs3に保存する
AWS Toolkit for Visual Studio Codeがリリースされたのでserverlessなアプリを作りながら試してみることに。
前から気になっていたHeadless Chromeをjsから操作できるpuppeteerを使って、スクリーンショットをs3に保存するAPIを作ることにした。
「Create new SAM Application」でLambdaアプリの雛形を作り、VS Code上からLambdaのデプロイができた。
とはいえ毎回デプロイ対象の指定が必要になるので、ターミナルから実行したほうが楽だと感じた。
実装
先人の記事を参考に、puppeteer用のLambda Layerを追加し、雛形のtemplateを修正。
動かすのを優先で今回は雛形のままHelloWorldFunctionというキーにしてしまった。
# template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
LambdaLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: aws-lambda-puppeteer
CompatibleRuntimes:
- nodejs8.10
ContentUri: lambda-layer.zip
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Policies:
- S3CrudPolicy:
BucketName: {s3_bucket_name}
CodeUri: src/
Handler: app.lambda_handler
Runtime: nodejs8.10
Layers:
- !Ref LambdaLayer
MemorySize: 512
Timeout: 120
Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
Variables:
PARAM1: VALUE
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /
Method: get
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
lambdaのコードは以下。
// src/app.js
process.env["HOME"] = process.env["LAMBDA_TASK_ROOT"];
const chromium = require("chrome-aws-lambda");
const puppeteer = require("puppeteer-core");
const aws = require("aws-sdk");
const crypto = require("crypto");
const s3 = new aws.S3();
const bucket = "s3_bucket_name";
const generateHash = data => {
const sha256 = crypto.createHash("sha256");
sha256.update(data);
return sha256.digest("hex") + ".jpg";
};
exports.lambda_handler = async (event, context) => {
const query = event.queryStringParameters;
const url = query && query.url ? query.url : "https://www.amazon.co.jp/";
let result = null;
let browser = null;
try {
browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath,
headless: chromium.headless
});
let page = await browser.newPage();
await page.goto(url);
const screenshot = await page.screenshot({ fullPage: true, type: "jpeg" });
const fileName = generateHash(url);
await s3
.putObject({
Bucket: bucket,
Key: fileName,
Body: screenshot
})
.promise();
const download_url = s3.getSignedUrl("getObject", {
Bucket: bucket,
Key: fileName,
Expires: 86400
});
result = { download_url: download_url };
} catch (error) {
return context.fail(error);
} finally {
if (browser !== null) {
await browser.close();
}
}
return context.succeed({
statusCode: 200,
body: JSON.stringify(result)
});
};
日本語が文字化けするので対応方法にハマったが、下記の記事の方法で手軽に対応できた。
.fontsディレクトリにfontファイルを設置し、環境変数HOMEを設定する。
src
├── .fonts
| └── NotoSansCJKjp-Regular.otf
├── app.js
デプロイ用にシェルスクリプトを追加。
# deploy.sh
sam package \
--template-file template.yaml \
--output-template-file packaged.yaml \
--s3-bucket sam-package-bucket
sam deploy \
--template-file packaged.yaml \
--stack-name puppeteer \
--capabilities CAPABILITY_IAM
template.yamlでAPIキーを設定するのが手間だったので、今回はAPI Gatewayのダッシュボードから手動で設定した。
これで任意のURLに対してスクリーンショットを撮れるAPIができた。
以下のようにAPIキーを指定してcurlでリクエストを送るとスクリーンショットのURLが返ってくる。
curl 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod?url=https://www.amazon.co.jp/' --header 'x-api-key:API_KEY'
{"download_url":"https://~"}
追記
Expiresの指定より前にトークンが期限切れになってしまう問題があったので、専用のIAMユーザを利用する方法に変更した。