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:
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())
}
|
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:
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:
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:
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())
}
|
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