func PathEscape

1
func PathEscape(s string) string

PathEscape转义字符串,以便可以安全地放置在URL路径段中。

根据需要用%XX序列替换特殊字符(包括/)

func PathUnescape

1
func PathUnescape(s string) (string, error)

PathUnescape执行PathEscape的逆变换,将形式为“%AB”的每个3字节编码子字符串转换为十六进制解码字节0xAB。如果任何%后面没有两个十六进制数字,则返回错误。

PathUnescape与QueryUnescape完全相同,只是它不会将’+‘转换为’’(空格)。

func QueryEscape

1
func QueryEscape(s string) string

QueryEscape函数对s进行转码使之可以安全的用在URL查询里。

func QueryUnescape

1
func QueryUnescape(s string) (string, error)

QueryUnescape函数用于将QueryEscape转码的字符串还原。它会把%AB改为字节0xAB,将’+‘改为’ ‘。如果有某个%后面未跟两个十六进制数字,本函数会返回错误。

type Error

1
2
3
4
5
type Error struct {
    Op  string
    URL string
    Err error
}

Error会报告一个错误,以及导致该错误发生的URL和操作。

func (*Error) Error

1
func (e *Error) Error() string

func (*Error) Temporary

1
func (e *Error) Temporary() bool

func (*Error) Timeout

1
func (e *Error) Timeout() bool

func (*Error) Unwrap 1.13

1
func (e *Error) Unwrap() error

type EscapeError

1
type EscapeError string

func (EscapeError) Error

1
func (e EscapeError) Error() string

type InvalidHostError

1
type InvalidHostError string

func (InvalidHostError) Error

1
func (e InvalidHostError) Error() string

type URL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type URL struct {
    Scheme     string
    Opaque     string    // 编码后的不透明数据
    User       *Userinfo // 用户名和密码信息
    Host       string    // host或host:port
    Path       string    // path (relative paths may omit 双斜线)
    RawPath    string    // encoded path hint (see EscapedPath method); added in Go 1.5
    ForceQuery bool      // append a query ('?') even if RawQuery is empty; added in Go 1.7
    RawQuery   string    // 编码后的查询字符串,没有'?'
    Fragment   string    // 引用的片段(文档位置),没有'#'
}

URL类型代表一个解析后的URL(或者说,一个URL参照)。URL基本格式如下:

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

scheme后不加双斜线的URL被解释为如下格式:

1
scheme:opaque[?query][#fragment]

注意路径字段是以解码后的格式保存的,如/%47%6f%2f会变成/Go/。这导致我们无法确定Path字段中的斜线是来自原始URL还是解码前的%2f。除非一个客户端必须使用其他程序/函数来解析原始URL或者重构原始URL,这个区别并不重要。这种情况下,可以使用RawPath,这是一个可选字段,仅当默认编码与Path不同时才设置该字段。URL的String方法使用EscapedPath方法获取路径。有关更多详细信息,请参见EscapedPath方法。

举例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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)

Output:

1
https://google.com/search?q=golang
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	// Parse + String preserve the original encoding.
	u, err := url.Parse("https://example.com/foo%2fbar")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Path)
	fmt.Println(u.RawPath)
	fmt.Println(u.String())
}

Putput:

1
2
3
/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

func Parse

1
func Parse(rawurl string) (url *URL, err error)

Parse函数解析rawurl为一个URL结构体,

rawurl可以是相对的(a path, without a host)或绝对的(starting with a scheme)。 尝试在不使用方案的情况下解析主机名和路径是无效的,但由于解析不明确,可能不一定会返回错误。

func ParseRequestURI

1
func ParseRequestURI(rawurl string) (url *URL, err error)

ParseRequestURI函数解析rawurl为一个URL结构体,本函数会假设rawurl是在一个HTTP请求里,因此会假设该参数是一个绝对URL或者绝对路径,并会假设该URL没有#fragment后缀。(网页浏览器会在去掉该后缀后才将网址发送到网页服务器)

func (*URL) EscapedPath

1
func (u *URL) EscapedPath() string

EscapedPath返回转义形式的u.Path。 通常,任何路径都有多种可能的转义形式。 当escapedPath是u.Path的有效转义时,它返回u.RawPath。 否则,EscapedPath会忽略u.RawPath并自行计算转义表单。 String和RequestURI方法使用EscapedPath构造其结果。 通常,代码应该调用EscapedPath而不是直接读取u.RawPath。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("http://example.com/path with spaces")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.EscapedPath())
}

output:

1
/path%20with%20spaces

func (*URL) Hostname

1
func (u *URL) Hostname() string

返回u.Host,没有任何端口号。

如果Host是具有端口号的IPv6文本,则Hostname将返回不带方括号的IPv6文本。 IPv6文字可能包含区域标识符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org:8000/path")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Hostname())
	u, err = url.Parse("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Hostname())
}

output:

1
2
example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334

func (*URL) IsAbs

1
func (u *URL) IsAbs() bool

IsAbs报告URL是否为绝对URL。绝对表示它具有非空scheme。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"fmt"
	"net/url"
)

func main() {
	u := url.URL{Host: "example.com", Path: "foo"}
	fmt.Println(u.IsAbs())
	u.Scheme = "http"
	fmt.Println(u.IsAbs())
}
1
2
false
true

func (*URL) MarshalBinary

1
func (u *URL) MarshalBinary() (text []byte, err error)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, _ := url.Parse("https://example.org")
	b, err := u.MarshalBinary()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", b)
}

output:

1
https://example.org

func (*URL) Parse

1
func (u *URL) Parse(ref string) (*URL, error)

Parse在接收方的上下文中解析URL。提供的URL可以是相对的或绝对的。解析失败时,解析返回nilerr,否则其返回值与ResolveReference相同。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org")
	if err != nil {
		log.Fatal(err)
	}
	rel, err := u.Parse("/foo")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(rel)
	_, err = u.Parse(":foo")
	if _, ok := err.(*url.Error); !ok {
		log.Fatal(err)
	}
}

output:

1
https://example.org/foo

func (*URL) Port

1
func (u *URL) Port() string

port返回u.Host的port部分,没有前导冒号。如果u.Host不包含合法的数字类型端口,则Port返回空字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Port())
	u, err = url.Parse("https://example.org:8080")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Port())
}

output:

1
2

8080

func (*URL) Query

1
func (u *URL) Query() Values

Query方法解析RawQuery字段并返回其表示的Values类型键值对。它默默地丢弃格式错误的值对。要检查错误,请使用ParseQuery。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org/?a=1&a=2&b=&=3&&&&")
	if err != nil {
		log.Fatal(err)
	}
	q := u.Query()
	fmt.Println(q["a"])
	fmt.Println(q.Get("b"))
	fmt.Println(q.Get(""))
}

output:

1
2
3
[1 2]

3

func (*URL) RequestURI

1
func (u *URL) RequestURI() string

RequestURI方法返回编码好的path?query或opaque?query字符串,用在HTTP请求里。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org/path?foo=bar")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.RequestURI())
}

/path?foo=bar

func (*URL) ResolveReference

1
func (u *URL) ResolveReference(ref *URL) *URL

本方法根据一个绝对URI将一个URI补全为一个绝对URI,参见RFC 3986 节 5.2。参数ref可以是绝对URI或者相对URI。ResolveReference总是返回一个新的URL实例,即使该实例和u或者ref完全一样。如果ref是绝对URI,本方法会忽略参照URI并返回ref的一个拷贝。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("../../..//search?q=dotnet")
	if err != nil {
		log.Fatal(err)
	}
	base, err := url.Parse("http://example.com/directory/")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(base.ResolveReference(u))
}
1
http://example.com/search?q=dotnet

func (*URL) String

1
func (u *URL) String() string

String将URL重构为一个合法URL字符串。

1
2
scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

如果u.Opaque非空,String使用第一个格式;否则它使用第二种形式。要获取路径,String使用u.EscapedPath()。

在第二种形式中,适用以下规则:

1
2
3
4
5
6
7
8
9
- if u.Scheme is empty, scheme: is omitted.
- if u.User is nil, userinfo@ is omitted.
- if u.Host is empty, host/ is omitted.
- if u.Scheme and u.Host are empty and u.User is nil,
   the entire scheme://userinfo@host/ is omitted.
- if u.Host is non-empty and u.Path begins with a /,
   the form host/path does not add its own /.
- if u.RawQuery is empty, ?query is omitted.
- if u.Fragment is empty, #fragment is omitted.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
	"fmt"
	"net/url"
)

func main() {
	u := &url.URL{
		Scheme:   "https",
		User:     url.UserPassword("me", "pass"),
		Host:     "example.com",
		Path:     "foo/bar",
		RawQuery: "x=1&y=2",
		Fragment: "anchor",
	}
	fmt.Println(u.String())
	u.Opaque = "opaque"
	fmt.Println(u.String())
}

output:

1
2
https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor

func (*URL) UnmarshalBinary

1
func (u *URL) UnmarshalBinary(text []byte) error
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u := &url.URL{}
	err := u.UnmarshalBinary([]byte("https://example.org/foo"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", u)
}

output:

1
https://example.org/foo

type Userinfo

1
2
3
type Userinfo struct {
    // 内含隐藏或非导出字段
}

Userinfo类型是一个URL的用户名和密码细节的一个不可修改的封装。一个真实存在的Userinfo值必须保证有用户名(但根据 RFC 2396可以是空字符串)以及一个可选的密码。

func User

1
func User(username string) *Userinfo

User函数返回一个用户名设置为username的不设置密码的*Userinfo。

func UserPassword

1
func UserPassword(username, password string) *Userinfo

UserPassword函数返回一个用户名设置为username、密码设置为password的*Userinfo。

这个函数应该只用于老式的站点,因为风险很大,不建议使用,参见RFC 2396。

func (*Userinfo) Username

1
func (u *Userinfo) Username() string

Username方法返回用户名。

func (*Userinfo) Password

1
func (u *Userinfo) Password() (string, bool)

如果设置了密码返回密码和真,否则会返回假。

func (*Userinfo) String

1
func (u *Userinfo) String() string

String方法返回编码后的用户信息,格式为"username[:password]"。

type Values

1
type Values map[string][]string

Values将建映射到值的列表。它一般用于查询的参数和表单的属性。不同于http.Header这个字典类型,Values的键是大小写敏感的。

Example

1
2
3
4
5
6
7
8
9
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"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])

Output:

1
2
3
Ava
Jess
[Jess Sarah Zoe]

func ParseQuery

1
func ParseQuery(query string) (m Values, err error)

ParseQuery函数解析一个URL编码的查询字符串,并返回可以表示该查询的Values类型的字典。本函数总是返回一个包含了所有合法查询参数的非nil字典,err用来描述解码时遇到的(如果有)第一个错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/url"
	"strings"
)

func main() {
	m, err := url.ParseQuery(`x=1&y=2&y=3;z`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(toJSON(m))
}

func toJSON(m interface{}) string {
	js, err := json.Marshal(m)
	if err != nil {
		log.Fatal(err)
	}
	return strings.Replace(string(js), ",", ", ", -1)
}

output:

1
{"x":["1"], "y":["2", "3"], "z":[""]}

func (Values) Get

1
func (v Values) Get(key string) string

Get会获取key对应的值集的第一个值。如果没有对应key的值集会返回空字符串。获取值集请直接用map。

func (Values) Set

1
func (v Values) Set(key, value string)

Set方法将key对应的值集设为只有value,它会替换掉已有的值集。

func (Values) Add

1
func (v Values) Add(key, value string)

Add将value添加到key关联的值集里原有的值的后面。

func (Values) Del

1
func (v Values) Del(key string)

Del删除key关联的值集。

func (Values) Encode

1
func (v Values) Encode() string

Encode方法将v编码为url编码格式(“bar=baz&foo=quux”),编码时会以键进行排序。

QueryEscape与PathEscape

区别:URL中的空格

W3C标准规定,当Content-Type为application/x-www-form-urlencoded时,URL中查询参数名和参数值中空格要用加号+替代,所以几乎所有使用该规范的浏览器在表单提交后,URL查询参数中空格都会被编成加号+。

而在另一份规范(RFC 2396,定义URI)里, URI里的保留字符都需转义成%HH格式(Section 3.4 Query Component),因此空格会被编码成%20,加号+本身也作为保留字而被编成%2B,对于某些遵循RFC 2396标准的应用来说,它可能不接受查询字符串中出现加号+,认为它是非法字符。所以一个安全的举措是URL中统一使用%20来编码空格字符。

QueryEscape会把空格转成+,PathEscape会把空格转成%20

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// QueryEscape escapes the string so it can be safely placed

// inside a URL query.

func QueryEscape(s string) string {

    return escape(s, encodeQueryComponent)

}

// PathEscape escapes the string so it can be safely placed                                                                                                                                                                           

// inside a URL path segment.

func PathEscape(s string) string {                                                                                                                                                                                                     

    return escape(s, encodePathSegment)

}

与上述两个函数配套使用的是QueryUnescape和PathUnescape,切记不能交叉使用.

Go语言的默认选项

默认情况下,golang将空格转码为加号,也就是说使用PathEscape和PathUnescape

Query:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "fmt"

import "net/url"


func main() {
	query := url.Values{}
	query.Add("a", " ")
	query.Add("b", "*")
	fmt.Println(query.Encode())
}
1
a=+&b=%2A

URL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
)

import "net/url"


func main() {
	AA := "hello world"
	req := "https://www.baidu.com?query=" + AA
	u, _ := url.Parse(req)
	q := u.Query()
	u.RawQuery = q.Encode() //urlencode
	fmt.Println(u.String())
}
1
https://www.baidu.com?query=hello+world

PathUnescape的使用场景

因为PathUnescape会将’/‘和’?‘转换成%xx的形式,所以不能用于整体url的编码,我们常用PathUnescape将query部分转换成url安全的字符串,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
)

import "net/url"

func main() {
	//编码query部分
	AA := "hello world"
	ss := url.PathEscape("data=" + AA)
	req := "http://127.0.0.1:8001/api" + "?" + ss
	fmt.Println(req)
	//编码整体url
	url := url.PathEscape("http://127.0.0.1:8001/api" + "?" + "data=" + "hello world")
	fmt.Println(url)
}

output:

1
2
http://127.0.0.1:8001/api?data=hello%20world
http:%2F%2F127.0.0.1:8001%2Fapi%3Fdata=hello%20world

Values用法

修改URL的query失效

先看一段代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
    "net/url"
    "fmt"
)

func main(){
    urlObj, _ := url.Parse("http://www.baidu.com/aaa?keyword=mp3")
    fmt.Println(urlObj.String())
    urlObj.Query().Add("kkk", "vvv")
    fmt.Println(urlObj.Query().Get("kkk"))
    fmt.Println(urlObj.String())
}

这段代码运行的结果是:

1
2
3
http://www.baidu.com/aaa?keyword=mp3

http://www.baidu.com/aaa?keyword=mp3

显然结果中并没有把参数kkk=vvv加到url中,直接从api看,应该会加进去的啊,那为啥?只能看go的源代码了。

1
2
3
4
5
6
7
8
9
type URL struct {
    Scheme   string
    Opaque   string    // encoded opaque data
    User     *Userinfo // username and password information
    Host     string
    Path     string
    RawQuery string // encoded query values, without '?'
    Fragment string // fragment for references, without '#'
}

从这里看到query在URL对象中不是以一个map的形式存储的,而是一个原生的字符串

1
2
3
4
5
// Query parses RawQuery and returns the corresponding values.
func (u *URL) Query() Values {
    v, _ := ParseQuery(u.RawQuery)
    return v
}

从这里可以看到ParseQuery的参数为URL.RawQuery字段,根据参数的值传递机制,ParseQuery函数的返回值v是一个与u.RawQuery无关的一个Values对象,此时对Values对象进行AddSetDel的操作都不会改变u中的RawQuery。所以要想把修改URL对象,就需要把修改后的Values转成字符串,再赋值给u.RawQuery字段。看下修改后的代码:

1
2
3
4
5
6
7
8
9
func main(){
    urlObj, _ := url.Parse("http://www.baidu.com/aaa?keyword=mp3")
    fmt.Println(urlObj.String())
    v := urlObj.Query()
    v.Add("kkk", "vvv")
    urlObj.RawQuery = v.Encode()
    fmt.Println(urlObj.Query().Get("kkk"))
    fmt.Println(urlObj.String())
}

这样运行结果就正确了:

1
2
3
http://www.baidu.com/aaa?keyword=mp3
vvv
http://www.baidu.com/aaa?kkk=vvv&keyword=mp3

Add与Set

Add与Set是有区别的,Golang底层Header的实现是一个map[string][]string,Set方法如果原来url中没有值,那么是没问题的,如果有值,会将原来的值替换掉。而Add的话,是在原来值的基础上,再append一个值,例如,原来header的值是“s”,我后req.Header.Add一个”a”的话,变成了[s a]。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
    "fmt"
    "net/url"
)

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"))
    fmt.Println(v.Get("friend"))
    fmt.Println(v["friend"])
}

如何将一个url转义

问题

1
2
3
AA := "hello word"
url := " http://127.0.0.1:8001/api?data=" + AA
res,_ := httpGet(url)

这样请求会报错,因为url里的query有空格.那么golang有没有将url转义的方法?

PathEscape

利用PathEscape将Query部分转义,然后拼接query前面的部分:scheme://[userinfo@]host/path?

代码如下:

1
2
3
4
AA := "hello world"
ss := url.PathEscape("data=" + AA)
req := "http://127.0.0.1:8001/api?" + ss
res, _ := http.Get(req)

query

先将scheme://[userinfo@]host/path部分转化成url.URL结构体,再在结构体内添加Query,最后将结构体转化成rawurl:

代码如下:

1
2
3
4
5
6
7
AA := "hello world"
req := "http://127.0.0.1:8001/api"
u, _ := url.Parse(req)
q := u.Query()
q.Add("data", AA)
u.RawQuery = q.Encode() //urlencode
res, _ := http.Get(u.String())

参考: https://www.jianshu.com/p/2ba7dda583b5 http://www.voidcn.com/article/p-cznfozwq-pb.html http://rongmayisheng.com/?p=273