PlayFramework-Scala + (EC2 + ElasticIP + CloudFront) で LINE BOT オウム返し

2016.04.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

LINE BOTは、いろいろな動作環境が提案されていますが、今回は表題のものを使ってささっと構築してみたいと思います。ちなみに、私自身は普段どちらかというとサーバサイドのコードを書いている時間が長く、AWSはまだそんなに触っていません。それでもなんとか動くところまでこぎつけたのでハードルは高くないと思います。

LINE BOTについては、AWS Lambdaを利用した方法でも実装することが可能なようです。弊社せーのの記事をご参照ください。

用意するもの

LINE BOT

BOT API Trial Account https://business.line.me/ja/products/4/introduction

何をするにもまずはBOTのアカウントを登録します。先着1万人にトライアル提供ということだったので、わりと競争になるかと思ったのですが、結構余裕がありそうですね。現在でもまだ登録できるのでしょうか? →2016年4月16日時点で上限に達したようです。

LINE BOTを動作させる環境

LINE BOTは、以下のような流れでメッセージが流れます。

  1. ユーザがBOTに対してメッセージを送る
  2. LINEサーバが登録したコールバック先に対して受け取ったメッセージを送る
  3. コールバック先でPOSTリクエストを受け取り、BOTプログラムを動かす
  4. BOTプログラムがLINE指定のURLに返信メッセージを送る
  5. LINEサーバがBOTから受け取ったメッセージをユーザに届ける
  6. ユーザがBOTからメッセージを受け取る

環境準備においてやや手間なのが以下のようなポイントです。

  • 2で、コールバック先のURLはHTTPS通信が求められる
  • 4で、LINEサーバへリクエストを送るサーバは予めIPアドレスを登録しておく必要がある。

つまり、「HTTP通信可能な」「固定IPの」サーバが要求されます。最初、途方にくれてしまいましたが、今自分がいる会社を思い出し、AWSを使って環境を構築してみることにしました。結論として以下のように構成すればLINE BOT用サーバを実現できます。なお、今回はPlay-Scalaでまず動かしてみることを重視しており、「堅牢なセキュリティ」と「独自ドメインでの利用」は想定していません。

構成図

BOTプログラム

PlayFramework-Scalaで、受け取ったメッセージをそのまま返す、いわゆる「オウム返しBOT」を作ります。

やっていく - AWS構築

実際に上図の環境を構築しましょう。

EC2インスタンスを立ち上げる

ステップに従ってインスタンスを構築しましょう。動作確認程度であればnanoかmicroで十分です。ここへは、後でSSHでログインしてPlayFrameworkを実行する環境を用意します。私はUbuntuを利用しました。(PlayFrameworkが動けばOSはどれでもOKです)

EC2

ElasticIPでIPアドレスを固定する

ElasticIPのアイテムを作成すると、固定IPを割り当てる先として、先ほど作成したインスタンスが候補に上がるはずです。そのまま「OK」とすると、先ほど作成したインスタンスのIPアドレスが固定となっています。お手軽。これでIPアドレスを固定しなければならない要件はクリアです。

ElasticIPElasticIP-setting

CloudFrontでHTTPSリクエストを受け取れるようにする

このままではもうひとつの要件である「HTTPS通信可能なサーバ」を満たせません。EC2単体では難しいので、CloudFrontを設置し、ドメインとSSL証明書を拝借することにします。ドメインは、新しくCloudFrontを用意するときに自動で決められるものであり、こちらで任意のドメイン名に設定することができない点に注意してください。

cloudfront-selectcloudfront

新しくCloudFrontを作成して、HTTPSのみ受け付けるようにし、受け付けたリクエストを先ほどのEC2インスタンスへ送るよう設定します。送り先のポート番号は9000にします。PlayFrameworkの起動ポートです。

EC2のセキュリティグループを編集する

EC2を立ち上げると勝手にセキュリティグループがくっついていると思いますので、それを編集します。本当は、セキュリティグループはどこでどのように運用されるかを考慮して名前なども決める必要があるらしいのですが、今回は気にしません。

インバウンドルール

  • 自分のPCからSSHでログインできるようにする
  • HTTP通信を受け付ける

security

アウトバウンドルール

  • すべてのプロトコル、すべてのIPを許可する

やっていく - LINE側の設定

いま構築した情報をLINE側に登録します。

コールバックURLの設定

CloudFrontのURLを設定します。ポート番号を明示する必要があることに注意してください。

LINE-callbackurl

サーバホワイトリストの設定

ElasticIPで固定したIPアドレスを登録します。

LINE-serverwhitelist

サーバからLINEへのリクエストが通るか確認

EC2インスタンスにログインして、BOTのプロファイルを取得するリクエストを叩いてみましょう。無事取得することができればBOTからLINEサーバへメッセージを送ることが可能な状態です。

curl -H "X-Line-ChannelID: {cannel id}" \
-H "X-Line-ChannelSecret: {channel secret}" \
-H "X-Line-Trusted-User-With-ACL: {mid}" \
-XGET https://trialbot-api.line.me/v1/profiles?mids={mid}

# example
curl -H "X-Line-ChannelID: 1462512345314" \
-H "X-Line-ChannelSecret: 0c97123ag2rdfasdasf12" \
-H "X-Line-Trusted-User-With-ACL: 2ru9ur92r23ur023r203" \
-XGET https://trialbot-api.line.me/v1/profiles?mids=2ru9ur92r23ur023r203

やっていく - オウム返しBOTの作成

オウム返しコード

Playframework-Scalaで実装します。activatorコマンドでアプリケーションを作成するといくつか自動でファイルができます。その中のHomeControllerにメッセージの受け口となるメソッドを追加します。

HomeController.scala

package controllers

import javax.inject._

import play.api.Logger
import play.api.libs.json.Json
import play.api.libs.ws._
import play.api.mvc._

import scala.concurrent.ExecutionContext.Implicits.global

/**
 * This controller creates an `Action` to handle HTTP requests to the
 * application's home page.
 */
@Singleton
class HomeController @Inject()(ws: WSClient) extends Controller {

  /**
   * Create an Action to render an HTML page with a welcome message.
   * The configuration in the `routes` file means that this method
   * will be called when the application receives a `GET` request with
   * a path of `/`.
   */
  def index = Action {
    Ok(views.html.index("Your new application is ready."))
  }

  def line = Action.async(BodyParsers.parse.json) { data =>
    val req = ws.url("https://trialbot-api.line.me/v1/events")
    val headers = req.withHeaders(
      "Content-Type" -> "application/json; charset=UTF-8",
      "X-Line-ChannelID" -> "4123412513", //Channel Id (改変してます)
      "X-Line-ChannelSecret" -> "283tujfgdsfjfj23rewfsdsfbdf", //Channel Secret(改変してます)
      "X-Line-Trusted-User-With-ACL" -> "fasdfqwioe14412" //MID (改変してます)
    )

    val content = (data.body \\ "content").head
    val exFrom = (content \ "from").get.as[String]
    val exText = (content \ "text").get.as[String]

    val body = Json.parse(
      s"""
         |{
         |  "to":["${ exFrom }"],
         |  "toChannel":1383378250, // LINE指定の固定値
         |  "eventType":"138311608800106203", // LINE指定の固定値
         |  "content":{
         |    "contentType":1,
         |    "toType":1,
         |    "text":"${ exText }"
         |  }
         |}
      """.stripMargin)

    headers.post(body).map { res => Logger.info(res.body.toString()); Ok() }
  }
}

 

routes

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# An example controller showing a sample home page
GET         /                    controllers.HomeController.index
POST        /                    controllers.HomeController.line
# An example controller showing how to use dependency injection
GET         /count               controllers.CountController.count
# An example controller showing how to write asynchronous code
GET         /message             controllers.AsyncController.message

# Map static resources from the /public folder to the /assets URL path
GET         /assets/*file        controllers.Assets.versioned(path="/public", file: Asset)

 

デプロイ

これでOK。あとはgitなりSCPなりでEC2インスタンスへPlayのプロジェクトフォルダを送りつけます。Playを動かすために、Javaをインストールします。

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install -y oracle-java8-installer

起動させます。

$ cd line-bot-project-directory
$ ./activator run

動かしてみる

さて、これで準備OKです。自分のLINEからBOTに対してメッセージを送ってみましょう。メッセージがBOTから返ってきたら成功です。

LINE-screen

おわりに

ここまでくればあとはサーバサイドエンジニアリングの世界に持ち込めます。スクレイピング、レコメンドプッシュ、遠隔操作…BOTOpsへの夢が広がりますね。

今回紹介した方法では、ドメイン名やSSL証明書など、独自のものを利用することはできませんが、そこにこだわりがなければ簡単な手順で構築することができます。

利用料については、この環境で動作させたところ、EC2のnanoインスタンスで0.8ドル/日でした。1ヶ月だとおおよそ3,000円〜5,000円くらいでしょうか(リクエスト回数などによっても変動します)。スポットで開発するだけであれば、インスタンス停止しつつ必要なときだけ起動…という運用にすることで安価に利用できるでしょう。費用については以下も参考にしてみてください。

http://aws.amazon.com/jp/ec2/pricing/

それでは、快適なBOTエンジニアリングライフを!

参考

LINE Developers - BOT API - Getting started with BOT API Trial