Go 学习笔记(三十一)URLs

本文原创地址:博客园骏马金龙Go Web:URLs

URL 也是一个结构体:

type URL struct {
		Scheme     string
		Opaque     string    // encoded opaque data
		User       *Userinfo // username and password information
		Host       string    // host or host:port
		Path       string    // path (relative paths may omit leading slash)
		RawPath    string    // encoded path hint (see EscapedPath method)
		ForceQuery bool      // append a query ('?') even if RawQuery is empty
		RawQuery   string    // encoded query values, without '?'
		Fragment   string    // fragment for references, without '#'
}

URL 结构表示解析之后的 URL,一般格式为:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

由于 path 和 query 部分只能使用大小写字母、数字以及有限的几个特殊标点,其它所有的字符都需要进行 URL 编码:百分号 +2 位 16 进制数。例如,空格被编码为 "+",斜线被编码为 "%2f",有时候 query 的 value 中空格会被编码为 "+"。

例如,query 部分被编码后的内容如下:

name=Hiram+Veeblefeetzer&age=35&country=Madagascar+abc

它表示 3 个 key/value:name="Hiram Veeblefeetzer"age=35country=Madagascar abc

关于 URL,其中:

  • Host 字段是包含 host 和 port 两部分的,如果需要分别返回 host、port,使用URL.HostnameURL.Port
  • User 字段包含了 Username 和 Password,要分别返回它们,使用URL.User.Username()URL.User.Password()
  • Path 字段表示解码后的 URL 路径,也就是不带 "%" 的普通字符串路径
  • RawPaht 字段表示编码后的安全路径,即带上了 "%" 的路径
  • RawQuery 字段表示编码后的 Query,即打上了 "%" 的 query 字符串,它包含了所有 key/value,要分离每个 key/value,使用 ParseQuery() 方法将它们解析到一个 map 中

URL 解析示例

使用 URL 的Parse(string)方法可以将字符串构造成一个 URL 对象,并返回这个 URL 对象的指针。

现在使用这个方法来构造一个 URL 对象,并解析其中的各个部分:

package main

import "fmt"
import "net/url"

func main() {
	// 将字符串构造成URL对象
	s := "postgres://user:pass@host.com:5432/path?k=v#f"
	u, err := url.Parse(s)
	if err != nil {
		panic(err)
	}

	// 获取schema部分
	fmt.Println(u.Scheme)

	// User字段包含了Username和Password,需要分别获取
	fmt.Println(u.User)
	fmt.Println(u.User.Username())
	p, _ := u.User.Password()
	fmt.Println(p)

	// Host字段包含了hostname和port
	fmt.Println(u.Host)
	fmt.Println(u.Hostname())
	fmt.Println(u.Port())

	// 取得Path和Fragment字段
	fmt.Println(u.Path)
	fmt.Println(u.Fragment)

	// 取得query的key/value
	// 要取出各个key/value,使用ParseQuery()将RawQuery字段解析成map
	// key是字符串,value是字符串的slice,如果有key相同,则多个值放进这个slice
	fmt.Println(u.RawQuery)
	m, _ := url.ParseQuery(u.RawQuery)
	fmt.Println(m)
	fmt.Println(m["k"][0])
}

结果:

postgres
user:pass
user
pass
host.com:5432
host.com
5432
/path
f
k=v
map[k:[v]]
v

构造 URL

URL 的Parse(string)方法可以将字符串构造成一个 URL 对象,URL 的 String() 方法可以返回编码后的 URL 值。

例如:

urlstr := "https://qiankunpingtai.cn"
myurl,_ := url.Parse(urlstr)
fmt.Println(myurl.String())

输出:

https://qiankunpingtai.cn

由于 URL 的 path 和 query 部分可能包含特殊字符,直接使用纯字符串构造 URL 会不安全。应该使用另外两个函数将普通字符转换成编码后的字符:

func PathEscape(s string) string
func QueryEscape(s string) string

再将编码之后的 path 和 query 作为 Parse() 方法的一部分。

例如:

package main

import (
	"fmt"
	"net/url"
)

func main() {
	// 要构造:http://www.example.int/下
	// Path: search
	// Query: food=pie aaa
	//        action=like
	// 的URL
	s := "http://www.example.int"
	p := "search"
	path := url.PathEscape(p)

	// query部分
	qfood := "pie aaa"
	qaction := "like"
	qqfood := url.QueryEscape(qfood)
	qqaction := url.QueryEscape(qaction)
	// 将query组合起来
	query := "food=" + qqfood + "&action=" + qqaction

	// 构造url
	myurlstr := s + "/" + path + "?" + query
	myurl, err := url.Parse(myurlstr)
	if err != nil {
		panic(err)
	}

	// 解析URL
	fmt.Println(myurl.String())
	// 解析url的query部分
	fmt.Println(myurl.RawQuery)
	qmaps, _ := url.ParseQuery(myurl.RawQuery)
	fmt.Println(qmaps["food"])
	fmt.Println(qmaps["action"])
}

这很麻烦,对于 Query 部分,更好的方法是使用 Values.Encode() 方法,见后文。

url Path 部分

在 URL 结构中:有 Path 和 RawPath 两个字段

type URL struct{
	...
	Path     string    // path (relative paths may omit leading slash)
	RawPath  string    // encoded path hint (see EscapedPath method)
	...
}

其中 Path 是解码后的路径,RawPath 是编码后的路径。

前面解释了 PathEscape() 函数,它是将字符串转换为编码后的字符串,可以直接将编码后的结果作为构造 url 的 path 部分,这样是最安全的构造方式。

除此之外,还有一个 EscapePath()方法:如果 RawPath 存在且有效,则直接返回 RawPath 字段的值,如果不存在,则 EscapePath() 自己计算一个编码后的路径。

URL.String()方法是直接调用 EscapePath() 来构造路径部分的。

Userinfo

type Userinfo
func User(username string) *Userinfo
func UserPassword(username, password string) *Userinfo
func (u *Userinfo) Password() (string, bool)
func (u *Userinfo) String() string
func (u *Userinfo) Username() string

User() 函数构造一个 Userinfo,但只包含 Username 不包含 password。

UserPassword() 函数构造一个 Userinfo,包含 Username 和 password,但因为明文显示,不建议使用。

String()返回 "username[:password]" 格式的 username 和 Password。

Username()和 Password() 分别返回对应的部分。

Query 部分

URL 结构中有一个 RawQuery 字段:

type URL struct {
	...
	RawQuery string
	...
}

RawQuery 字段是编码后的 query。

有几个方法:

func (u *URL) Query() Values

它读取 RawQuery 字段的值并返回 Values 类型。但会直接丢弃错误或畸形的 query 部分,如果要检查错误或畸形,使用 ParseQuery() 函数。

注意上面 Query() 的返回值是 Values 类型,它是一个 map 结构:

type Values map[string][]string
func ParseQuery(query string) (Values, error)
func (v Values) Add(key, value string)
func (v Values) Del(key string)
func (v Values) Encode() string
func (v Values) Get(key string) string
func (v Values) Set(key, value string)

ParseQuery() 函数解析给定字符串 query 并将 query 的各个部分填充到返回值类型 Values 的 map 结构中,同时会检查错误。

Add()、Del()、Set()和 Get() 都用来操作 Values 的 map 结构,意义都很清晰。唯一需要注意的是,Add()是追加操作,Set() 是替换已有值,如果操作的 key 不存在,则直接创建。

Encode() 是将 Values 中的数据根据 key 排序后编码成 URL 的 query,且是编码后的 query。

下面是一个用法:

package main

import (
	"fmt"
	"net/url"
)

func main() {
	s := "http://www.example.int"
	p := "search"
	path := url.PathEscape(p)

	// query部分
	values := url.Values{}
	values.Set("food","pie aaa")
	values.Add("action","like")
	values.Add("name","abc")
	values.Add("name","def")
	values.Add("name","gh")
	// Encode() == "action=like&food=pie+aaa&name=abc&name=def&name=gh"
	query := values.Encode()

	// 构造url
	myurlstr := s + "/" + path + "?" + query
	myurl, err := url.Parse(myurlstr)
	if err != nil {
		panic(err)
	}

	// 解析url
	fmt.Println(myurl.String())
}

上一篇 Go 学习笔记(三十)Handler
Go 学习笔记(目录)
下一篇 Go 学习笔记(三十二)自带的 ServeMux multiplexer