认识 URL 及其编码

2020-05-22 17:30:21 浏览数 (1)

1.URL 是什么

URL(Uniform Resoure Locator)中文译为统一资源定位符,是 Internet 上资源的地址,比如一个文本文件,一张图片,一个视频。通过 URL 我们可以知道网络资源的位置以及访问它的协议。

URL 由互联网工程任务组织(IETF)URI 工作小组制定并成为一个互联网标准,收录于 RFC1738。

2.URL 一般语法

正如有许多不同的获取资源的方法一样,描述这些资源的位置也有多种方案。不同的协议有不同的 URL 语法,但是 URL 的通用语法为其他协议建立新的方案提供了框架。一般而言,URL 的编写方式如下:

代码语言:javascript复制
 <scheme>:<scheme-specific-part>

URL 包含特定的协议名称<scheme>,后面是冒号,然后是字符串,该字符串的解释取决于不同协议的具体实现。

3.具体协议

Internet 有很多应用层的协议可以获取网络资源,其 URL 的格式语法会有所不同。以下是常见的协议。

代码语言:javascript复制
ftp                     File Transfer protocol
http                    Hypertext Transfer Protocol
gopher                  The Gopher protocol
mailto                  Electronic mail address
news                    USENET news
nntp                    USENET news using NNTP access
telnet                  Reference to interactive sessions
wais                    Wide Area Information Servers
file                    Host-specific file names
prospero                Prospero Directory Service

虽然 URL 除协议外的其余部分的语法可能会因特定协议而有所不同,但涉及直接对 Internet 上的指定主机使用基于 IP 的协议的 URL,<scheme-specific-part> 拥有通用的语法:

代码语言:javascript复制
 //<user>:<password>@<host>:<port>/<url-path>

其中<user>:<password>@<password>:<port>/<url-path>都是可选的。URL 协议特定部分以双斜杠 “//” 开头,表明它符合通用 Internet 协议语法。不同的组件遵循以下规则:

  • user 一个可选的用户名。有些协议(例如 ftp)允许指定用户名。
  • password 一个可选的密码。如果存在,则在用户名后面用冒号分隔。

用户名和密码如果存在,后面跟着一个 @ 字符。在用户名和密码字段中,必须对字符 : @ / 进行编码。

  • host 必选的网络主机名由合法的域名或者 IP 表示,其中合法的域名中一系列域标签由点号分隔,每个域标签以字母数字字符开头和结尾,中间可能还包含 - 字符,最右边的域标签永远不会以数字开头详情可参考 Section 3.5 of RFC 1034 和 Section 2.1 of RFC 1123。
  • port 可选的端口号。大多数协议具有默认端口号,所以有时可以省略。端口号以十进制为单位,与主机用冒号分隔,如果省略了端口,冒号也是可以省略的。
  • url-path url-path 指定了资源在主机上的具体位置,其语法取决于所使用的协议。

4.HTTP(S) URL

在了解了 URL 基本概念以及基本组成结构后,下面重点讲解 HTTP URL 的语法,要想了解其他协议的 URL 的具体实现可以查阅 rfc1738。

HTTP(S) 不允许使用用户名或密码,一个合法的 HTTP(S) URL 格式如下:

代码语言:javascript复制
http(s)://<host>:<port>/<path>?<query>#<frag>

(1)协议名:http 或 https 协议; (2)<host>:主机名。一个 URL 中,既可以使用域名也可以使用 IP 表示主机地址; (3)<port>:端口。主机名和端口之间使用冒号分隔。端口是可选的,如果省略将采用默认端口,http 默认端口是 80,https 默认端口 443; (4)<path>:资源路径。资源在网络主机上的路径,路径也是可选的,缺省访问默认资源; (5)<query>:查询参数。格式为 key=value,多个参数使用 & 分隔;参数也是可选的; (6)<frag>:片段。从 # 开始到最后,一般用于定位到资源内的一个片段,比如文档的一个章节;片段也是可选的。

示例如下:

代码语言:javascript复制
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name

观察示例,对照 HTTP URL 的格式,我们可以知道: (1)http 为协议; (2)www.aspxfans.com 为主机域名; (3)8080 为端口号; (4)news/index.asp 为资源路径; (5)boardID=5&ID=24618&page=1 为查询参数; (6)name 为资源内的一个片段。

5.HTTP(S) URL 特殊字符

通过上面对 HTTP(S) URL 格式的分析,我么可以很清楚地知道 HTTP(S) URL 中的特殊字符。

字符

特殊含义

十六进制值

:

协议与URL 实现部分以及主机名与端口号之间的分隔符

:

/

分隔目录和子目录

/

&

URL 中指定的参数间的分隔符

&

=

URL 中指定参数的值

=

?

分隔实际的 URL 和参数

?

#

表示书签

#

表示空格

%

指定特殊字符

%

当以上字符在不表示其特殊含义时,出现在 HTTP(S) URL 中均需要对其编码。

6.URL 编码

6.1 为什么需要对 URL 编码

URL 是一个字符序列,由数字、字母和特殊字符组成。对 URL 进行编码主要原因有如下几点:

  • ASCII 无法表示的字符 URL 只使用 ASCII 编码字符集中的可打印字符来编写,因此不可打印字符以及 ASCII 之外的字符如果出现在 URL 中,都需要进行编码;
  • 不安全的字符 由于多种原因,有些字符可能是不安全的。比如空字符是不安全的,因为当 URL 被排版或接受文字处理程序处理时,重要的空格可能会消失,也可能会引入无关紧要的空格。字符 <> 是不安全的,因为它们被用作自由文本中 URL 周围的分隔符。引号"用于在某些系统中分隔 URL。字符 # 不安全,应该总是被编码,因为它用于万维网和某些协议(如 HTTP(S))中片段/锚的分隔符。字符 % 不安全,因为它用于其他字符的编码。还有些字符也不安全,因为已知网关和其他传输代理有时会修改这样的字符,这些字符有:{ } | ^ ~ ` [ ]
  • 保留字符 许多 URL 方案将某些字符保留为一种特殊的含义:它们在 URL 的方案特定部分中的出现具有指定的语义。如果 URL 中出现了不表示其特殊含义的保留字符,则必须对保留字符进行编码。比如字符; / ? : @ = &是方案中可能保留的具有特殊含义的字符。

因此只有字母和数字[0-9a-zA-Z]、具有特殊含义的保留字符以及非保留字符,才可以不经过编码直接用于 URL。

6.2 URL 编码规则

RFC3986 规定了 URL 中非保留字符,即无需转义的没有任何特殊含义的字符,其定义如下:

代码语言:javascript复制
unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"

ALPHA 表示 52 大小写英文字母,DIGIT 表示 10 个数字,后面依次时连字符、点号、下划线和波浪号。

除了上面非保留字符,其他任何字符出现在 URL 的不同部分时,如果与该部分的保留字符发生冲突或不可打印或超出 ASCII 表示范围,均需要对其编码。

6.3 URL 编码方式

URL 的编码方式也比较简单,即使用字符 % 后跟两个十六进制数字(0123456789ABCDEF 或 0123456789abcdef)表示字符码值的单个字节值。比如:

代码语言:javascript复制
https://www.baidu.com/s?wd=春节

因为上面的 URL 参数部分存在 ASCII 无法表示的汉字"春节",因此需要对上面 URL 参数部分进行编码,编码后的 URL 是:

代码语言:javascript复制
https://www.baidu.com/s?wd=春节

其中 0xE698A5 是汉字"春"的 UTF8 码值,0xE88A82 是汉字"节"的 UTF8 码值。

以 Go 为例,可以使用 url.QueryEscape 和 url.QueryUnescape 实现参数的编码和解码。

代码语言:javascript复制
package main

import (
	"net/url"
)
func main() {
	param := "春节"
	fmt.Println(url.QueryEscape(param))
	fmt.Println(url.QueryUnescape(url.QueryEscape(param)))
}

运行输出:

代码语言:javascript复制
春节
春节 <nil>

上面只是对 HTTP URL 的参数进行了编码,如果 URL 中的路径部分存在特殊字符,比如 / 或者 ?,也需要对其进行编码,可以调用 Go 的 url.PathEscape 和 url.PathUnescape 来完成编解码。


参考文献

[1] rfc1738 [2] rfc3986 [3] URL 编码解码在线工具 [4] 查看字符编码(UTF-8) [5] 关于url编码标准的说明

0 人点赞