[翻译] URL 的历史
1982 年 1 月 11 日,22 名计算机科学家在一起讨论「计算机邮件」(也就是今天人们所熟知的电子邮件),于是他们总结出了 RFC805。与会人员有 创建了 Sun Microsystems 的那个家伙、开发了文字冒险游戏 Zork 的那个家伙、发明了 NTP 协议的那个家伙、还有说服政府应该购买 Linux 的那个家伙。他们要解决的问题很简单:ARPANET 上已经接入了 455 个终端,现在快要失控了。
原文标题:The History of the URL
原文作者:Zack Bloom
原文链接:https://blog.cloudflare.com/the-history-of-the-url/
本文由 Sukka 翻译,首发于 Sukka's Blog
ARPANET 即将从它们 原始的 NCP 协议、切换到 TCP/IP 协议(现代互联网的动力)。因为这次切换,将会有更多的互相连接的网络(「互联网」,嗯?),这需要一个更加「分层」的域系统,在该系统中,ARPANET 可以解析其自己的域,而其他网络则可以解析它们的域。
当时除了 ARPANET 以外,还有一些其它网络如 COMSAT、CHAOSMET、UCLNET 和 INTELPOSTNET,由美国各地的大学和公司团体维护,他们希望支付负担得起的费用以互相交流——从电话公司购买 56K 线路和 PDP-11 路由器。
在最初的 ARPANET 设计中,中央网络信息中心(NIC)负责维护列出网络上每个主机的文件,该文件称为 HOSTS.TXT
文件(在 RFC952 中规定),类似于当今 Linux 或 OS X 系统上的 /etc/hosts
文件。 每次网络更改都需要将 NIC 转换为 FTP(FTP 协议 1971 年就被发明出来了)到网络上的每个主机,这给它们的基础设施带来了很大的负担。
当然,互联网上的每个主机只有一个文件、不会是无限制的。但是现在电子邮件是当务之急,于是他们决定创造一个分层系统。使用这个系统时你只需要查询外部系统中所需的一个或几个域,用他们的话说:“当前的 user@host
应该被扩展到 user@host.domain
,其中 domain
是域的层次结构”。域的概念诞生了。
上图是 1980 年十月时、ARPANET 的地图。
重要的是,不要幻想这些决定是出于对域名未来的预见而做出的。实际上他们得出的结论是「对现有系统造成最少困难的解决方案」。例如,他们当时的一个建议是将电子邮件地址的格式设置为 <user>.<host>@<domain>
,如果当天的电子邮件用户名尚未包含 .
字符,那么您今天可能会通过 sukka.skk@moe
向我发送电子邮件。毫无疑问,如今的电子邮件格式并没有包含 .
字符分隔的用户名。
UUCP 和井喷之路
有人说,操作系统的主要功能是为同一对象定义许多不同的名称,以便它可以自己忙于跟踪所有不同名称之间的关系。 网络协议似乎具有相同的特征。
—— David D. Clark,1982 年,RFC814
除了上文所说的 .
,另一个失败的建议涉及用惊叹号(!
)分隔域组件。 例如,要连接到 ARPANET
上的 ISIA
主机,你将连接到 !ARPA!ISIA
;然后,你还可以使用通配符查询主机,因此 !ARPA!*
将返回给你每个 ARPANET
主机。
这种解决方法并不是与标准域名的疯狂分歧,而是对其进行维护的尝试。感叹号分隔的域名系统可以追溯到 1976 年创建 的名为 UUCP 的数据传输工具。如果您是在 OS X 或 Linux 计算机上阅读的本文,则 uucp
可能仍会安装在你的系统中、并且你可以通过终端使用它。
ARPANET 是 1969 年发明的、迅速成为一种强大的通信工具……但是只有少数使用它的大学和政府机构才可以使用。为我们所知的互联网要等到 1991 年(21 年过去了)才能在除研究机构以外的地方公开使用。但是这并不意味着 1991 年以前,公众的计算机之间就不能互相通信。
在互联网时代之前,计算机之间的一般通信方法是使用直接的点对点拨号连接。 例如,如果你想向我发送文件,则你的调制解调器(Modem,猫)将呼叫我的调制解调器,之后我们将能够传输文件。为了将其构建为各种网络,UUCP 就诞生了。
在 UUCP 的系统中,每台计算机都有一个文件,其中列出了其知道的主机,其电话号码以及该主机上的用户名和密码。 然后,您通过主机(每个主机都知道如何连接到下一个主机)来设计从当前计算机到目的地的「路径」:sw-hosts!digital-lobby!sukka
。
该地址不仅可以作为发送文件或直接与计算机连接的方法,还可以作为我的电子邮件地址。 在「邮件服务器」之前的那个时代,如果我的计算机关闭了,谁都不能向我发送电子邮件。
虽然 ARPANET 的使用仅限于一流大学和研究机构的使用,但是 UUCP 为当时的大众创建了一个「互联网」。以 UUCP 为基础的「互联网」为我们带来了 Usenet 和 BBS 的概念。
DNS
我们今天在使用的 DNS 系统,是在 1983 年在 RFC882 中提出来的。如果今天进行 DNS 查询(比如 dig
工具),你可能会看到如下结果:
;; ANSWER SECTION:
blog.skk.moe. 590 IN A 104.18.101.28
blog.skk.moe. 590 IN A 104.18.100.28
这个结果告诉我们,blog.skk.moe
可以通过 IP 地址 104.18.101.28
和 104.18.100.28
访问。你可能已经知道,A
是一种地址(Address
)类型、将域名和一个 IPv4 地址映射起来;而 590
是 Time to Live
,表示在这个映射关系在多少秒内可以确保是合法有效的、之后应该再查询一次。但是,IN
又是什么意思呢?
IN
其实指代的是 Internet
、互联网,这个可以追溯到有几个相互竞争的计算机网络需要互操作的时代。除了 IN
、以外,还有可能是 CH
指代的是 CHAOSNET、或者是 HS
指代的是 Hesiod、雅典娜系统 中的域名解析系统。CHAOSNET 很久之前就消亡了,但是雅典娜系统至今仍然在 MIT 中被广泛使用。你可以在 IANA 的网站上看到如今仍然在使用的 DNS 类型列表,但是毫无疑问的是如今被广泛使用的只有其中一种(IN
)。
TLD 们
其他任何 TLD 不可能再被创建出来。
—— John Postel,1984 年在 RFC1591 中提出。
一旦决定了域名应该分层,那么就必须确定作为根的第一层是什么。这个根就是 .
,所以实际上所有域名都应该以 .
结尾,如 skk.moe.
,而且这个地址也绝对可以在大部分浏览器里使用。虽然由于过于广泛、现在位于末尾的 .
已经被忽略了。
第一个 TLD 是 .arpa
,主要在过渡期间用于对过去的 ARPANET 向后兼容。例如,过去我在 ARPANET 中的主机名被注册为 sukkaw
,那么过渡期间我的地址就会变成 sukkaw.arpa
。这一用法仅在过渡期间内使用,而在过渡期间,网络系统的管理员必须要选择他们该选择哪一个 TLD:com
、gov
、org
、edu
、还是 mil
。
当我们说 DNS 是分层时,是指由一组根 DNS 服务器,这些根 DNS 服务器负责将 google.com
中的 .com
转换为指示对 .com
的名称服务器的查询,而会由 .com
的名称服务器再来告诉你 google.com
该如何访问。互联网的根 DNS 服务器由 13 个服务器集群组成,为什么是 13 个呢?因为一个 UDP 数据包里只能容纳 13 个服务器的地址。从历史上来看,DNS 使用 UDP 数据包,意味着对请求的响应永远不能超过 512 个字节。
; This file holds the information on root name servers needed to
; initialize cache of Internet domain name servers
; (e.g. reference this file in the "cache . "
; configuration file of BIND domain name servers).
;
; This file is made available by InterNIC
; under anonymous FTP as
; file /domain/named.cache
; on server FTP.INTERNIC.NET
; -OR- RS.INTERNIC.NET
;
; last update: March 23, 2016
; related version of root zone: 2016032301
;
; formerly NS.INTERNIC.NET
;
. 3600000 NS A.ROOT-SERVERS.NET.
A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
;
; FORMERLY NS1.ISI.EDU
;
. 3600000 NS B.ROOT-SERVERS.NET.
B.ROOT-SERVERS.NET. 3600000 A 192.228.79.201
B.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:84::b
;
; FORMERLY C.PSI.NET
;
. 3600000 NS C.ROOT-SERVERS.NET.
C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12
C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c
;
; FORMERLY TERP.UMD.EDU
;
. 3600000 NS D.ROOT-SERVERS.NET.
D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13
D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d
;
; FORMERLY NS.NASA.GOV
;
. 3600000 NS E.ROOT-SERVERS.NET.
E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10
;
; FORMERLY NS.ISC.ORG
;
. 3600000 NS F.ROOT-SERVERS.NET.
F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241
F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f
;
; FORMERLY NS.NIC.DDN.MIL
;
. 3600000 NS G.ROOT-SERVERS.NET.
G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4
;
; FORMERLY AOS.ARL.ARMY.MIL
;
. 3600000 NS H.ROOT-SERVERS.NET.
H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53
H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53
;
; FORMERLY NIC.NORDU.NET
;
. 3600000 NS I.ROOT-SERVERS.NET.
I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17
I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53
;
; OPERATED BY VERISIGN, INC.
;
. 3600000 NS J.ROOT-SERVERS.NET.
J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30
J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30
;
; OPERATED BY RIPE NCC
;
. 3600000 NS K.ROOT-SERVERS.NET.
K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129
K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1
;
; OPERATED BY ICANN
;
. 3600000 NS L.ROOT-SERVERS.NET.
L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42
L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:9f::42
;
; OPERATED BY WIDE
;
. 3600000 NS M.ROOT-SERVERS.NET.
M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33
M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35
; End of file
你可以想象 根 DNS 服务器是在一个安全严密的保险箱中运行。在保险箱上有一个时钟以确保监控摄像头没有被循环播放,特别要考虑到等了多少年 DNSSEC 才被规定和实现出来。在此之前对其中一台服务器的攻击可能使得攻击者可以重定向整个互联网上所有用户的通信。当然,这个情节已经可以拍成最精彩的抢劫电影。
译者注:很显然,文章的作者忘记了 2011 年世界上某个人口最多的国家对根 DNS 的投毒影响了荷兰和美国的网民的事件。世界上这个世界上人口最多的国家的政府成功劫持了这个国家的互联网上所有用户的通信,这个剧情还是挺精彩的,对吧?
毫不奇怪,顶级 TLD 的名称服务器实际上并不会经常更改。需要注意的是,根 DNS 服务器收到的请求 98% 其实都是错误的,通常是客户端损坏、或者有的人在玩 dig 114514.yjsnpi
。这类请求是无法被缓存的,所以这些请求都会到达根 DNS 服务器。在早年,这个问题是如此严重,以至于一些 根 DNS 服务器的运营商不得不使用特殊的服务器(译者注:DNS 攻击清洗设备)、希望让这些请求都「走开」。
TLD 名称服务器都在由世界各地的不同公司或政府管理(如 Verisign 管理 .com
)。当你购买 .com
域名时,ICANN 将获得 0.18
美元、而 Verisign 将会获得 7.85
美元,而剩下的钱则是作为域名注册商的收入。
Punycode
在这个世界上,很少有开发人员为新项目想到的愚蠢的名字、结果成为了公开产品的最终名字。也许,我们会因为公司的注册地是特拉华州而把数据库的名称取名为特拉华,但是一般在生产环境上会被命名为 MetadataDataStore
。但是,总会有意外发生的(比如老板度假去了)。
Punycode 是我们用于将 Unicode 编码为域名的系统,它要解决的问题很简单 —— 当整个互联网都在使用 ASCII 字符来拼写域名时,你应该怎么拼出 比萨.com
呢?
如果将域名改用 Unicode 的话,这可就没这么简单了。因为规定域名的原始文档 RFC1035 规定了必须使用 ASCII 编码,而且过去 40 年每一种互联网硬件(比如 Cisco、Juniper)都是基于这个基础设计的。
网络本身 绝对不是纯粹的 ASCII 的,它实际上最初是基于 IDO 8859-1 的、并增加了一个额外的特殊字符(如 ¼
)和特殊标记的字母(如 ä
),唯独不包括任何非拉丁字母。
对 HTML 字符的限制最终于 2007 年被 RFC2070 中被取消,同年 Unicode 成为了网络上最流行的字符集。但是域名仍然只允许使用 ASCII。
实际上,Punycode 并不是第一个解决这个问题的提案。你多半听说过 UTF-8 了,这是一种将 Unicode 编码成 8 个比特的方式(为什么是 8 呢?因为 8 比特构成一个字节)。在 2000 年时,IETF(Internet Engineer Task Force)的人提出了 UTF-5、将 Unicode 编码成 5 个字节的块,这样的话,你的域名将只能用 A - V、0 - 9 这些字符组成。如果我有一个 日本語.com
域名,那么就会被编码为 M5E5M72COA9E.com
(注意、域名只允许使用小写字母、而在编码中的字母是大写字母)。
毫无疑问,这种编码方法糟透了。例如、输出编码中只允许使用 A - V 或者 0 - 9,所以你在域名中包含这些字母时、那就得用其他方式进行编码。而且,域名的长度被限制为不大于 63 个字符,那么缅甸语的域名就不能超过 15 个缅甸文字。其他有趣的提案包括编码为摩尔斯电码等。
制定编码时还有一个问题,你的客户端需要知道这个域名已经被编码了、这样他们才可以在地址栏里显示原始的 Unicode 字符(而不是在地址栏里显示什么 M5E5M72COA9E.com
)。当时还有 几个提案,比如使用 DNS 响应中未使用的一个比特 ——「标头中最后一个未使用的比特」。但是负责 DNS 的那帮家伙似乎「非常犹豫要不要放弃」。
所以另一个建议是使用 ra--
作为编码的前缀标识。在 2000 年四月中旬 的时候,还没有域名以 ra--
作为前缀。那么发生什么了呢?有一个人在这个草案发布后立刻注册了一个以 ra--
为前缀的域名。
2003 年,终于有一个终极解决方案出现了,那就是 RFC3492,制定了一个格式叫做 Punycode,包括一种增量压缩规范、可以大大缩短编码域名的长度。Delta 压缩是一个特别好的主意,因为域名中的所有可能字符都位于 Unicode 的常规区域中。比如说,波斯语中的两个字母之间的距离、比波斯语中的一个字母与印地语中的另一个字母之间的距离、要近得多。还是听不懂?我再举一个例子:
يذؽ
如果以未压缩的格式存储为三个字符 [1610, 1584, 1597]
(基于他们的 Unicode)。为了对其压缩,我们首先对其进行排序得到 [1584, 1597, 1610]
。然后我们存储最低值 1584
、与下一个字符的距离差额 13
、与再下一个字符的距离差额 23
。这样就大大减少了我们要传输和存储的信息量。
然后,Punycode 会使用一种非常有效的方式将这些整数,编码为域名中允许的字符,并在开头 xn--
,以使客户端知道这个域名被 Punycode 编码过。你会注意到,所有的 Unicode 字符都会出现在域名的末尾。它们不仅对它们自己的值进行了编码、它们还对它们所在的位置进行了编码。再举个例子,热狗sales.com
变成了 xn--sales-r65lm0e.com
。每当浏览器的地址栏中输入了基于 Unicode 的域名时,都会以这种方式进行编码。
由于浏览器仍然会展示编码前的域名(意味着这一编码对访问者来说都是透明的),因此这也引入了一个主要的安全问题。各种 Unicode 字符都有可能与现有的 ASCII 字符在印刷时是非常接近的。比如您可能看不出来 а
(西里尔文小写字母 a)和 a
(拉丁文小写字母 a)之间的区别,如果我注册了西里尔字母的 аmazon.com
(xn--mazon-3ve.com
)并且设法诱骗你访问,你很难会发觉的。因此,浏览器对于易被混淆的字母仍然会显示 Punycode 编码。比如,你在访问 🍕💩.ws 时、浏览器仍然会在地址栏中显示 xn--vi8hiv.ws
,不信你就试试。
协议
URL 的第一部分是协议、用于指示如何访问这个 URL。最常见的协议是 http
,这是 蒂姆·博纳斯·李 专门发明的为网络提供动力的协议。这不是唯一的选择,有人认为我们只需要使用一种协议 Gopher
。但是 Gopher
并不是通用的、而是专门终于发送类似于文件树结构的结构化数据。
什么意思?我再举个例子吧。你请求了 /Car
,那么它可能会返回如下结果:
1Chevy Camaro /Archives/cars/cc gopher.cars.com 70
iThe Camero is a classic fake (NULL) 0
iAmerican Muscle car fake (NULL) 0
1Ferrari 451 /Factbook/ferrari/451 gopher.ferrari.net 70
这个返回结果可以用于识别两辆汽车,以及有关这两辆汽车的一些元数据,以及你可以在哪里连接以获取更多信息。 客户端会将这些信息解析为人能看得懂的形式。
第一个流行的协议是于 1971 年发明的 FTP,一种在远程计算机上列出和下载文件的方式。Gopher 是对此的逻辑扩展,不仅提供类似的列表、还提供了一系列条目的元数据,因此 Gopher 不仅可以用于传输文件、还可以用于传输新闻或简单的数据库。但是,Gopher 没有 HTTP 和 HTML 所具有的自由性和简单性。
HTTP 协议是一种非常简单的协议,特别是和 FTP 协议、或者现在开始变得流行起来的 HTTP 第三版协议(HTTP/3)的协议相比。为什么呢?首先,HTTP 完全基于文本而不是二进制数据。蒂姆·博纳斯·李 意识到,使用基于文本的格式将使未来的程序员更容易开发和调试基于 HTTP 的应用程序。
HTTP 也几乎不对你要传输的内容作出任何假设,尽管事实上它设计时为了传输 HTML,但是它并没有限于文本、而是允许你指定类型为任何类型(MIME Content-Type)。HTTP 协议也非常简单:
一个如下的请求:
GET /index.html HTTP/1.1 Host: www.example.com
可能会得到如下结果:
HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>
互联网现在在使用 IP(Internet Protocol)协议、负责从一台计算机到另一台计算机获取一小包数据(大概 1500 字节)。在 IP 之上我们有了 TCP、可以通过许多 IP 数据包可靠地发送整个文件。最重要的是,我们接着实现了 HTTP 或者 FTP 这样的协议,这种协议规定通过 TCP(或者 UDP)传输时应该使用什么格式,使发送的数据易于理解。
换句话说,TCP/IP 将一整堆字节发送到另一台计算机,该协议说明了这些字节应该是什么以及它们的含义。
你也可以发明自己的协议、决定如何汇编你的 TCP 中的字节。唯一的要求是,无论你与谁通话,他必须听得懂。于是我们需要很多常见的标准协议。
当然,如今还有很多那些不那么常见的协议,比如「当日报价」协议(RFC865,运行在端口 17 上)和一个「随机字符」协议(RFC864,运行在端口 19 上)。这些协议在今天看起来可能很愚蠢,但是它们展示了像 HTTP 这样通用的协议的重要性。
端口
Gopher 和 HTTP 的发明先后顺序可以根据它们默认使用的端口号来推断。Gopher 的默认端口是 70,HTTP 的默认端口是 80。HTTP 的默认端口是在 1990 年 和 1992 年 之间应 蒂姆·博纳斯·李 的要求、由 IANA 的 Jon Postel 分配的。
注册端口号的概念,甚至早于互联网的发明。在为 ARPANET 供电的原始 NCP 协议中,远程地址由 40 个比特标识,其中前 32 个标识了远程主机(与今天的 IP 地址类似),后面八个被称为 AEN(Another Eight Number,另外八个数字),这就是端口号的雏形。换句话说,地址决定了消息应该发给哪台计算机、端口号决定了计算机应该由哪个应用程序接收盖信息。
很快,他们就 要求 用户需要注册「连接编号」,避免冲突。在 TCP/IP 协议中,端口号被扩展为 16 个比特(65535)。
虽然协议具有默认端口,但是允许手动指定端口也是很有意义的,这样你可以在本地开发时在同一台计算机上部署多个相同的服务。相同的逻辑是为域名中 www
的基础。当时并没有访问域名的概念、为每个计算机指定一个主机名(比如 wo-de-fu-wu-qi.skk.moe
),那么你在更换服务器时会遇到麻烦。经过一个域名 www.skk.moe
、你可以更改对应到不同服务器。
域名和协议之间的比特
你应该能够注意到在 URL 的协议和域名之间有两个斜杠(//
):
http://skk.moe
这两个斜杠是从 Apollo 计算机系统 中得到的,而这个第一个联网的工作站。Apollo 计算机系统 和 蒂姆·博纳斯·李 遇到的问题是一样的:需要一种将路径与运行该路径的机器分开的方法。他们的解决方案是创建一种特殊的路径格式:
//wo-de-fu-wu-qi/path/to/file
蒂姆·博纳斯·李 于是也采用了两个斜杠。有趣的是,他现在 反悔了 这个决定,希望域名是路径中的一部分,就像这样(以 example.com
为例):
http:com/example/path/to/file
URL 从未像现在这样成为 URL:用户在网络上标识网站的一种不可思议的方式。不幸的是,我们从未能够对 URN 进行标准化,这将为我们提供一个更有用的命名系统。争论当前的 URL 系统就足够了,就像赞扬 DOS 命令行,并指出大多数人应该简单地学习使用命令行语法。我们拥有 Windows 这种 GUI 系统的原因是这会使计算机更易于使用,并且用途更广。同样的想法应该引导我们找到一种在 Web 上定位特定站点的更好的方法。
—— Dale Dougherty,于 1996 年提出。
译者注:我相信,他绝对不会同意二维码、微信小程序码是「更好的方法」。
有许多不同的方法理解「什么是互联网」。一堆计算机使用网络连接在一起,这个概念的互联网于 1969 年 ARPANET 成为了现实。而电子邮件、文件传输、聊天的概念,早在 HTTP、HTML 或者浏览器被发明之前、就已经加入互联网成为互联网的一部分。
到了 1992 年 蒂姆·博纳斯·李 发明了 HTTP、HTML 和浏览器,从而诞生了我们现今所公认的互联网。HTTP、HTML 和 URL,他的目标是让超文本(Hyper Text)进入我们普罗大众的生活。超文本是一种创建互相连接的文档的方式,在那个年代只是一种科幻的概念,而如今你却可以用来承载几乎任何东西。
超文本的概念的关键是从一个文档链接到另一个文档。但是,在 蒂姆·博纳斯·李 的时代、这些文档用多种格式托管、使用 Gopher 或 FTP 协议进行访问。他需要一种一致的方式引用这些需要通过不同协议的文件、以及这个文件存放的主机、以及文件在主机上的位置。
在万维网(World-Wide-Web,WWW)1992 年三月由 蒂姆·博纳斯·李 描述为 UDI(Universal Document Identifier,通用文档标识符)。许多不同的格式 都可以被认为是这种标识符:
protocol: aftp host: xxx.yyy.edu path: /pub/doc/README
PR=aftp; H=xx.yy.edu; PA=/pub/doc/README;
PR:aftp/xx.yy.edu/pub/doc/README
/aftp/xx.yy.edu/pub/doc/README
上述文档中还解释了为什么在 URL 中空格需要被编码(编码为 %20
):
在 UDI 中应该避免使用空格字符,因为空格不是合法字符。因为在电子邮件中经常会引入多余的空白,或者纯粹是为了排版而主动添加的空格。因此,空格需要在传输前编码。
最重要的是,URL 也只是一种表示方案,域名、端口、协议、路径的组合和缩写,以前都只是一种共识。在 1994 年,URL 被 RFC1738 定义为一种规范:
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
这个规范使得从文本中引用不同的系统成为可能,不过现在几乎所有内容都通过 HTTP 协议访问。早在 1996 年 浏览器就会在用户输入不完整的 URL 时自动补全 http://
和 www
。
路径
我不认为问题在于人们能否学习什么是 URL,我只觉得逼迫爷爷奶奶学习 UNIX 的文件系统在道德上是可憎的。
—— Israel del Rio,于 1996 年提出
URL 中的斜杠,对于过去 50 年以来制造的任何计算机的任何用户,都应该非常熟悉。分层文件系统本身由 MULTICS 系统提出。反过来,这个系统的发明者将其归于他 在 1952 年和 阿尔伯特·爱因斯塔 进行了两个小时的交谈。
在 MUCLTICS 系统中文件路径的分隔符是大于号 >
:
>usr>bin>local>awk
看起来合情合理了,不过不幸的是 UNIX 的那帮家伙 决定 大于号 >
应该表示重定向,将路径分隔符委托给斜杠 /
。
对最高法院的一瞥
错。我现在清楚地看到我们之间意见不同。
……
作为一个人,我保留处于不同目的使用不同标准的权利。我希望能够为通用作品、特定的翻译和特定的版本命名。我想要一个比你的提议更加丰富的世界,我不想受到你的「文档」和「其它」两级系统的约束。
—— 蒂姆·博纳斯·李,于 1993 年
美国最高法院的意见中所引用的 URL 中,一半已经不再存在。如果你在 2011 年时阅读一篇撰写于 2001 年的学术论文,那么你一定坚信 URL 从来不会永远有效。
然而 在 1993 年,人们都认为 URL 将会消失,URN(Uniform Resource Name,统一资源名称)才是未来。URN 是一种对内容的永久引用,和 URL 不同 URN 永远有效、指向的内容永远不会消失。蒂姆·博纳斯·李 早在 1991 年就提出了对此的「紧急需求」。
制作 URN 最简单的方法可能是直接使用内容的加密哈希,比如 urn:791f0de3cfffc6ec7a0aacda2b147839
。不过,这种方法不符合网络社区的标准,因为实际上无法弄清楚是谁要求将该哈希值转换回真实内容、也没有考虑到格式的变化(比如在传输时是否经过压缩)。
在 1996 年,Keith Shafer 和其他几个人提出了 URL 打不开的 解决方案。这个链接现在已经打不开了。Roy Fielding 在 1995 年 7 月 提出了一种实现方案,这个链接现在也打不开了。
我可以通过 Google 找到这些页面,而 Google 在功能上已经将这些页面标题设置为当今的 URN。URN 的格式本身于 1997 年制定完成,但是此后从来没有被使用过。实现的本身还是很有趣的,每个 URN 由两个部分组成,一个可以解析成 URN 的权限、以及以任何格式确定的可索引的特定 ID,例如 urn:isbn:1145141919810
可以标识为一本书,形成一个永久链接,你本地的 ISBN 程序也许可以索引这个 URN。
感谢搜索引擎的强大功能,当今最好的 URN 格式可能就是一种简单的将文件指向其以前的 URL 的方法。我们可以允许搜索引擎将这些信息编入索引:
<!-- On http://zack.is/history -->
<link rel="past-url" href="http://zackbloom.com/history.html">
<link rel="past-url" href="http://zack.is/history.html">
Query Params
application/x-www-form-urlencoded
格式在许多方面都是异常的怪诞现象,是多年的意外和折衷结果,导致了互操作性的一系列要求,但是绝不代表这种设计实践是良好的。
—— WHATWG URL 规范
译者注:同一份规范规定了浏览器的地址栏中允许隐藏
www
和https://
主要你接触过 Web,你一定对 Query Parameters 非常熟悉。他们都遵循 URL 的路径规范,并包含了形如 ?name=sukka&state=blog
片段。也许看起来 Query 时使用的分隔符 &
同样也被用于 HTML 编码特殊字符。实际上,你如果使用过 HTML,你总会遇到将 &
编码在 URL 里的时候、将 http://host/?x=1&y=2
变成 http://host/?x=1&y=2
or http://host?x=1&y=2
。这种混淆 一直以来都是存在的。
你应该也能注意到,Cookie 使用了类似但是不同的格式:x=1; y=2
,实际上与 HTML 字符编码完全不冲突。这个想法在 W3C 中丢失,早在 1995 年 时 W3C 就在鼓励人们使用 ;
或 &
。
最初,URL 的这一部分严格用于「索引」。Web 最初就是高能物理学家创建的一种写作方法。这并不是说 蒂姆·博纳斯·李 不知道他的发明是一种通用通信工具。他多年来并没有对表格 提供支持,也许物理学家正好需要一种类似的索引。
不论如何,物理学家们需要一种编码和链接到某一特定信息的方式,以及一种搜索这一信息的方式。为此,蒂姆·博纳斯·李 提出了一个标签 <ISINDEX>
,如果这个标签出现在页面上,浏览器将视为这个页面可以被搜索。浏览器应该显示一个搜索字段、允许用户向服务器发送搜索请求。
这个 搜索请求 被格式化为用 +
分隔的多个关键词:
http://cernvm/FIND/?sgml+cms
很快,这个方法被用于做各种事情,包括 /sqrt/?49
来计算 49 的平方根。很快有人提出了通用的 <input>
的标签:
http://somehost.somewhere/some/path?x=xxxx+y=yyyy+z=zzzz
但是这并不能让所有人都满意。有的人认为 链接另一侧的内容应该是可搜索的:
<a HREF="wais://quake.think.com/INFO" INDEX=1>search</a>
回顾过去,我可以说我很高兴,更通用的解决方案最终胜出了。
<INPUT>
标签的实际工作 始于 1993 年 1 月,基于较旧的 SGML 类型。(不幸的是)他们还决定 <SELECT>
输入需要单独的,更丰富的结构:
<select name=FIELDNAME type=CHOICETYPE [value=VALUE] [help=HELPUDI]>
<choice>item 1
<choice>item 2
<choice>item 3
</select>
你可能会好奇为什么现在没有复用 <li>
标签、而是发明了 <option>
标签呢,毫无疑问 是考虑了的。当时还有其他草案,其中有一个 包括一些作为替代的变形形式。比如我们想象以下 Angular 今天可能会是以下形式:
<ENTRYBLANK TYPE=int LENGTH=length DEFAULT=default VAR=lval>Prompt</ENTRYBLANK>
<QUESTION TYPE=float DEFAULT=default VAR=lval>Prompt</QUESTION>
<CHOICE DEFAULT=default VAR=lval>
<ALTERNATIVE VAL=value1>Prompt1 ...
<ALTERNATIVE VAL=valuen>Promptn
</CHOICE>
在上面这个例子中,TYPE
属性指定了输入的类型检查,并且 VAR
值可以 被 URL 中的字符串替换,比如这样:
http://skk.moe/apps/$appId
还有一个 提案 甚至决定使用 @
,用于代替 =
分隔 Query 的组件:
name@value+name@(value&value)
最后,是 Marc Andreessen 根据他已经在 Mosaic 系统中的实现、提出了 我们当前使用的方法:
name=value&name=value&name=value
在这个提案两个月以后,Mosaic 系统对 method=POST
这种形式提供了支持。现代的 HTML 诞生了。
当然,也是 Marc Andreessen 的公司网景(Netscape)发明了了 Cookie 的格式,并且使用 ;
而不是 &
作为分隔符。非常可惜,他们的一些建议目光短浅得令人痛苦,比如 Set-Cookie2
响应头,导致了一些至今还令人头疼的结构化问题。
Fragments & Hash
URL 中 #
之后的部分称为 Fragment。自 URL 的草案提出以来,Fragment 就是 URL 的一部分,用于链接到正在加载的页面上的特定位置。 例如,如果我的站点上有一个锚点:
<a name="foxtail"></a>
我就可以用 #
链接到它:
https://skk.moe/#foxtail
这个概念逐渐扩展、并且至今已经对所有标签都生效,并且使用了 id
属性、不再依赖 name
属性。
<h1 id="bio">Bio</h1>
蒂姆·博纳斯·李 决定使用美国人的地址中的分割符(也就是 #
),虽然他是一个英国人。他是 这么解释的:
在美国的邮件地址中,通常将数字符号用于建筑内的公寓编号或者房间号,因此 12 Acacia Av #12 就表示建筑物位于 12 Acacia 大街、房间号是 12。因此 # 符号简直是显而易见的选择了。
http://www.example.com/foo#bar
的意思就是在http://www.example.com/foo
中,它所在的位置是bar
。
结果呢?Douglas Englebart 创立的 原始的超文本系统 也因为同一个目的使用了同一个字符,真是一个巧合。
Fragment 不会包含在 HTTP 中,它永远只存在于浏览器中。当需要实现客户端导航时(在 pushState
被引入之前)这显然非常合适。一些需要将状态存储在 URL 里、不发送到服务器时也很有价值。这是什么意思呢?让我们接着看:
丘陵和山峰
关于电子数据交换 (sic),有一个完整的标准,如 SGML,它表示表单和表单的提交。我只知道这个看起来像个空格。
—— 蒂姆·博纳斯·李,于 1993 年 提出。
人们普遍认为,2002 年 HTTP/1.1 和 HTML 4.01 的最终确定、到 HTML5 真正步入正轨之间这一段时间,互联网标准组织(IETF)并没有做什么有意义的事情。我把这个时代称为「XHTML 的黑暗时代」。事实是,标准化人员在此期间就像勤劳的小蜜蜂一样忙碌,只是他们的工作并没有被证明具有存在的价值。
语义化网络就是其中之一。这个梦想是创建一个资源描述框架,为所有内容的元数据提供一个通用的表示方式。比如,与其创建一个关于「Corvette Stingray」(译者注:一种豪华跑车的名字)的网页,不如制作一种资源描述文档、描述它的颜色、大小尺寸、以及我在驾驶它时会获得多少超速的罚单。
这的确是一个好主意。但是这个格式是基于 XML 的,而且引出了一个先有鸡还是先有蛋的问题:是先有这种格式的文档、还是先有浏览器。这种格式甚至为哲学讨论提供了强大的论证,其中最好的论据之一持续了十年,并且以精湛的代号 httpRange-14
而闻名。
httpRange-14
是要解答关于 URL 的一个基本问题:URL 必须指向一个页面,还是可以指向任何东西?我们能不能为我家的汽车弄一个 URL?他们没有试图令人满意的方式回答这个问题。相反的,他们专注于如何、和何时使用 303 重定向的方式将用户从 URL 跳转到实际链接,以及何时使用 #
将用户指向链接中的具体内容。
在现在实用主义至上的时代,这个问题看起来很愚蠢,我们现在想什么时候使用 URL 就怎么用。但是在语义化网络的框架中,他们只关心「语义」。在 2002 年 7 月 1 日,2002 年 7 月 15 日,2002 年 7 月 22 日,2002 年 7 月 29 日,2002 年 9 月 16 日,以及 2005 年以前进行的其它 20 次讨论,都围绕这个主题展开。2005 年 httpRange-14
解决了这一问题。但是 2007 年 和 2011 年 因为申诉,讨论重新开放,2012 年时 呼吁采取新的解决方案。这个问题由一个非常恰当地命名的「学究网络小组」负责讨论。故事的结局呢?URL 从来都不是语义化的。
认证
你可能已经知道了,在 URL 里可以包括 用户名 或者 密码:
https://sukkaw:foxtail@skk.moe
浏览器也可以将这部分数据编码成 Base64 格式、并通过 Authentication
请求头发送(译者注:HTTP Basic Auth)。
使用 Base64 编码的唯一原因是允许在请求头中包括可能无效的字符,Base64 不会加密用户名和密码。在没有 SSL 的年代,这存在一个非常严重的问题:任何中间人都可以知道用户名和密码。因此,当时提出了 很多提案,其中最著名的是 Kerberos
协议,不论是在当时还是现在都被广为使用。
但是和之前提过的例子一样,HTTP Basic Auth 对于当时唯一的浏览器(Mosaic 系统)而言最容易实现,这使其成为第一个、也是最终唯一的解决方案,直到开发者为自己专门构建了身份验证系统的工具。
Web 应用
超链接的基础是 Web 可能有些奇怪。超链接是一种将一个文档链接到另一个文档的方法,逐渐地得到完善:样式、代码执行、会话、身份验证,最终现在成为了社会共享体验。在那个年代,有很多类似(但是失败的创意)。这个道理也沿用至今。不论什么工具、哪怕多么简陋和不完善,只要有人使用,它最终就会不断完善。反之,即使技术上非常完善,如果没有人用,他们也会成为历史。