PlayFramework-Scala + (EC2 + ElasticIP + CloudFront) で LINE BOT オウム返し
はじめに
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は、以下のような流れでメッセージが流れます。
- ユーザがBOTに対してメッセージを送る
- LINEサーバが登録したコールバック先に対して受け取ったメッセージを送る
- コールバック先でPOSTリクエストを受け取り、BOTプログラムを動かす
- BOTプログラムがLINE指定のURLに返信メッセージを送る
- LINEサーバがBOTから受け取ったメッセージをユーザに届ける
- ユーザが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です)
ElasticIPでIPアドレスを固定する
ElasticIPのアイテムを作成すると、固定IPを割り当てる先として、先ほど作成したインスタンスが候補に上がるはずです。そのまま「OK」とすると、先ほど作成したインスタンスのIPアドレスが固定となっています。お手軽。これでIPアドレスを固定しなければならない要件はクリアです。
↓
CloudFrontでHTTPSリクエストを受け取れるようにする
このままではもうひとつの要件である「HTTPS通信可能なサーバ」を満たせません。EC2単体では難しいので、CloudFrontを設置し、ドメインとSSL証明書を拝借することにします。ドメインは、新しくCloudFrontを用意するときに自動で決められるものであり、こちらで任意のドメイン名に設定することができない点に注意してください。
↓
新しくCloudFrontを作成して、HTTPSのみ受け付けるようにし、受け付けたリクエストを先ほどのEC2インスタンスへ送るよう設定します。送り先のポート番号は9000にします。PlayFrameworkの起動ポートです。
EC2のセキュリティグループを編集する
EC2を立ち上げると勝手にセキュリティグループがくっついていると思いますので、それを編集します。本当は、セキュリティグループはどこでどのように運用されるかを考慮して名前なども決める必要があるらしいのですが、今回は気にしません。
インバウンドルール
- 自分のPCからSSHでログインできるようにする
- HTTP通信を受け付ける
アウトバウンドルール
- すべてのプロトコル、すべてのIPを許可する
やっていく - LINE側の設定
いま構築した情報をLINE側に登録します。
コールバックURLの設定
CloudFrontのURLを設定します。ポート番号を明示する必要があることに注意してください。
サーバホワイトリストの設定
ElasticIPで固定したIPアドレスを登録します。
サーバから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
にメッセージの受け口となるメソッドを追加します。
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 # 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から返ってきたら成功です。
おわりに
ここまでくればあとはサーバサイドエンジニアリングの世界に持ち込めます。スクレイピング、レコメンドプッシュ、遠隔操作…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