🤪
Java/Kotlin の HTTP 系ライブラリで「?&key=value」がどう扱われるか検証してみた
TLDR:

// クエリのサイズが・・・
// => 2
Android Uri, OkHttp HttpUrl
// => 1
Apache HttpComponents URIBuilder, Spring UrlComponents
// ?
Ktor
Why?
ある日、適当に Ktor で遊んでいたところ、以下のような問題に遭遇しました。
@Test
fun testEmptyQuery() {
val baseUrl = URLBuilder().apply {
encodedPath = "/"
parameters.appendAll("", emptyList())
parameters.append("key", "value")
}.build()
val url = URLBuilder(baseUrl).build()
// fail
// expect: /?&key=value
// actual: /?key=value
assertEquals(baseUrl.fullPath, url.fullPath)
}
URL クラスの作成方法によってクエリーパラメータの扱いが変わるというものです。
これはプルリクエストを作るチャンスですね。さっそく修正してテストを走らせてみました。
・・・テストで落ちるようになりました。おっと?
厄介なことに、「空のクエリーパラメータを維持する」ことを期待するテストと「空のクエリーパラメータを破棄する」ことを期待するテストの2つが存在していたのです。
両方維持することも出来なくはなさそうですが、片方に統一するのが健全でしょう。
というわけで「他のライブラリではどうなっているのか?」を検証してみました。
検証
検証コードはすごくシンプルです。
val url = TODO("それぞれのやり方で URL クラスを作成")
val params = TODO("〃クエリパラメータ作成")
println(params.size)
では検証していきます。
Android Uri
まず最初に思い付いたのがこれでした。
val url = Uri.parse("https://example.com?&key=value")
val params = url.queryParameterNames
println(params.size)
// => 2
はい、2です。
では次。
OkHttp HttpUrl
val url = HttpUrl.parse("https://example.com?&key=value")
val params = url.queryParameterNames
println(params.size)
// => 2
同じですね。現時点では残す側が優勢のようです。
Apache HttpComponents URIBuilder
val url = URIBuilder("https://example.com?&key=value")
val params = url.getQueryParams()
println(params.size)
// => 1
おっと、ここでついに1派が登場。
Spring UrlComponents
val url = UriComponentsBuilder.fromUriString("http://example.com?&key=value").build()
val params = url.queryParams
println(params.size)
// => 1
また1です。これで割れました。
結論
// クエリのサイズが・・・
// => 2
Android Uri, OkHttp HttpUrl
// => 1
Apache HttpComponents URIBuilder, Spring UrlComponents
// ?
Ktor
です。
どうしよう。どっちに合わせるべきなんだ。
おまけ
JavaScript の URLSearchParams を Chromium と Firefox で試してみました。
const params = new URLSearchParams("?&key=value")
console.log(params.size)
// => Chromium / Firefox のどちらも 1
どうしましょうねこれ。
Discussion