在日常编码中,我曾不止一次对于go语言的url.URL
结构中Opaque
和RawPath
字段含义产生过疑义,不知其有何意义。
比如如下代码
1
2
|
uStr := "http://root:password@localhost:28080/home/login?id=1&name=foo#fragment"
u, _ := url.Parse(uStr)
|
其对应u
中字段的值如下截图

其中的Opaque
、RawPath
字段为空,我对其具体是什么含义带有些许的疑问。如果你确实也疑惑过,那么下面将为您简单讲解这两个字段的作用。
在讲解Opaque
字段前,先介绍下几个概念,URI
,URL
,URN
URI = Universal Resource Identifier
统一资源标志符,用来标识抽象或物理资源的一个字符串。
URL = Universal Resource Locator
统一资源定位符,表明在一个资源在网络中的访问方式
URN = Universal Resource Name
统一资源名称,通过特定命名空间中的唯一名称或ID来标识资源
URI
的格式
一个标准的 URI
格式为:[scheme:]scheme-specific-part
,其中scheme
是可选的。如:http://qiuyueqy.com
, mailto:qiuyue9971@126.com
, news:comp.go.lang
,/home/path/
。
URI
可以细分为为不透明的
(opaque)和分层的
(hierarchical)两类:
opaque
指 scheme-specific-part
不以 /
开头,是一个整体。呈 [scheme]:opaque[?query][#fragment]
的形式。如:mailto:qiuyue9971@126.com
,www.google.com:443
, opaque
必须是绝对的。
hierarchical
指 scheme-specific-part
以 /
开头且可以划分为好几部分。呈 [scheme:][//[userinfo@]host[:port]]path[?query][#fragment]
的形式。如:http://qiuyueqy.com/categories/
, hierarchical
可以是绝对的,也可以是相对的,如:https://github.com/yue-qiu/CUG_EmptyClassroom
, ../../static/verify.js
判断是opaque
或者分层的依据就是判断scheme:
后面是否以/
开头
GO语言中的URL
GO语言中的URL
定义如下,实际该类型是一个URI
的含义,可以从该结构体的头部注释可以看到,因此我们可以直接将该类型当做URI
来理解即可。
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
26
27
|
// A URL represents a parsed URL (technically, a URI reference).
//
// The general form represented is:
//
// [scheme:][//[userinfo@]host][/]path[?query][#fragment]
//
// URLs that do not start with a slash after the scheme are interpreted as:
//
// scheme:opaque[?query][#fragment]
type URL struct {
Scheme string // 协议
Opaque string // 如果是opaque格式,那么此字段存储有值
User *Userinfo // 用户和密码信息
Host string // 主机地址[:端口]
Path string // 路径
RawPath string // 如果Path是从转移后的路径解析的,那么RawPath会存储原始值,否则为空,见后面详解
ForceQuery bool // 即便RawQuery为空,path结尾也有?符号
RawQuery string // ?后面query内容
Fragment string // #后面锚点信息
RawFragment string // 与RawPath含义一致
}
type Userinfo struct {
username string
password string
passwordSet bool
}
|
Opaque
类型
字段的含义基本已经在注释中简述了。现在来看看其中Opaque
字段。
1
2
|
uStr := "http://root:password@localhost:28080/home/login?id=1&name=foo#fragment"
u, _ := url.Parse(uStr)
|
此时的到Opaque
为空,因为这个url
是一个分层类型
1
2
|
uStr := "ibsn:3242-4242-3244sf3-324s?pb=2021-05-28"
u, _ := url.Parse(uStr)
|
解析后得到个字段的值如下
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Scheme": "ibsn",
"Opaque": "3242-4242-3244sf3-324s",
"User": null,
"Host": "",
"Path": "",
"RawPath": "",
"ForceQuery": false,
"RawQuery": "pb=2021-05-28",
"Fragment": "",
"RawFragment": ""
}
|
可见Opaque
字段是在URL
类型为不透明
类型时才有意义
RawPath
不知道你是否对于注意到过经常性解析出来后URL
中的RawPath
是空的。
例如
1
2
|
uStr := "http://root:password@localhost:28080/home/login?id=1&name=foo#fragment"
u, _ := url.Parse(uStr)
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Scheme": "http",
"Opaque": "",
"User": {}, // 这里输出为空,是因为我使用了json.MarshalIndent来打印,而Userinfo 类型中的字段都是非导出的,所以序列化时为空,并非没有值
"Host": "localhost:28080",
"Path": "/home/login",
"RawPath": "", //这里为空,属实有点奇怪
"ForceQuery": false,
"RawQuery": "id=1\u0026name=foo",
"Fragment": "fragment",
"RawFragment": ""
}
|
对应的RawPath
为空。
我们再来试另外一个例子
1
2
|
uStr := "http://root:password@localhost:28080/home%2flogin?id=1&name=foo#fragment"
u, _ := url.Parse(uStr)
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Scheme": "http",
"Opaque": "",
"User": {},
"Host": "localhost:28080",
"Path": "/home/login",
"RawPath": "/home%2flogin",
"ForceQuery": false,
"RawQuery": "id=1\u0026name=foo",
"Fragment": "fragment",
"RawFragment": ""
}
|
可以看到此时RawPath
有值,为原始的path
值,而字段Path
存储的是将原始值反转义后的值。
对应的源码:src/net/url/url.go:676 #go1.16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func (u *URL) setPath(p string) error {
path, err := unescape(p, encodePath) // 此处将原始path做反转义,将反转移后的path赋值给Path
if err != nil {
return err
}
u.Path = path
if escp := escape(path, encodePath); p == escp { // 再将path做转移后与原始path作比较,如果不同,则将原始path放入到rawpath变量中
// Default encoding is fine.
u.RawPath = ""
} else {
u.RawPath = p
}
return nil
}
|
看到这里,大家应该就能明白了,RawPath
只有在原始path
中包含了转移字符时才会有值。然而知道这一点并没有什么用,GO语言推荐我们使用URL
的EscapedPath
方法而不是直接使用RawPath
字段
1
2
|
// In general, code should call EscapedPath instead of
// reading u.RawPath directly.
|
ForceQuery
比较简单,这里直接给出例子
1
2
|
uStr := "http://root:password@localhost:28080/home/login?#fragment"
u, _ := url.Parse(uStr)
|
输出
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Scheme": "http",
"Opaque": "",
"User": {},
"Host": "localhost:28080",
"Path": "/home/login",
"RawPath": "",
"ForceQuery": true,
"RawQuery": "",
"Fragment": "fragment",
"RawFragment": ""
}
|
此时ForceQuery
为true
。
URL
常用方法
EscapedPath
方法
1
2
3
4
5
6
7
8
9
10
11
|
// 1. path中不含转移字符
uStr := "http://root:password@localhost:28080/home/login?#fragment"
u, _ := url.Parse(uStr)
fmt.Print(u.EscapedPath())
//out: /home/login
// 2. path中含有转移字符
uStr := "http://root:password@localhost:28080/home%2flogin?#fragment"
u, _ := url.Parse(uStr)
fmt.Print(u.EscapedPath())
//out:/home%2flogin
|
可见EscapedPath
会原样返回path
内容,不管是否含有转义。
String
方法
1
2
3
4
5
6
7
8
9
|
uStr := "http://root:password@localhost:28080/home%2flogin?#fragment"
u, _ := url.Parse(uStr)
fmt.Print(u.String())
//out:http://root:password@localhost:28080/home%2flogin?#fragment
uStr := "mailto:xxxx.gmail.com"
u, _ := url.Parse(uStr)
fmt.Print(u.String())
//out:mailto:xxxx.gmail.com
|
可见String
方案会按照URI
格式输出。
Redacted
方法
1
2
3
4
|
uStr := "http://root:password@localhost:28080/home%2flogin"
u, _ := url.Parse(uStr)
fmt.Print(u.Redacted())
//out:http://root:xxxxx@localhost:28080/home%2flogin
|
Redacted
方法会对密码脱敏
IsAbs
方法
该方法得到URL
是否是绝对的。判断依据就是scheme
是否为空,是的话就是绝对的。对应源码
1
2
3
4
5
|
// IsAbs reports whether the URL is absolute.
// Absolute means that it has a non-empty scheme.
func (u *URL) IsAbs() bool {
return u.Scheme != ""
}
|
Parse
方法
将一个字符串解析为一个URL
对象。