Kotlin/NativeでHTTPサーバーを作るアドベントカレンダー(12日目:start-lineをパースする)

この記事はkdnaktの1人 Advent Calendar 2020の12日目の記事です。

 

2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。

 

 

[start-lineをパースする]

10日目の記事で確認した内容をもとに、HTTPリクエストのstart-lineをパースするためのテストコードを書いていきます。

kdnakt.hatenablog.com

 

▼事前準備

まずテストコードを書くための準備として、パースした結果を格納する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 objectwhen式を利用して実装しました。

 

テスト対象とする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メソッドと差異があるため、テストは失敗します。

f:id:kidani_a:20201212230240p:plain

 

このテストが成功するように実装を修正します。

 

▼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-targethttp-versionに関するテストを追加して終了です。

 

[まとめ]

  • HTTPリクエストのstart-lineをパースする実装を追加した
  • 過去のKotlin勉強会で学んだ内容が役に立った
  • 実装中のコードは以下のリポジトリにまとめてある

github.com