この記事はkdnaktの1人 Advent Calendar 2020の19日目の記事です(1日遅れ)。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[POSTメソッドの実装に着手]
2日目にスコープを考えたときに、POSTメソッドも実装する計画でした。
POSTメソッドはHTTPリクエストに含まれるContent-Type
ヘッダーを元に、リクエストボディの種類を決定する必要があります。
また、リクエストボディのサイズを知るためには、Content-Length
ヘッダーも取得できなくてはいけません。
[リクエストヘッダーをパースする]
厳密にリクエストヘッダーをパースしようとすると、RFC7230の以下の定義を思い出す必要があります。
header-field = field-name ":" OWS field-value OWS
OWSはOptional whitespaceなので、半角スペースまたは水平タブが入る場合もあれば、省略されることもあります。
以上の内容をテストコードに書き出します。
@Test fun shouldParseHeaderWithRightSize() { val reqByteArray = ("GET /index.html HTTP/1.1" + crlf + "Host: localhost:8080" + crlf + crlf).encodeToByteArray() val context = parser.parse(reqByteArray) assertEquals(1, context.headers.size) } @Test fun shouldParseHeaderContentWithoutOWS() { val reqByteArray = ("GET /index.html HTTP/1.1" + crlf + "Host:localhost:8080" + crlf + crlf).encodeToByteArray() val context = parser.parse(reqByteArray) assertEquals("localhost:8080", context.headers["Host"]) } @Test fun shouldParseHeaderContentWithOWSAfterFieldValue() { val reqByteArray = ("GET /index.html HTTP/1.1" + crlf + "Host: localhost:8080\t" + crlf + crlf).encodeToByteArray() val context = parser.parse(reqByteArray) assertEquals("localhost:8080", context.headers["Host"]) }
まず、request.headers
フィールドがないためコンパイルエラーになるので、RequestContext.kt
にフィールドを追加します。
data class RequestContext( val method: HttpMethod, val requestTarget: String, val httpVersion: HttpVersion) + { + val headers = mutableMapOf<String, String>() + }
これでコンパイルエラーはなくなりました。テストを実行すると、追加したテストが失敗しています。
以下のように、リクエストヘッダーをパースする実装を追加します。
- return RequestContext(httpMethod, + val req = RequestContext(httpMethod, startLineElements[1], httpVersion ) + reqString.split("\r\n\r\n")[0] + .split("\r\n") + .let { it.slice(1 until it.size) } + .map { headerString -> + val indexOfFirstColon = headerString.indexOfFirst { it == ':' } + req.headers.put( + headerString.substring(0 until indexOfFirstColon), + headerString.substring(indexOfFirstColon + 1).trim()) + } + return req
これでテストが無事通過しました。
本当はリクエストのサイズとかもうすこし気にして処理する必要がありそうですが、今日はこの辺で……。
[まとめ]