[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