[Go]net/urlパッケージを見てみる
記事の概要
業務にてGo言語の標準パッケージnet/url
パッケージを使用することがあったので、改めてnet/url
パッケージのドキュメントやコードを読んでまとめてみました。よく使用しそうな関数やメソッドをまとめておりますので、参考程度に見ていただければと思います。
対象読者
- Go言語を多少なりとも書ける方
- Go言語でurl操作をしたい方
- そういえば
net/url
パッケージ気になってたわ、という方
参考
net/url
パッケージを見てみる
そもそもnet/url
パッケージとは何をするものなのかというと、urlを解析したり、エスケープ処理をしたり、urlの内容を変更したり、url関連の操作をするパッケージになります。
Package url parses URLs and implements query escaping.
早速見ていきます。
func JoinPath
JoinPath
は指定されたパス要素をbaseの既存のパスに結合し、./
や../
などの不要な要素を取り除いたurl文字列を返す。
func JoinPath(base string, elem ...string) (result string, err error)
example
func main() {
base := "https://example.com"
result, err := url.JoinPath(base, "foo", ".", "bar", "/", "//buz")
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // https://example.com/foo/bar/buz
}
func PathEscape
PathEscape
は文字列をurlパスセグメント内に安全に配置できるようにエスケープをして、必要に応じて特殊文字をパーセントエンコーディングする
func PathEscape(s string) string
example
func main() {
result := url.PathEscape("my/cool+blog&about, stuff")
fmt.Println(result) // my%2Fcool+blog&about%2C%20stuff
}
func PathUnescape
PathUnescape
はPathEscape
の逆変換で、パーセントエンコーディングでエスケープされた文字列を、エスケープ前の状態に戻す
func PathUnescape(s string) (string, error)
example
func main() {
result, err := url.PathUnescape("my%2Fcool+blog&about%2Cstuff")
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // my/cool+blog&about, stuff
}
func QueryEscape
QueryEscape
はurlクエリ内に安全に配置できるよう、文字列をエスケープする
func QueryEscape(s string) string
example
func main() {
result := url.QueryEscape("my/cool+blog&about, stuff")
fmt.Println(result) // my%2Fcool%2Bblog%26about%2C+stuff
}
func QueryUnescape
QueryUnescape
はQueryEscape
の逆変換で、パーセントエンコーディングでエスケープされた文字列を、エスケープ前の状態に戻す
func QueryUnescape(s string) (string, error)
example
func main() {
result, err := url.QueryUnescape("my%2Fcool%2Bblog%26about%2C+stuff")
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // my/cool+blog&about, stuff
}
type URL
URL
は解析(parse)されたurlを表す構造体。
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port (see Hostname and Port methods)
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
OmitHost bool // do not emit empty host (authority)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
RawFragment string // encoded fragment hint (see EscapedFragment method)
}
一般的にURLの構造は以下のようになっているので、それにフィールドが対応している感じですね。
[scheme:][//[userinfo@]host][/]path[?query][#fragment]
example
func main() {
u, err := url.Parse("http://bing.com/search?q=dotnet")
if err != nil {
log.Fatal(err)
}
u.Scheme = "https"
u.Host = "google.com"
q := u.Query()
q.Set("q", "golang")
u.RawQuery = q.Encode()
fmt.Println(u) // https://google.com/search?q=golang
}
実際に各フィールドがどのように出力されるかが気になる方は以下の記事が参考になります。
func Parse
Parse
は生のurlを解析してURL
構造体に変換する
func Parse(rawURL string) (*URL, error)
func ParseRequestURI
ParseRequestURI
は生のurlを解析して、URL
構造体に変換する
func ParseRequestURI(rawURL string) (*URL, error)
func (*URL) String
String
はURL
構造体を有効なurl文字列に再構築する
func (u *URL) String() string
func (*URL) IsAbs
IsAbs
は絶対パス(スキームから始まるパス)かどうかを検証する
func (u *URL) IsAbs() bool
type Values
Values
は文字列と文字列のリストを持つmapの型で、主にはクエリパラメータやフォームの値に使用される
type Values map[string][]string
example
func main() {
v := url.Values{}
v.Set("name", "Ava")
v.Add("friend", "Jess")
v.Add("friend", "Sarah")
v.Add("friend", "Zoe")
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
fmt.Println(v.Get("name")) // Ava
fmt.Println(v.Get("friend")) // Jess
fmt.Println(v["friend"]) // [Jess Sarah Zoe]
}
func (Values) Set
Set
はキーと値をセットする(既存の値は入れ替わる)
func (v Values) Set(key, value string)
example
func main() {
v := url.Values{}
v.Add("cat sounds", "meow")
v.Add("cat sounds", "mew")
v.Add("cat sounds", "mau")
fmt.Println(v["cat sounds"]) // [meow mew mau]
v.Set("cat sounds", "meow")
fmt.Println(v["cat sounds"]) // [meow]
}
func (Values) Add
Add
はキーに対して値を追加する(Set
とは違い、既存の値は入れ替わらない)
func (v Values) Add(key, value string)
func (Values) Del
Del
は指定したキーの値を削除する
func (v Values) Del(key string)
example
func main() {
v := url.Values{}
v.Add("cat sounds", "meow")
v.Add("cat sounds", "mew")
v.Add("cat sounds", "mau")
fmt.Println(v["cat sounds"]) // [meow mew mau]
v.Set("cat sounds", "meow")
fmt.Println(v["cat sounds"]) // []
}
func (Values) Get
Get
は指定されたキーの最初の値を取得する
キーに関連付けられた値がない場合、Get
は空の文字列を返す
func (v Values) Get(key string) string
example
func main() {
v := url.Values{}
v.Add("cat sounds", "meow")
v.Add("cat sounds", "mew")
v.Add("cat sounds", "mau")
fmt.Printf("%q\n", v.Get("cat sounds")) // "meow"
fmt.Printf("%q\n", v.Get("dog sounds")) // ""
}
func (Values) Has
Has
は指定されたキーが設定されているかをチェックする
func (v Values) Has(key string) bool
example
func main() {
v := url.Values{}
v.Add("cat sounds", "meow")
v.Add("cat sounds", "mew")
v.Add("cat sounds", "mau")
fmt.Printf(v.Has("cat sounds")) // true
fmt.Printf(v.Has("dog sounds")) // false
}
補足
PathEscapeとQueryEscapeの違い
PathEscape
とQueryEscape
は同じくエスケープの処理なのですが、一部挙動が異なります。
というのも、「パスでは/
をエンコードしたくない」「クエリパラメータではスペースを+
に変換したい」など、それぞれのセグメントに応じて使い分けが必要になることもあるので、それに合わせてエンコードをいい感じにやってくれる、ということになります。
なので、実装の際には用途に合わせて使い分けるのが無難そうですね。
ParseとParseRequestURIの違い
両方とも受け取った文字列のurlをURL
構造体に変換している処理ですが、少しだけ用途が変わってきます。ドキュメントのコメントを読んでみると、
Parse parses a raw url into a URL structure.
The url may be relative (a path, without a host) or absolute (starting with a scheme). Trying to parse a hostname and path without a scheme is invalid but may not necessarily return an error, due to parsing ambiguities.
ParseRequestURI parses a raw url into a URL structure. It assumes that url was received in an HTTP request, so the url is interpreted only as an absolute URI or an absolute path. The string url is assumed not to have a #fragment suffix. (Web browsers strip #fragment before sending the URL to a web server.)
というふうに、相対パスを許容するかしないかの違いでした。
-
Parse()
- 相対パスを許容
-
ParseRequestURI()
- 相対パスを許容せずエラーを返す
- 絶対パス(スキームから始まるパス)のみ許容する
なので、urlの解析を絶対パスを想定しているか、相対パスを想定しているかで用途を分けるイメージが正しそうです。
コード内でurlを組み立てる時
プログラム内でurlを組み立てる時も少し注意が必要です。
以下のように文字列を組み立てる方法でも可能ではあるのですが、
url := fmt.Sprintf("%s/%s", endpoint, path)
url := endpoint + "/" + path
これだと万が一/
が一つ多い状態で来たり、urlで扱えない文字が来た時の想定ができてません。
なので、url.JoinPath()
やpath.Join()
などを使用してちゃんと対策をするほうが無難ではあります。
URL
構造体を使用して初期化をすることも筆者としてはオススメです。
url := &url.URL{
Scheme: "https",
Host: "example.com",
Path: "hoge/hoge",
}
Discussion