Akka HTTP で LINE bot を作ってみました

こんにちは、Xiaoです。

最近Akka HTTPの正式版 **[X](http://akka.io/news/2016/11/22/akka-http-10.0.0-released.html)** がで出ましたね。
自分は前からAkka HTTPに興味があって、ちょうどいい機会なので、Akka HTTPを詳しく勉強するため、簡単のWebサービスを作ってみたいと思います。

# LINE Bot とは

> LINE が提供する Messaging API により、LINEのトーク画面を使った対話型Botアプリケーションの開発が可能になります。

![図](https://developers.line.me/wp-content/uploads/2016/09/bottrial-fig1.png)

※ Image taken from [developers.line.me](https://developers.line.me/messaging-api/overview)

この図を説明すると、

- サーバのURLを LINE Bot にするアカウントと繋ぎます。
- ユーザがBotに対して、友たち追加、メッセージ送信などを行う際に、設定されたサーバにイベント情報が送られます。
- サーバがイベント情報を基づいて、LINE Messaging API経由でユーザに返信できます。

※ LINE アカウントの設定について、[LINEの公式ドキュメント](https://developers.line.me/messaging-api/overview)に記載されているので、割愛します。

# Akka HTTPについて

Akka HTTP は Akka Actor と Akka Stream をベースに作られた、HTTPサーバ／クライエント処理をサポートするためのツール、ライブラリです（[フレームワークではありません！](http://doc.akka.io/docs/akka-http/current/scala/http/introduction.html#philosophy)）。

LINE botを作るには、Akka HTTP みたいなツールが丁度良いと思います。

# 今回で作る Bot 

今回は Akka HTTP の特性の紹介するため、一番シンプルな「受けたメッセージをそのまま返す」Botを例にして、Akka HTTP の特性を幾つか紹介したいと思います。

<!-- more -->

- JSON を変換する ((un)Marshalling)
- HTTP request を処理する
- HTTP request を送る

最終のコードは [こちら](https://github.com/xoyo24/akka-http-line-bot) でたどり着けます。

# TL;DR

## JSON を変換する ((un)Marshalling)

LINE Messaging API とのやりとりは全部JSONを扱っています。これに対して、Akka HTTP の特徴の一つは、(un)Marshalling 機構によって、JSON、 XML或いはバイナリデータを簡単サポート出来ます。

◆ spray-jsonモジュールを使ため、Dependencyに"akka-http-spray-json"を追加。

```scala
"com.typesafe.akka" %% "akka-http-spray-json" % "10.0.0"
```

◆ 次は、スコープ内に RootJsonFormat[T] を用意する。

- Case Class がある場合、要素の数に応じて、**jsonFormatX** メソッドを使えばいいです。
- それ以外の場合、**RootJsonFormat[T]** を実装する必要があります。

※ 詳しい説明は [spray-json](https://github.com/spray/spray-json) を参照ください。

◆ 最後、Marshalling と Unmarshalling 機構を使うため、SprayJsonSupport を導入する。

[Reply Message API](https://devdocs.line.me/ja/#reply-message) のRequestをJSONに変換したい場合は、こんな感じです。

```scala
case class TextMessage(`type`: String = "text", text: String)

case class Messages(replyToken: String, messages: List[TextMessage])

trait MessagesJsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
  implicit val textMessageFormat = jsonFormat2(TextMessage)
  implicit val messageFormat = jsonFormat2(Messages)
}
```

## HTTP requestを処理する

ユーザがBotに対して、友たち追加、メッセージ送信などを行う際に、設定されたサーバにイベント情報が送られます。
HTTP サービスをシンプルに実装出来るため、Akka HTTP は High Level Routing DSL を提供しています。

こんな感じで使います。

```scala
def routes: Route = {
  path("line" / "callback") {
    (post & entity(as[Events])) { request =>
      
      ...

      complete {
        "OK"
      }
    }
  }
}
```

見た目の通り、今回は以下のDSLを使いました。

- **path("line" / "callback")** はサービスのエンドポイントURLです。
- **(post & entity(as[Events]))** は POST メソッドをマッチし、Requestデータを **Events** に変換します。
- **complete** は処理成功を表します。

※ 詳しい説明は [High-level Server-Side API](http://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/index.html) を参照ください。

関連するコードは [こちら](https://github.com/xoyo24/akka-http-line-bot/blob/master/src/main/scala/bot/application/EchoBot.scala) で参照できます。

## HTTP request を送る

ユーザに返信する場合、LINE の [Reply Message API](https://devdocs.line.me/ja/#reply-message) を経由で実現します。

Akka HTTPでは、こんな感じで書けます。

```scala
val auth = headers.Authorization(OAuth2BearerToken(accessToken))
val content = Messages(
  replyToken = token,
  messages = List(TextMessage(text = message))
)

val request = RequestBuilding.Post(
  uri = "https://api.line.me/v2/bot/message/reply",
  content = content
).withHeaders(auth)

val responseFuture: Future[HttpResponse] = Http().singleRequest(request)
```

- Akka HTTPは、**Authorization** 機構を通じで、OAuth認証などをサポートしています。
- **RequestBuilding** を使って、Requestを作成していますが、**HttpRequest** を直接実装するのもできる。
- HTTPコネクションを管理したくない場合、一番簡単な **Http().singleRequest** を使います。

※ 詳しい説明は [Consuming HTTP-based Services (Client-Side)](http://doc.akka.io/docs/akka-http/current/scala/http/client-side/index.html) を参照ください。

関連するコードは [こちら](https://github.com/xoyo24/akka-http-line-bot/blob/master/src/main/scala/bot/application/ReplyService.scala) で参照できます。

# まとめ

今回はテキストベースのBotに必要のものを紹介しました。

後は、DBやAkka Presistenceを使えば、情報を保持出来るBotを作れるし、他の 3rd Party API に繋げば、いろんなサービスを提供することが出来ます。

本記事はSepteni Engineer's Blogの転載です。
http://labs.septeni.co.jp/entry/2016/12/26/120625