この記事はkdnaktの1人 Advent Calendar 2020の8日目の記事です。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[ベースとなるecho serverの実装]
7日目はKotlin/NativeのサンプルプロジェクトであるEcho serverをビルドしてみました。
Echo serverをベースにして、HTTPサーバーのソケット部分を以下のように実装します。
fun main() {
val port: Short = 8080
memScoped {
val buffer = ByteArray(1024)
val serverAddr = alloc()
val listenFd = socket(AF_INET, SOCK_STREAM, 0)
.ensureUnixCallResult("socket") { !it.isMinusOne() }
with(serverAddr) {
memset(this.ptr, 0, sockaddr_in.size.convert())
sin_family = AF_INET.convert()
sin_port = posix_htons(port).convert()
}
bind(listenFd, serverAddr.ptr.reinterpret(), sockaddr_in.size.convert())
.ensureUnixCallResult("bind") { it == 0 }
listen(listenFd, 10)
.ensureUnixCallResult("listen") { it == 0 }
val commFd = accept(listenFd, null, null)
.ensureUnixCallResult("accept") { !it.isMinusOne() }
buffer.usePinned { pinned ->
while (true) {
val length = recv(commFd, pinned.addressOf(0), buffer.size.convert(), 0).toInt()
.ensureUnixCallResult("read") { it >= 0 }
if (length == 0) {
break
}
send(commFd, pinned.addressOf(0), length.convert(), 0)
.ensureUnixCallResult("write") { it >= 0 }
}
}
}
}
[リクエストをログに残す]
echo serverをベースに、HTTPサーバーとしての機能を追加します。まずは、クライアントから送られてきたリクエストをprintln()で出力してみます。
buffer.usePinned { }の中で、recv()とsend()が呼ばれており、echo serverがここでリクエストを受け取って、エコーレスポンスを返していることが分かります。
IntelliJのサジェストを見ると、pinnedというラムダ式のパラメーターは、Pinned<ByteArray>という型のようです。

公式サイトの説明を読むと、Pinned<T>型は、get()関数を呼ぶと実体のT型のオブジェクト、今回の場合はByteArray型のオブジェクトを得られることが分かります。
まずは、シンプルにprintln()関数で出力してみます。
buffer.usePinned { pinned ->
while (true) {
val length = recv(commFd, pinned.addressOf(0), buffer.size.convert(), 0).toInt()
.ensureUnixCallResult("read") { it >= 0 }
if (length == 0) {
break
}
// 以下の追加
println("[DEBUG]:")
println(pinned.get())
send(commFd, pinned.addressOf(0), length.convert(), 0)
.ensureUnixCallResult("write") { it >= 0 }
}
}
これを./gradlew nativeBinariesコマンドでビルドして実行し、別のターミナルからcurlコマンドでリクエストを送ると、以下のようにハッシュ値が出力されてしまいます。println(pinned.get().toString())のようにtoString()の呼び出しを追加しても結果は変わりませんでした。

ByteArray型について調べると、toKString()というバイト配列から文字列に変換してくれる関数を見つけました。
toKString - Kotlin Programming Language
これを利用して、println(pinned.get().toKString())のように修正・ビルドして実行してみると、無事サーバー側で受け取ったリクエストを文字列として出力することができました。

ByteArray型のオブジェクトを文字列に変換してターミナルに出力することができました。
このままでは、最終的にはPOSTリクエストでバイナリのデータが送られてくる場合にログが正しく出力できないという問題が起きるでしょう。しかし、いったん素朴な実装のまま次のステップであるリクエストパーサーの実装に進みます。
[まとめ]
- ByteArrayを文字列に変換する場合は
toKString()関数を利用する - 実装中のコードは以下のリポジトリにまとめてある