この記事はkdnaktの1人 Advent Calendar 2020の12日目の記事です。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[start-lineをパースする]
10日目の記事で確認した内容をもとに、HTTPリクエストのstart-lineをパースするためのテストコードを書いていきます。
▼事前準備
まずテストコードを書くための準備として、パースした結果を格納するRequestContext.ktファイルを以下の内容で作成します。
data class RequestContext(
val method: HttpMethod,
val requestTarget: String,
val httpVersion: HttpVersion)
enum class HttpMethod {
GET,
POST,
}
enum class HttpVersion() {
HTTP_0_9,
HTTP_1_0,
HTTP_1_1;
companion object {
fun from(version: String) = when(version) {
"" -> HTTP_0_9
"HTTP/1.0" -> HTTP_1_0
"HTTP/1.1" -> HTTP_1_1
else -> HTTP_1_1
}
}
}
HTTPバージョンは、Kotlin勉強会で学んだcompanion objectとwhen式を利用して実装しました。
テスト対象とするRequestParser.ktファイルを作成し、ダミーの実装を追加します。
class RequestParser {
fun parse(byteArray: ByteArray): RequestContext {
return RequestContext(HttpMethod.POST, "", HttpVersion.HTTP_1_0)
}
}
▼テストコード
テストコードを実装するRequestParserTest.ktファイルを作成し、シンプルなGETリクエストのバイト配列をパースして、戻り値のHTTPメソッドがGETかどうか判定するテストを実装します。
class RequestParserTest {
private val parser: RequestParser = RequestParser()
private val crlf = "\r\n"
@Test
fun shouldParseGetMethod() {
val reqByteArray = ("GET /index.html HTTP/1.1" + crlf
+ "Host: localhost:8080" + crlf
+ crlf).encodeToByteArray()
val context = parser.parse(reqByteArray)
assertEquals(HttpMethod.GET, context.method, "should return GET method")
}
}
この状態でテストを実行すると、ダミーの実装ではPOSTメソッド、期待値はGETメソッドと差異があるため、テストは失敗します。

このテストが成功するように実装を修正します。
▼RequestParserを実装する
HTTPリクエストの1行目、つまり最初のCRLFまでが半角スペースで区切られたstart-lineなので、その部分を取り出し半角スペースで分割します。
val startLineElements = byteArray.toKString()
.split("\r\n")[0]
.split(" ")
分割した結果の要素が2以下の場合は、HTTP/0.9の仕様にしたがって、HTTPバージョンがないstart-lineとなります。
val httpVersion = if (startLineElements.size <= 2)
HttpVersion.HTTP_0_9 else HttpVersion.from(startLineElements[2])
以上のデータをもとに、RequestContextオブジェクトを作成します。
return RequestContext(
HttpMethod.valueOf(startLineElements[0]),
startLineElements[1],
httpVersion
)
これでテストを実行すると、成功します。あとは、request-targetとhttp-versionに関するテストを追加して終了です。