1. 程式人生 > >Go Web: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:][//[[email protected]]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:[email protected]: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())
}