【メモ】AWS lambda layerでpuppeteer環境つくって、S3にスクショをアップロードするlambda関数をSAMでdeployして、ついでにAPI Gatewayで公開

# 概要
* lambdaでpuppeteerを動かす
* chromeはlayerを利用
    * [chrome-aws-lambda](https://github.com/alixaxel/chrome-aws-lambda) を layerで追加
* lambda functionはSAMを利用してdeploy
* （おまけ） API gatewayで公開

# 前提
* aws cliでawsにフルにアクセスできる状況になっている

# コード
* https://github.com/junara/screenshot2s3

# 参考
以下の3つの記事を合体した感じです

* [AWS Lambda Layers上でHeadless Chromeを動かすいくつかの方法](https://qiita.com/tabimoba/items/9ffe4ba6f2af28c702af)
* [AWS LambdaでPuppeteerを動かす](https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d)
    * [chrome-aws-lambda](https://github.com/alixaxel/chrome-aws-lambda)
* [Lambda Layers をnode.js(SAM)で試してみる](https://qiita.com/monamu/items/96d63dd2151a8ab7e6cf)


# 手順
## chromeとpuppeterのlayerを追加

[AWS LambdaでPuppeteerを動かす](https://qiita.com/kodai-saito/items/9051d2b30a29c7d64f7d) この記事の __Layerのアップロード__ までやる

## SAMテンプレート作成

* [Lambda Layers をnode.js(SAM)で試してみる](https://qiita.com/monamu/items/96d63dd2151a8ab7e6cf) を参考。
* `Layers` の箇所は上記で作成したLambdaのLayersのARNをコピーして貼り付けましょう

`bucket` は後述のlabmdaと同じものです。ご自分のbucketを指定してください。（なければ作りましょう！）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/cd798b42-83b6-6d40-8794-f398dbc712f2.png)


```template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: screenshot2s3
Resources:
  Screenshot2S3Function:
    Type: AWS::Serverless::Function
    Properties:
      Policies:
        - S3CrudPolicy:
            BucketName: screenshot2s3
      FunctionName: screenshot2s3
      CodeUri: src
      Handler: index.handler
      MemorySize: 1600
      Runtime: nodejs8.10
      Timeout: 30
      Events:
        Get:
          Type: Api
          Properties:
            Path: /
            Method: get
      Layers:
        - "arn:aws:lambda:ap-northeast-1:あなたの:layer:myPup:1"
```

`Events` の箇所はおまけで使うAPI Gatewayでアクセスするためです。

## Lambda Function追加

* `functions/pupfunc/index.js` にLambda関数を書きます。
* [usage](https://github.com/alixaxel/chrome-aws-lambda#usage) をもとに、screenshotをキャプチャするコードに書き換えました。
* 日本語フォントが使えるように下記の記述を追加しました

```javascript
await chromium.font('https://raw.githack.com/googlei18n/noto-cjk/master/NotoSansCJKhk-Regular.otf');
```

[https://github.com/googlei18n/noto-cjk](https://github.com/googlei18n/noto-cjk)から選んで
[https://raw.githack.com](https://raw.githack.com/)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/3022c087-ad63-a618-cdba-99d37ca573d3.png)
に書いて `chromium.font(ここ)` に貼るだけ。

`bucket` はご自分のbucketを指定してください。

```src/index.js
const chromium = require('chrome-aws-lambda');
const puppeteer = require('puppeteer-core');
const aws = require('aws-sdk');
const s3 = new aws.S3();
const bucket = 'screenshot2s3';
const crypto = require('crypto');

exports.handler = async (event, context) => {
  const query = event.queryStringParameters;
  const displaySize = {
    pc: {width: 1024, height: 1024},
    iPhoneX: {width: 375, height: 812},
    iPad: {width: 768, height: 1024}
  };
  const devise = displaySize['pc'];

  const url = query.url;
  let browser = null;
  await chromium.font('https://rawcdn.githack.com/googlei18n/noto-cjk/cf29231ab8029678af4bbc1a9480e2b296a5b2d3/NotoSansCJKhk-Regular.otf');

  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);
    page.setViewport({width: devise.width, height: devise.height})
    const screenshot = await page.screenshot();
    const filename = randomFilename(screenshot);
    let params = {Bucket: bucket, Key: filename, Body: screenshot};
    await s3.putObject(params).promise();
    const download_params = {Bucket: bucket, Key: filename, Expires: 86400};
    const download_url = s3.getSignedUrl('getObject', download_params);
    return ({
      "statusCode": 200,
      "body": JSON.stringify({download_url: download_url})
    })
  } catch (error) {
    return context.fail(error);
  } finally {
    if (browser !== null) {
      await browser.close();
    }
  }
};

const randomFilename = (data) => {
  const sha256 = crypto.createHash('sha256');
  sha256.update(data);
  return sha256.digest('hex') + '.png'
}
```

`const query = event.queryStringParameters;` の箇所は、後述のAPI gatewayでアクセスするためです。 lambdaだけであれば、 `event.url` でシンプルに受け取ってOK。

### 参考
* https://confrage.jp/aws-s3%E3%81%AE%E6%9C%9F%E9%99%90%E4%BB%98%E3%81%8Durl%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%80%81s3%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%83%80%E3%82%A6%E3%83%B3/
* https://aws.amazon.com/jp/premiumsupport/knowledge-center/malformed-502-api-gateway/
* https://dev.classmethod.jp/server-side/serverless/20171203-updates-about-aws-serverless-application-model/
* https://sasaplus1.hatenadiary.com/entry/20120211/1328964435
* https://qiita.com/naoki_koreeda/items/c2a32198c86e8d9a5bb6
* https://qiita.com/Quantum/items/91ad6b6b788bf4051055
* https://confrage.jp/amazon-api-gateway%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9%E3%81%A8%E3%82%AF%E3%82%A8%E3%83%AA%E6%96%87%E5%AD%97%E5%88%97%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AE%E6%B8%A1%E3%81%97%E6%96%B9/
* https://dev.classmethod.jp/server-side/serverless/20171203-updates-about-aws-serverless-application-model/
* 
## deploy

`--s3-bucket` の指定はご自分のバケットつかってください。
SAMのデプロイは一度S3にアップロードが必要です。なければ、[バケットはここをみてつくってきださい](https://qiita.com/junara/items/2d08c41f42487a097c57#%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E4%BD%9C%E3%82%8B)。コマンドライン一行ですよー。

```shell
sam package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket つくったバケット

sam deploy \
    --template-file packaged.yaml \
    --stack-name sam-app \
    --capabilities CAPABILITY_IAM
```

## 確認
Lambdaを開いて先ほど作った関数がデプロイされていて、layerが割り当てられていることを確認して下さい。

テストのデータは下記のようなデータを指定してください。 `"https://xxxxxx.co.jp"` は、なんでもいいです。

```
{
  "queryStringParameters": {
    "url": "https://xxxxxx.co.jp"
  }
}
```

lambdaのテスト実行して、ご自分のバケットに、xxxx.pngがアップロードされていれば成功です。例はyahooのサイトです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/6a4cf8df-4f3f-1fad-6fe8-1ef33f7fd3a7.png)

以上。

# おまけ
ドメインとってAPI gatewayで公開しました。
## 使い方
* `https://webscreenshot.work` に url パラメータにスクショ撮りたいアドレスを書いて下さい。
* リクエストヘッダーに `x-api-key: 7eXZ6duNSJ754gLUt5ENX3mSXgnv3UqX7QYsB5ZF` をいれてください。
* 出力されるjsonの `download_url` のvalueにアクセスするとダウンロードできます。
* APIキー毎に回数を制限しているので、エラーが発生したらそれは回数制限に達したからです。翌日アクセスお願いします。
 
curlでかくとこんな感じ。

```console
curl -v https://webscreenshot.work/?url=https://yahoo.co.jp -H "x-api-key: 7eXZ6duNSJ754gLUt5ENX3mSXgnv3UqX7QYsB5ZF"

省略

* Connection #0 to host webscreenshot.work left intact
{"download_url":"https://screenshot2s3.s3.ap-northeast-1.amazonaws.com/b35d3bda10ac3714eb9b03b19e361cc8d801ddf61eb165fd7b489988276d2d06.png?AWSAccessKeyId=ASIAREY2BQFLWVZRL7GP&Expires=1555342761&Signature=3MkWy5EjqOchNCzJsj1qOvvan1w%3D&x-amz-security-token=FQoGZXIvYXdzECEaDLlrEieFh6KQaAnxIiLmAamAhWMbYrvRuLbJcQPyL7p%2BCx4CYVG%2BkLrRliDzXpxnmDepaxumy7kWPIyXLmqpAk6hbg2lbQhUDciPs4bgnrPr%2FjZm%2FsxtVvtOsnvP3nQRC%2FswlLsodcUR%2FicANlR2Po4yIUriJ6ZxIhj19AVZo5ZfaVdhNqF%2B0uUPRwqDZNucCEcxjuRBNDlLU%2FYk2WS%2BZatRq1geZJNBq%2Ffc%2FM5mcad%2F%2BGHE1c7ipEhVmF1FhIvyUR31GK%2F3dyhkS3iXut8lWKrxrVW0TmGH9xVmHrDp8821lg7P%2Bd6F%2FBKfT4TWQDqahk09ARnoKI6ozeUF"}%
```


## 手順
SAMでdeployするとすでにAPI gatewayにリソースが追加されていますので、それをベースに使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/bc17ad6d-2981-50d6-6e59-3bfb3bf92e4f.png)


APIの必要性を trueにします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/76c42586-9cc3-b832-d1b9-0da4597b4dcc.png)

* APIをProdでデプロイします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/14c6c81e-60fb-7cf2-fec3-d64c9fd208d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/6a17198f-a298-24a3-9bf1-d3d957d5205a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/a815f381-9627-895b-c9dc-537e8941a1b5.png)

* [API GatewayをカスタムドメインでHTTPS化する](https://dev.classmethod.jp/cloud/api-gateway-custom-domain-ssl/) を参考に作業します。私の場合は value-domainでドメインを用意していたので、 [こちら](http://yamada.daiji.ro/blog/?p=87)も参考にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/436cd571-fde2-0017-7e7a-370dbb4044cb.png)

* お金かかるのでAPIのアクセス制限します
  * https://qiita.com/kawaz/items/07d67a851fd49c1c183e
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/2647833f-a443-36a8-bca1-e1a5242e1d7d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124445/73522f5c-bc9c-3df5-1a05-9b290adecc19.png)

以上