📝

【Go】配列のURLクエリパラメータを渡す推奨パターン

2023/11/14に公開

概要

HTTPのリクエストで配列のURLクエリパラメータ(クエリストリング / クエリ文字列)を渡す場合に、Goのnet/httpにおける推奨パターンを考察します。

cf. Query string - Wikipedia

環境

  • go 1.21.4

配列のURLクエリパラメータを受け渡すパターン

RFC3986 - Uniform Resource Identifier (URI): Generic Syntaxでは、配列のURLクエリパラメータを受け渡すパターンに関して明確に定義されていません。
言語やフレームワークごとに以下のようなパターンで受け渡しされることが多いようです。

  1. ?hobbies=programming&hobbies=sports
  2. ?hobbies[]=programming&hobbies[]=sports
  3. ?hobbies[1]=programming&hobbies[2]=sports
  4. ?hobbies=programming,sports

cf. How to pass an array within a query string? - Stack Overflow

Goのnet/httpにおけるパターン

Goのnet/httpでは、URLクエリパラメータは以下のように取得可能です。

import (
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	// クエリパラメータを取得
	queryParams := r.URL.Query()
  ...

cf. https://pkg.go.dev/net/url#URL.Query

このとき、返り値としてValuesが返却されますが、このValuesの型は map[string][]string です。
これはURLクエリパラメータが配列か単一かに限りません。
このValuesから任意のURLクエリパラメータを取得するには、以下のようにmapのキーに関連付けられた値を取得するGoの一般的な記法を利用するか、

  hobbies, ok := queryParams["hobbies"]

一致した最初のURLクエリパラメータの取得だけでよければ、提供されているGet を利用します。

  limit := queryParams.Get("limit")

上記のnet/httpの設計に沿い、実装コストが少なくよりシンプルに書ける方法を考えると、

  1. ?hobbies=programming&hobbies=sports

のパターンが最適だと思います。

2のパターンでも下記のように記述するだけなのであまり違いはないですが、そもそもPHPの記法なのと命名を複数形にすれば配列であることは自明のため、[] を付与することはGoのnet/httpにおいては余計でありシンプルさに欠けます。

  hobbies, ok := queryParams["hobbies[]"]

エビデンスとしては心許ないですが、Query() のユニットテストで bar のように複数の値を渡しているのでGo側も1のパターンを想定していそうです。

func TestQueryValues(t *testing.T) {
	u, _ := Parse("http://x.com?foo=bar&bar=1&bar=2&baz")
	v := u.Query()

cf. https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/net/url/url_test.go;l=130

実際にはクライアントとの兼ね合いもあるため、サーバーだけの理由では決められませんが特にクライアント側の合理的な要望がなければ1のパターンを採用するのがよさそうです。

GitHubで編集を提案

Discussion