在日常编码中,我曾不止一次对于go语言的url.URL结构中OpaqueRawPath字段含义产生过疑义,不知其有何意义。 比如如下代码

1
2
uStr := "http://root:password@localhost:28080/home/login?id=1&name=foo#fragment"
u, _ := url.Parse(uStr)

其对应u中字段的值如下截图

其中的OpaqueRawPath字段为空,我对其具体是什么含义带有些许的疑问。如果你确实也疑惑过,那么下面将为您简单讲解这两个字段的作用。

在讲解Opaque字段前,先介绍下几个概念,URIURLURN

  • 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)两类:

  • opaquescheme-specific-part 不以 / 开头,是一个整体。呈 [scheme]:opaque[?query][#fragment] 的形式。如:mailto:qiuyue9971@126.comwww.google.com:443, opaque 必须是绝对的。
  • hierarchicalscheme-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语言推荐我们使用URLEscapedPath方法而不是直接使用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": ""
}

此时ForceQuerytrue

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对象。