Serverless FrameworkでLINE BOTが簡単にできちゃった

## Serverless Framework
[Serverless Framework](https://github.com/serverless/serverless)は簡単に言うと、yamlで記述した設定ファイルを元に、CloudFormation経由でAWS上の**サーバレスアーキテクチャ**を構築管理してくれる**AWS Lambda**を中心としたインフラまわりのフレームワークです。

フレームワークと聞くとRuby on RailsやCakePHPなどを思い浮かべてしまいますが、そういったアプリケーション的な機能は薄く、少し意味合いが違った印象です。

[![image](https://qiita-image-store.s3.amazonaws.com/0/7132/a03a8641-e049-72f2-8aca-4a9db9997a50.png)](https://serverless.com/)
10月にv1.0になり、すでに現在v1.4.0になってます。
それ以前のバージョンとはかなり変わっているので注意

## お天気チャンネル for LINE
LINEで「天気教えて」ってメッセージを送ると、
現在の渋谷の天気情報とスクランブル交差点のウェブカメラの画像を返してくれるChatBotをつくりました。
https://line.me/R/ti/p/%40wra7063k
[<img src="https://qiita-image-store.s3.amazonaws.com/0/7132/400e521b-fe96-5f76-d403-5acee990419a.jpeg" height="500">](https://line.me/R/ti/p/%40wra7063k)
地味に便利


## LINE@
まだアカウントを持っていなければ[LINE Business Center](https://business.line.me/ja/services/bot)からアカウントを作成し、
[LINE Developers](https://developers.line.me/)で`Channel Access Token`を準備してください。

### LINE Messaging API
[Messaging API](https://developers.line.me/messaging-api/overview)はLINEでメッセージが届くと[Webhook](https://devdocs.line.me/ja/#webhook-event-object)経由でリクエストが届きます。

Webhookなので必要なときだけコードを実行する**AWS Lambda**のようなイベント駆動のシステムを使用したアーキテクチャは、このようなBOTに適しているといえます。
また、LINE APIはhttpsが必須ですが、**Serverless**で構築するAPI Gatewayがカバーしてくれます。

## 下準備
ドキュメントを見ながら実装していきます。
https://serverless.com/framework/docs/providers/aws/guide/intro/

### node.jsのバージョン
実際にAWS Lambda上で動作する**node.js**は`v4.3.2`なので、nodebrewなどでバージョンを合わせておいた方が良いでしょう。

```bash
$ nodebrew install-binary v4.3.2
$ nodebrew use v4.3.2
$ node -v
v4.3.2
```
`install`より`install-binary`を使うと速い

### AWS準備
#### aws-cliインストール

```bash
$ pip install awscli
$ aws --version
aws-cli/1.11.28 Python/2.7.12 Darwin/16.1.0 botocore/1.4.85
```

かなり頻繁アップデートされるので、気が向いたときにupgradeかけると良いです。

```bash
$ pip install --upgrade awscli
```

#### アクセスキー設定
指示に従い、`Access Key ID`と`Secret Access Key`とリージョンを設定しておきます。

```bash
$ aws configure
```

#### IAMポリシー設定
ドキュメントでは`AdministratorAccess`の権限をセットしろと書かれているのですが、流石に強権すぎるので、インラインでセットします。
とりあえず、今回はこの辺があればOKです。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:*",
                "s3:*",
                "iam:*",
                "apigateway:*",
                "lambda:*"
            ],
            "Resource": "*"
        }
    ]
}
```


### Serverlessインストール
コマンド実行できるようにnpmでグローバルにインストールしてしまいます。

```bash
$ npm install -g serverless
$ serverless -v
1.3.0
```

## Serverlessサービスの雛形作成

Serverlessでは１つ１つをサービスという単位で作成していきます。
pythonとかjavaとか色々選べますが、今回は`aws-nodejs`を選択

```bash
$ serverless create --template aws-nodejs --path tenkibot
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/xxxx/xxxx/xxxx/xxxx/tenkibot"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.3.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
```

実行するとtenkibotの中に`handler.js`と`serverless.yml`が作成されています。

さらに、`package.json`がないので`npm init`する

```bash
$ cd ./tenkibot
$ npm init
```

ディレクトリー構成はこんな感じ

```bash
$ tree
.
├── handler.js
├── package.json
└── serverless.yml
```

## ローカル実行環境
Serverlessには実行環境がなく、nodeコマンドとかで頑張ることもできますが、面倒なので
`serverless-offline`をインストールしてAPI Gatewayをローカルでシミュレートしてもらいます。

```bash
$ npm install --save-dev serverless-offline
```

そしてserverless.ymlに２つ書き加えます

```yaml:serverless.yml
plugins:
  - serverless-offline

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
```

そしてローカル実行

```bash
$ serverless offline start
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000
```

これで http://localhost:3000/hello にアクセスし

```json:
{
  "message": "Go Serverless v1.0! Your function executed successfully!",
  ...
}
```

と返ってくればOKです。


## LINE WebHook作成

### superagentをインストール
API通信用に`superagent`を使用します。

```bash
$ npm install --save-dev superagent
```

## Function作成
新しく`line.js`というファイルを作成し、その中にリクエストの処理を記述していきます。
ひとまずオウム返しするBOT
accessTokenにはLINE Developersで取得した`Channel Access Token`を使用してください。

```js:./line.js
const request = require('superagent');
const endpoint = 'https://api.line.me/v2/bot/message/reply';
const accessToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

module.exports.webhook = (event, context, callback) => {
  var body = JSON.parse(event.body);
  body.events.forEach(function(data) {
    var replyToken = data.replyToken;
    var message = data.message.text

    request.post(endpoint)
            .set('Content-type', 'application/json; charset=UTF-8')
            .set('Authorization',  'Bearer ' + accessToken)
            .send({
              replyToken: replyToken,
              messages: [
                {
                  type: 'text',
                  text: message,
                },
              ],
            })
            .end(function(error){
              if (error) {
                console.log(error);
              }
            });
  });

  callback(null, {statusCode: 200, body: JSON.stringify({})});
};
```

## URLを作成
`serverless.yml`に新しく作ったFunctionを追記します。
この記述がAPI Gatewayに反映されます、routing的なものだと考えてください。

handlerには`ファイル名.ファンクション名`の形式で指定します。
`handler: src/facebook.webhook`みたいにを作ることもできます。

```yaml:serverless.yml
functions:
  lineWebhook:
    handler: line.webhook
    events:
      - http:
          path: line/webhook
          method: post
```

これで http://localhost:3000/line/webhook というURLにPOSTすると動作するようになりました。
実際にlocalでこのままためそうとすると、LINEに送っちゃうのでお気をつけて

## デプロイ

### region設定
`serverless.yml`にデプロイのための追記を行います。
`region`がデフォルトで`us-east-1`なので、東京リージョン（`ap-northeast-1`）に変更します。

```yaml:serverless.yml
provider:
  name: aws
  runtime: nodejs4.3
  stage: dev
  region: ap-northeast-1
```

### デプロイ実行
ここまでできたら後はAWSにデプロイするだけ

```bash
$ serverless deploy
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
.....
.....
Serverless: Stack update finished...

Service Information
service: tenkibot
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  POST - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/line.webhook
  GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  tenkibot-dev-lineWebhook: arn:aws:lambda:us-east-1:000000000000:function:tenkibot-dev-lineWebhook
  tenkibot-dev-hello: arn:aws:lambda:us-east-1:000000000000:function:tenkibot-dev-hello
```

endpointsの
`GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello`
ここにアクセスすると、ローカルで実行したときと同じように`Go Serverless v1.0! Your function executed successfully!`が表示されます。

### Webhook URLを設定する
先程のendpointsから
`POST - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/line.webhook`
このURLをLINE DevelopersのWebhook URLを設定します。

## 完成
LINEで友達追加して、メッセージ送るとオウム返しされれば完成です。
<img width="375" alt="IMG_1100.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/7132/0438e50a-c9e5-0fa9-425c-fa08ff145de6.png">
うまくいかない場合はCloudWatchのログにからデバッグできます。

## おわり
最初に説明したお天気チャンネルは、更に[Yahoo! 気象情報API](http://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/weather.html)やウェブカメラの秘密のAPIを組み合わせて作っています。

AWSと聞くと若干高いイメージですが、
AWS Lambdaは月1,000,000件が無料なので、普通の使い方ではまず課金されない。
その後は`0.20 USD/1,000,000 件のリクエスト（0.0000002 USD/リクエスト）`と書かれているので、CloudFormationやAPI Gateway含め、値が小さ過ぎむしろコスト計算が大変。

Amazonもかなり力を入れてるみたいなので、もうAPIのサーバーとか必要のない時代が迫ってるのかもしれない
サーバー管理いらないし、リリースに気を使わなくて良いってのはかなりメリット