骏马金龙 (新博客:www.junmajinlong.com)

网名骏马金龙,钟情于IT世界里的各种原理和实现机制,强迫症重症患者。爱研究、爱翻译、爱分享。特借此一亩三分田记录自己成长点滴!!!

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进制数。例如,空格被编码为"%20",斜线被编码为"%2f",有时候query的value中空格会被编码为"+"。

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

name=Hiram%20Veeblefeetzer&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 := "http://www.cnblogs.com/f-ck-need-u"
myurl,_ := url.Parse(urlstr)
fmt.Println(myurl.String())

输出:

http://www.cnblogs.com/f-ck-need-u

由于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())
}
posted @ 2018-11-26 16:09  骏马金龙  阅读(2316)  评论(0编辑  收藏  举报