Ktor on Android

この記事は [リクルートライフスタイル Advent Calendar 2018](https://qiita.com/advent-calendar/2018/recruitlifestyle) の9日目の記事です。

本日は、ホットペッパービューティーのAndroidアプリ開発を担当している @oxsoft が、KtorをAndroidで使ってみた話を書いてみたいと思います。

# Ktorとは？
Kotlin製のWebフレームワークで、DSL形式で簡単に書くことができます。先日Ktor 1.0がリリースされました！
https://ktor.io/

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Welcome Ktor 1.0, a connected applications framework built by the Kotlin team! Create asynchronous, high-performing, and lightweight web servers and build non-blocking multiplatform web clients, all in one language with idiomatic APIs.<br> <a href="https://t.co/tb1G6zefYI">https://t.co/tb1G6zefYI</a> <a href="https://t.co/bxzqXsNopG">pic.twitter.com/bxzqXsNopG</a></p>&mdash; Kotlin (@kotlin) <a href="https://twitter.com/kotlin/status/1064594141659709441?ref_src=twsrc%5Etfw">2018年11月19日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

公式のFAQにも書かれていますが、KtorはKotlinで書かれているので、Androidでも動きます。（ただし、API24以降）
https://ktor.io/quickstart/faq.html#android-support

# 今回作るもの
今回はKtorをAndroid上で動かして、簡単なモックサーバアプリを作ってみたいと思います。通常の開発用サーバに接続する場合と比較して、モックサーバアプリを作る利点としては以下のようなことが挙げられます。

- 特殊なレスポンスも簡単にモックできる
- エンジニア以外もGUIで簡単にレスポンスを変更できる
- 端末単位で環境を用意することができる

もちろんKtorを使わなくてもAndroid上にHTTPサーバを立てる方法はいくつかありますが、今回はKtorをAndroidで使ってみたいという気持ちがあるのでKtorを使います :muscle:

# 環境構築
先述した通り、KtorはAPI24以降でしか動作しないので、まずはminSdkVersionを24以上にします

```groovy:app/build.gradle
android {
    defaultConfig {
        minSdkVersion 24
    }
}
```

そして、以下の依存関係を追加します。

```groovy:app/build.gradle
dependencies {
    implementation 'io.ktor:ktor-server-netty:1.0.0'
    implementation 'org.slf4j:slf4j-android:1.7.25' // ログ出力用
}
```

また、重複ファイルのエラーが出るので、以下のファイルを除外します。

```groovy:app/build.gradle
android {
    packagingOptions {
        exclude 'META-INF/io.netty.versions.properties'
        exclude 'META-INF/INDEX.LIST'
    }
}
```

当然、インターネットアクセスが必要となるため、以下のパーミッションも追加します。

```xml:AndroidManifest.xml
<manifest>
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>
```

# サーバーの起動
以下のコードをバックグラウンドスレッドから呼び出すことで、8080ポートにHTTPサーバを起動することができます。

```kotlin
embeddedServer(Netty, 8080) {
    routing {
        get("/") {
            call.respondText("<h1>Hello world!</h1>", ContentType.Text.Html)
        }
    }
}.start(wait = true)
```

Chromeを開いて`http://localhost:8080/`にアクセスすると、無事に`Hello world!`を表示する事ができました :tada:

<img src="https://qiita-image-store.s3.amazonaws.com/0/92361/e0caba6e-47ed-afea-98e9-672c34a71c06.png" width="320px" />

また、`CallLogging`を設定することにより、アクセスログを出力することができます。

```kotlin
embeddedServer(...) {
    install(CallLogging) {
        level = Level.INFO
    }
    ...
}
```

```
I/ktor.application: 200 OK: GET - /
I/ktor.application: Unhandled: GET - /favicon.ico
```

# リクエストの受付
今回は`assets/response/`以下に用意したファイルを返却するようにします。URLのパスとassetsフォルダを対応させたいので、URLのパスをハンドリングします。パスのハンドリングは以下のように書くことで簡単に行うことができます。

```
get("/{path...}") {
    val path = call.request.path()
}
```

ルーティングのパラメータ名を`path...`としていますが、今回は使っていないので実際には何でも大丈夫です。

もちろん、パラメータとしても受け取っているので、以下のようにしてもパスを取得することができます。（※スラッシュの有無などが、上記のパターンと異なります）

```
get("/{path...}") {
    val path = call.parameters.getAll("path").orEmpty().joinToString("/")
}
```

ルーティングの詳しい文法は、以下のドキュメントに書いてあります。
https://ktor.io/servers/features/routing.html

# レスポンスの返却
あとは、このパスに応じてassetsファイルを返却するだけです。

以下のような関数を用意しておいて、

```kotlin
fun readAsset(path: String) = BufferedReader(InputStreamReader(assets.open(path))).use { reader ->
    reader.readLines().joinToString("\n")
}
```

レスポンス時に呼び出します。

```
call.respondText(readAsset("response$path"), ContentType.Application.Json)
```

これでモックサーバアプリは完成しました :dancer:

ホットペッパービューティーのデバッグ版アプリでは、OkHttp3のInterceptorによってドメインを任意のものに差し替えるデバッグ機能があるので、それを`localhost:8080`に指定することで接続できました :kissing_smiling_eyes:

実際のモックサーバアプリでは、以下のような感じでAPIごとにモックするレスポンスを変更することができるようにしましたが、特に記事にするほどのことをしていないので、今回は割愛します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/92361/61515758-6d88-22bc-2913-a522382ac84b.png" width="320px" />

# まとめ
ほとんどつまづくことなく、簡単にHTTPサーバを立てることができました :smile:

今年も残りわずかとなりましたが、来年もまたAndroid/Kotlin界隈がより一層盛り上がっていくのが楽しみですね！それではみなさん良いお年を :bamboo: