AWS Lambda で S3 に置かれた Markdown を HTML に変換する

# tl;dr

ES2015 で書くので[前回](http://qiita.com/y13i/items/fb90858cdede3eb4dbdf)参照。 S3 バケットに Markdown を置いたら Lambda Function が発火して、別のパスに HTML が出力されるようにしてみる。[ソースはこちら](https://github.com/y13i/lambda-markdown2html)。

# やりたいこと

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/78376/4934602f-f239-9756-f660-c5f148e25f4c.png)

ユーザーが Markdown をアップロードするのは `/.src/` 以下、出力された HTML が出力されるのはバケット直下とします。

例: `s3://orenobucket/.src/orenopage/index.md` をアップロードすると `s3://orenobucket/orenopage/index.html` が出力される。

# Lambda Function を書く

[前回](http://qiita.com/y13i/items/fb90858cdede3eb4dbdf)、 ES2015 で書いて簡単にデプロイできるようになったので、このリポジトリの内容から引き続き作業する前提で進めます。

まず、 S3 オブジェクトへの読み書きをしなくてはならないので、 AWS SDK を追加。 `aws-sdk` は Lambda の Node.js にはグローバルで導入されているので必須ではないですが…

```shell-session
$ npm install aws-sdk --save
```

さらに、 Lambda が使用する IAM Role で `s3:GetObject` と `s3:PutObject` ができるようにしておくのを忘れずに。

もちろん Markdown を HTML に変換できなくてはならないので、今回は [evilstreak/markdown-js](https://github.com/evilstreak/markdown-js) を使用。

```shell-session
$ npm install markdown-js --save
```

せっかく Babel を使うのだから async/await を使ってみる。このために Babel の Stage 3 用 preset が必要。

```shell-session
$ npm install babel-preset-stage-3 --save-dev
```

Babel の設定ファイルにも `stage-3` preset を追加。

```json:.babelrc
{
  "presets": [
    "es2015",
    "stage-3"
  ]
}
```

## できたのがこちら

```js:functions/markdown2html/src/index.js
import lambda from 'apex.js'
import util   from 'util'
import AWS    from 'aws-sdk'

import {markdown} from 'markdown'

import 'babel-polyfill'

const s3 = new AWS.S3();

export default lambda(async (evt, ctx) => {
  let bucketName = evt.Records[0].s3.bucket.name;
  let srcKey     = evt.Records[0].s3.object.key;
  let destKey    = srcKey.replace(/^.src\//, '').replace(/\.md$/, '.html');

  let srcData = await s3.getObject({
    Bucket: bucketName,
    Key:    evt.Records[0].s3.object.key,
  }).promise();

  let markdownBody = (new Buffer(srcData.Body)).toString();

  let html = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>Converted from s3://${bucketName}/${evt.Records[0].s3.object.key}</title>
      </head>
      <body>
        ${markdown.toHTML(markdownBody)}
      </body>
    </html>
  `

  let destData = await s3.putObject({
    Bucket:      bucketName,
    Key:         destKey,
    Body:        html,
    ContentType: 'text/html',
  }).promise();

  return(destData);
});
```

async/await を使用することで、 S3 周りの非同期処理が極めてすっきり書けていることに注目されたい。

[ソース全部はこちら](https://github.com/y13i/lambda-markdown2html)。

**追記:** [`aws-sdk@2.3.0` で Promise がサポートされた](http://aws.amazon.com/releasenotes/8589740860839559)ので、自前で promisify する必要がなくなりました。最高。

# S3 の Notification を設定する

S3 バケットの設定でイベントトリガーを追加する。

<img width="622" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/78376/6dc1b263-68a4-d2a0-1415-6cdc409bc0c9.png">

# 試してみる

```markdown:test/index.md
# THE MOST EPIC MARKDOWN EVER

THE MOST EPIC PARAGRAPH!!

- THE MOST EPIC LIST ITEM 1
- THE MOST EPIC LIST ITEM 2
- THE MOST EPIC LIST ITEM 3
```

```markdown:test/orenopage/index.html
**日本語** もいける？

[リンク](http://qiita.com/y13i)もしちゃうよ？
```

適当に Markdown ファイルを作って S3 にアップロードする。

```shell-session
$ aws s3 cp --recursive test/ s3://orenobucket/.src
```

ちゃんと動いているっぽい。

```shell-session
$ aws s3 ls s3://orenobucket/
                           PRE .src/
                           PRE orenopage/
2016-05-06 20:56:02        363 index.html
```

せっかくなので、[静的ウェブサイトホスティング](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteHosting.html)を有効化し、 Bucket Policy で Public Read な状態にしてブラウザーから見てみる。

<img width="591" alt="Converted_from_s3___wordless-test__src_index_md 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/78376/fdb217e0-f8d1-4800-b394-5be50037afe4.png">

<img width="638" alt="Converted_from_s3___wordless-test__src_orenopage_index_md.png" src="https://qiita-image-store.s3.amazonaws.com/0/78376/00b52a6c-0317-77b5-e08e-3f5efb951a7d.png">

はい。

# 参考

- [Babel 6.7 で async/await する - Qiita](http://qiita.com/hashedhyphen/items/44701fcc18bd40066aa7)