見出し画像

KtorでAndroidをWebサーバーにしてみた - REALITY Advent Calendar #13

REALITYでAndroidのテックリードをしているメタルおじさんです。

画像7

今回の開発合宿では普段あまり触ることのないKtorのサーバーサイドAPIを使って、AndroidをWebサーバーにしてみるというチャレンジをしました。

ちなみにヘッダーの画像は宿で出たご飯です(この後もいっぱいおかずが出てきました)。おいしかったです。

Ktorとは

KtorはJetBrainsがオープンソースで開発しているネットワーキングフレームワークです。

・Kotlinマルチプラットフォーム対応
・CoroutinesベースのAPI
・HTTPのリクエストを送受信するプログラムを作成可能
・WebSocketのサーバー・クライアントを作成可能
・HTTPだけでなく、TCP/UDPのサーバー・クライアントを作成可能

…と、対応プラットフォーム・サポートしている機能ともに幅広く、現在も活発に開発が行われています。記事執筆時点でのバージョンは1.6.6ですが、近いうちにメジャーバージョンアップの2.0のリリースが予定されており、要チェックなKotlinライブラリだと思っています。

REALITYでは一部のWebSocket通信にKtorを使用しているのですが、今回の開発合宿では普段あまり触らないサーバーサイドのKtorを使ってみることにしました。

とはいえただサーバーにデプロイするだけでは面白くないので、せっかくAndroidエンジニアをやっているのだからKtorのサーバーコードをAndroid上で動かすというちょっと変わったチャレンジをしてみました。

プロジェクトセットアップ

まずは普通にAndroidの新規プロジェクトを作成します。

スクリーンショット_2021-11-30_18_48_22

親の顔より見たMainActivityです。

次に新規モジュールを作成します。今回はサーバー側ではAndroid関係のAPIを一切使用しないのでJava or Kotlin Libraryを選択しましたが、Android Libraryでも大丈夫です。

スクリーンショット 2021-11-30 18.52.01

そしてできあがったktor-serverモジュール内のbuild.gradleを開き、io.ktor:ktor-server-netty:1.6.6の依存を追加します。

スクリーンショット_2021-11-30_19_15_58

最後に、Server.ktにKtorサーバーのコードを書いたら完了です。

スクリーンショット_2021-11-30_19_18_54

import io.ktor.application.call
import io.ktor.response.respondText
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty

fun main() {
   Server().start()
}

class Server {
   fun start() {
       embeddedServer(Netty, port = 8000) {
           routing {
               get("/") {
                   call.respondText("Hello, world!")
               }
           }
       }.start(wait = true)
   }
}

さりげなくmainメソッドを書いています。Android Studioではmainメソッドを書いておくとエディタ上に実行ボタンが表れて、これをクリックすることで即実行することができます。とても便利です。

スクリーンショット_2021-11-30_19_11_19

実行が始まったらWebブラウザを開いて http://localhost:8000/ にアクセスしてみましょう。Hello, world!の文字が表示されたら成功です。

スクリーンショット 2021-11-30 19.25.42

Androidで動かしてみる

ここまでできたら、あとはAndroidアプリのコードからServer.start()を呼び出せば良さそうです。

app/build.gradleを編集して以下のコードを追加します。

android {
    ... 省略

    // このブロックを追加しないとビルドできなかった
    packagingOptions {
        exclude 'META-INF/INDEX.LIST'
        exclude 'META-INF/io.netty.versions.properties'
    }
}

dependencies {
    implementation project(':ktor-server') // この行を追加
}

MainActivityの中にServer.start()を呼び出すコードを追加します。

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       thread {
           Server().start()
       }
   }
}

onCreateの中で直接呼び出すとメインスレッドをブロックしてアプリがフリーズしてしまうので、新しいスレッドを作成してその中でServer.start()を呼ぶようにしてみました。

※今回は実験のためにものすごーーくシンプルな実装にしていますが、実際にアプリを作る時はこんな感じでスレッドを作ったまま放ったらかしにするようなコードを書いちゃダメですよ!

あと、忘れないようにAndroidManifest.xmlに<uses-permission android:name="android.permission.INTERNET" />も追加しておきましょう。

ここまでできたら完成です。Android端末にデプロイして実行してみましょう。

画像8

UIからは何もわかりませんが、裏でWebサーバーが動いているはずです。きっと。

Webブラウザからアクセスしてみる

アプリをAndroid端末で起動したら、Android端末と同じローカルネットワーク内にPCがいることを確認して、Webブラウザを開いてください。そしてURL欄に Android端末のIPアドレス:8000 と入力してEnterを押します。WebブラウザにHello, world!の文字が表示されたら成功です!

スクリーンショット 2021-12-02 21.56.12

私の環境ではたまたまAndroid端末のIPアドレスが192.168.86.185でしたが、皆さんはおそらく違うと思うので適切なIPアドレスを入力してください。

おわりに

Androidアプリを動かしている裏でWebサーバーを立ち上げることができると、PC等と連携していろいろ面白いことができそうだなと思いました。実際に今後のプロダクト開発にこの知見が活かせるかどうかは全然わかりませんが……普段触らない技術に触れた開発合宿はとても楽しかったです。

Learn More!

今回はAndroidでKtorサーバーを動かすことにフォーカスし、あえてシンプルなコードにしました。実際の製品でこの手法を取り入れようと思ったら、他にもいろいろ考えるべきことがあります。

おそらくサーバーをServiceで動かす必要があるでしょう(そしてきっとForeground Serviceにする必要があるでしょう)。

サーバーを止められるようにもしておくべきでしょう。そのためにはembeddedServer()は最初に一度だけ実行してApplicationEngineを保持しておき、start(), stop()を適切なタイミングで呼べるようにします。

Webブラウザが表示するHTMLは、static contentとしてホストするのが良いでしょう。

などなど……詳しくはKtorのドキュメントを参照ください。

また、Android端末上でサーバーを動作させるということは、悪意のあるソフトウェアや、悪意を持ったユーザーからの攻撃を受けやすい状態になる可能性があります。プロダクトで導入を検討する場合はこのようなリスクについても検討する必要があります。

明日のアドベントカレンダーは!

サーバーエンジニアKuyamaさんのArgo CDを使った半独立開発環境を作ってみたです!