RFC3986
(→路径片段常规化) |
(→基于语法的常规化) |
||
第821行: | 第821行: | ||
百分号编码机制([[RFC3986#百分号|2.1]])在其他相同的URIs中是一个频繁出现的来源. 除了上面注意到的大写常规化问题, 一些百分号编码八位字节的URI生产者不必需百分号编码, 结果是那些URIs等价于它们的未编码部分. 这些URIs应该被解码任何百分号编码来常规化以对应一个非保留的字符, 如[[RFC3986#非保留字符|2.3]]所述. | 百分号编码机制([[RFC3986#百分号|2.1]])在其他相同的URIs中是一个频繁出现的来源. 除了上面注意到的大写常规化问题, 一些百分号编码八位字节的URI生产者不必需百分号编码, 结果是那些URIs等价于它们的未编码部分. 这些URIs应该被解码任何百分号编码来常规化以对应一个非保留的字符, 如[[RFC3986#非保留字符|2.3]]所述. | ||
− | |||
=====路径片段常规化===== | =====路径片段常规化===== |
2013年10月31日 (四) 03:10的版本
本文的英文原文来自Uniform Resource Identifier (URI): Generic Syntax
被更新: 6874 | 标准 |
存在勘误表 | |
网络工作组 | T. Berners-Lee |
申请讨论: 3986 | W3C/MIT |
STD: 66 | R. Fielding |
更新: 1738 | Day Software |
取代: 2732 2396 1808 | L. Masinter |
类别: 标准跟踪 | Adobe Systems |
2005年1月 |
- 统一资源标识符(URI):通用语法
本文的状态
- 本文为互联网社区指定了一个互联网标准跟踪协议, 并为改进它而请求讨论和建议. 关于标准化状态和本协议的状态请参考当前版本的 "互联网官方协议标准" (STD 1). 本文不受限制分发.
版权声明
- Copyright (C) The Internet Society (2005).
摘要
- 一个统一资源标识符(URI)是一个字符的紧凑序列,用来标识一个抽象或物理资源. 本协议定义了通用的URI语法和一个解析相应格式URI引用的过程, 以及在互联网上使用URIs的指南和安全事项. URI语法定义了所有合法的URIs的一个超集的语法, 允许一个实现来解析一个URI引用的常用部件而不需要知道每个可能的标识符的scheme特有的需求. 本协议不为URIs定义现成的语法; 那个任务由每个URI scheme的个体的协议来执行.
目录 |
绪论
一个统一资源标识符(URI)为识别一个资源而提供一个简单的可扩展含义. 本协议的URI语法和语义衍生于万维网全局信息倡议所引入的概念, 这些标识符的使用从1990年就开始了并且在 "WWW中的统一资源标识符" RFC1630 中描述了它们. 语法的设计满足了 "互联网资源定位器功能建议" RFC1736 和 "统一资源名称功能需求" RFC1737 提出的建议.
本文取代了 RFC2396, 它融合了 "统一资源定位器" RFC1738 和 "相对统一资源定位器" RFC1808 以为所有URIs定义一个单一, 通用的语法. 它取代了 RFC2732, 该协议引入了用于IPv6地址的语法. 它排除了RFC 1738中定义了个别URI schemes的特有语法的部分; 那些部分将被更新到独立的文档中. 新URI schemes的注册过程被独立地定义在 BCP35. 给新URI schemes设计者的建议可以在 RFC2718 找到. 所有从 RFC 2396 而来的显著变更都备注在 附录D. RFC 2396以来的变更
本协议遵循 BCP19 提供的建议使用术语 "character" 和 "coded character set" , 并且以 "character encoding" 来取代 BCP19 中所指的 "charset".
URIs概述
URIs的特征如下:
统一
- 统一性提供了很多好处. 它允许不同类型的资源标识符被用在相同的上下文中, 即使当访问那些资源使用的机制不同的时候. 它允许跨越资源标识符的不同类型的常用语法习惯拥有统一的语义解释. 它允许引入新类型的资源标识符而不妨碍现有标识符的使用. 他允许标识符被重用于不同的上下文, 从而允许新的应用或协议支持一个早已存在的, 大的, 和广泛的资源标识符的集合.
资源
- 本协议不限制能成为一个资源的范围; 相反, 术语 "resource" 被用于一般意义上的任何可以由URI标识的东西. 熟悉的例子包括一个电子文档, 一个图片, 一个包含一致的目的的信息源(例如, "洛杉矶今日天气"), 一个服务(例如, 一个 HTTP 到 短信 的网关), 以及一个其他资源的集合. 通过互联网来访问一个资源不是必需的; 例如, 自然人, 组织, 以及在一个图书馆内的限定的书也可能成为资源. 同样的, 抽象概念能成为资源, 类似一个数学方程式的运算符和操作数, 关系的类型(例如, "父母" 或 "雇员"), 或数值(例如, 0, 1, 以及 无穷大).
- 标识符
- 一个标识符包含如何在身份识别的范围中从其他事物区分出被标识的事物的信息. 我们使用术语 "identify" 和 "identifying" (识别)来指代从其他资源区分出一个资源的行动, 不管那个行为是如何完成的(例如, 通过名称, 地址, 或上下文). 这些术语不应错误地假定一个标识符定义或包含的身份所指的事物, 尽管对某些标识符来会发生这种情况. 不应假定一个使用URIs的系统将成功访问所标识的资源: 在很多情况下, URIs被用于指示资源而没有让任何它们被访问的意图. 同样的, "一个" 资源可能并不自然地标识单数(例如, 一个资源可以是一个被命名的组合或一个随时间变化的映射).
一个URI是一个标识符,包含了一系列字符,匹配第三章中<URI>命名的语法规则. 它使得通过独立定义命名的Scheme3.1的可扩展组合来表达资源的统一身份成为可能. 那个身份如何被实现, 被指定, 或被允许取决于每个scheme协议.
本协议没有对一个资源的性质做任何限制, 原因是一个应用可能专注于一个资源, 其他各种系统可能把URIs用来标识多个资源. 本协议不要求一个URI随着时间变化而坚持标识相同资源, 尽管那是所有URI schemes的一个常见目标. 不过, 本协议没有任何东西阻止一个应用把自己限制到特有的资源类型, 或由那个应用维护的具有其期望特性的URIs的一个子集.
URIs有一个全局范围并且对它的一致性解释和上下文无关, 即使那个解释的结果可能和最终用户的上下文产生关系. 例如, "http://localhost/" 对于涉及的每个用户有相同的解释, 即使相当于 "localhost" 的网络接口对每个最终用户可以是不同的: 解释独立于访问. 无论如何, 在那个引用的基础上所做的一个动作将和该最终用户的上下文发生关系, 这暗示一个旨在涉及一个全局唯一的事物的动作必须使用URI来把那个资源从所有其他事物中区分出来. 识别该最终用户的本地上下文的URIs只应该在该上下文本身是该资源的定义的一部分的时候使用, 类似当一个在线帮助手册指向一个位于该最终用户的文件系统上的一个文件(例如, "file:///etc/hosts").
通用语法
每个URI开始于一个scheme名, 如3.1所定义的, 那代表一个该scheme赋予该标识符的协议. 就其本身而言, URI语法是他一个联合的可扩展的命名系统,每个scheme的协议可以进一步限制使用那个scheme的标识符的语法和语义.
本协议定义了那些所有URI schemes或很多常用的URI schemes需要的URI语法的元素. 为此它定义了需要的语法和语义来实现一个独立于scheme的URI参数解析机制, 由此对一个URI的独立于scheme的处理可以延期到需要该独立于scheme的语义的时候. 同样, 用于URI参数的协议和数据格式可以参考本协议,作为所有URIs都允许的语法的范围的定义, 包括那些已经被定义的schemes. 这把身份schemes的演变从协议,数据格式,和使用URIs的实现的演变中隔离出来.
一个通用URI语法的解析器可以把任何URI参数解析成它的主要部件. 一旦该scheme被确认, 更多scheme特有的解析可以在部件上被执行. 换句话说, URI通用语法是所有URI schemes语法的子集.
示例
以下示例URIs展示了一些它们的常用语法部件中的URI schemes和变量:
- ftp://ftp.is.co.za/rfc/rfc1808.txt
- http://www.ietf.org/rfc/rfc2396.txt
- ldap://[2001:db8::7]/c=GB?objectClass?one
- mailto:John.Doe@example.com
- news:comp.infosystems.www.servers.unix
- tel:+1-816-555-1212
- telnet://192.0.2.16:80/
- urn:oasis:names:specification:docbook:dtd:xml:4.1.2
URI, URL, 和URN
一个URI可以被进一步归类为一个定位器, 一个名字, 或两者都是. 术语统一资源定位器 "Uniform Resource Locator" (URL) 代表URIs的那个子集, 除了识别一个资源, 通过描述它的主要访问机制(例如, 它的网络位置 "location")来提供一个该定位资源的含义. 术语统一资源名称 "Uniform Resource Name" (URN) 历史上曾被同时用来代表在 "urn" scheme RFC2141 下的URIs(即使当资源终止存在或成为不可用的时候要想仍然保持全局唯一和持久化就需要它)和任何其他带有名称属性的URI.
一个独立的scheme不一定要被分类成名称 "name" 或定位器 "locator" 之一. 来自任何给定scheme的URIs的实例可以有名称或定位器的性质或两者都是, 经常取决于命名授权机构分配标识符的持久行和意愿, 而不是该scheme的任何能力. 未来的西医和相关文档应该使用通用术语 "URI" 而不是更加受限制的术语 "URL" 和 "URN" RFC3305.
设计事项
转录
URI语法已经被设计成把全局转录作为其主要因素. 一个URI是一系列来自有限集合中的字符: 基本拉丁字母表中的字母,数字,以及一些特殊字符. 一个URI可以被以各种方式展现; 例如, 纸上的墨水, 屏幕上的像素, 或一系列八进制编码的字符. 一个URI的解释只依赖于使用的字符而不是那些字符如何在一个网络协议中展现.
转录的目标可以由一个简单长今来描述. 想想两个同事, Sam 和 Kim, 坐在一个国际化会议的酒店并交换研究思路. Sam向Kim请求一个定位器以获得更多信息, 所以Kim在一个餐巾纸上写下了该研究网站的URI. 然后返回到家里, Sam拿出餐巾纸并把该URI输入到一台电脑中, 然后他获取Kim提到的信息.
这个场景揭示了很多设计因素:
- o 一个URI是一系列字符,不总是以一个八进制序列展现的.
- o 一个URI可以从一个非网络的来源转录并且因此应该包含适合输入到计算机的字符, 同时受到键盘(以及相关的输入设备)从语言到时区的限制的约束.
- o 一个URI经常不得不被人们记住, 并且当它包含有意义的或熟悉的部件的时候是易于被人们记住的.
这些设计因素不总是一致的. 例如, 经常会出现这种情况,对于一个URI部件的最有意义的名称将需要一些无法输入到某些系统的字符. 从一个介质转录一个资源标识符到另一个介质的能力被认为比让URI包含最有意义的组件更重要.
在时区或地区上下文中随着技术的提升, 用户可以从能使用更大范围的字符的能力中受益; 这类用法没有在本协议中定义. 百分号编码八位字节(2.1)可被用于一个URI以展示超出US-ASCII编码字符集范围的字符,如果这一展示被该URI引用的scheme或协议元素允许的话. 这样一个定义应该指定使用的字符编码以在为该URI执行百分号编码之前把那些字符映射到八位字节.
从交互分离身份
对URIs的一个常见误解是它们只用于代表可访问的资源. URI本身只提供身份识别; 一个URI的出现既不保证也不暗示能访问资源. 反之, 和一个URI参数关联的任何操作是由协议元素,数据格式属性,或它所显现的自然语言文本所定义的.
给定一个URI, 一个系统可以尝试对该资源执行各种操作, 这可以被描述为 "access" (访问), "update" (更新), "replace" (替换), 或 "find attributes" (寻找属性). 这类操作由那个使用URIs的协议定义, 不由本协议定义. 无论如何, 为了描述在URIs上的常用操作,我们确实会使用一些通用术语. URI "resolution" 是确定一个访问机制和适当的参数以解参考一个URI的过程; 这一解读可能需要多次迭代. 要使用那个访问机制以在该URI的资源上执行一个操作,就要 "dereference" (解参考)该URI.
当URIs被用于信息获取系统以识别信息来源, 最常用的URI格式解参考是 "retrieval" (获取): 使用一个URI是为了获取一个和它相关的资源的展现. 一个 "representation" (展现)是一系列八位字节, 同时带有展现元数据以描述那些八位字节, 那指定了但展现被生成的时候的那一刻该资源的状态的一个记录. 获取是由一个过程来实现的,可能包括使用一个URI作为一个缓存键来检查一个本地缓存的展现, 为了应用一个获取的操作, 对该URI的解读决定一个适当的访问机制(如果有的话), 以及解参考该URI. 执行获取取决于协议, 可能提供额外的关于该资源的信息(资源元数据)以及它和其他资源的关系.
信息获取系统中的URI参数被设计成延迟绑定: 一个访问的结果通常在它被访问的时候才决定,并且可能随着时间的过去而不同或取决于交互的其他方面. 这些参数的建立是为了在将来使用: 正在被识别的事物并不是一些过去获得的特定结果, 而不是一些用于将来结果的并被期望为真的特征. 在这种情况下, 由该URI代表的资源实际上是一个随时间消逝而被观察到的特征的同一个东西, 可能由额外的资源提供者做出的注释或声明来解释.
尽管很多URI schemes在协议之后被命名, 这不表示使用这些URIs将导致通过命名的协议来访问该资源. URIs经常被简单的用于身份识别的目的. 甚至当一个URI被用于获取一个资源的一个展现, 那个访问可能是通过网关, 代理, 缓存, 以及独立于该scheme名称相关的协议的名称解析服务. 一些URIs的解析可能需要使用多个协议(例如, DNS和HTTP典型的都被用于访问一个 "http" URI的原始服务器,当未在本地缓存中发现一个展现的时候.
层次标识符
URI语法被组织成多层次的, 列出的部件从左到右重要性逐次降低. 对于某些URI schemes, 可见的层次受限于该scheme本身: 在scheme部件分隔符(":")之后的每个东西被认为对URI处理是不透明的. 其他URI schemes让层次对通用解析算法明确和可见.
通用语法使用斜杠("/"), 问号("?"), 以及数字符号("#") 字符以分隔那些对于通用解析器对一个标识符的分层次解释很重要的部件. 除了通过熟悉的语法的一致性使用有助于这类标识符的可读性, 跨命名scheme的层次统一展示允许独立于scheme的参数被关联到那个层次.
常有的情况是一个文档组或树("tree")被构造出来服务于某个常见的目的, 其中在这些文档中的绝大多数URI参数指向的资源有树在里面而不是在它外面. 类似的, 位于一个特定网站的文档更加类似于代表那个网站的其他资源而不是位于远端网站的资源. URIs的相关参考允许文档树被部分独立于它们的位置和访问scheme. 例如, 单独一组超文本有可能被同时访问和遍历, 通过每个 "file", "http", 和 "ftp" schemes,如果这些文档以相对引用互相参考的话. 而且, 这类文档树可以被移动, 作为一个整体, 不改变任何相对引用.
一个代表一个资源的相对引用(4.2),就是描述一个层次名字空间中该参数上下文和该目标URI之间的不同. 该参数解析算法, 在第5章介绍, 定义了一个参数被转化多少到目标URI. 因为相对引用只能被用于一个层次化URI的上下文, 新URI schemes的设计者应该使用一个和通用语法的层次化部件一致的语法,除非有强迫的原因要禁止对那个scheme做相对引用.
- 注意: 之前的协议使用术语局部URI( "partial URI" )和相对URI ( "relative URI" )以表示一个对URI的相对引用. 因为一些读者误解那些术语,以为它意味着相对URIs是URIs的一个子集而不是一个引用URIs的方法, 本协议简单地以相对引用来代表它们.
所有URI参数使用的时候由通用语法解析器解析. 然而, 因为层次处理不影响在一个参数中的绝对URI,除非它包含一个或多个点段(完整的 "." 或 ".." 路径段, 如3.3所述), URI scheme协议可以定义不透明的标识符,通过不允许使用斜杠字符, 问号字符, 和URIs "scheme:." 以及 "scheme:..".
语法符号
本协议使用RFC2234的增强巴科斯范式(ABNF)符号, 包括由以下协议定义的核心ABNF语法规则: ALPHA (字母), CR (回车), DIGIT (小数位数), DQUOTE (双引号), HEXDIG (十六进制数字), LF (换行), 和 SP (空格). 完整的URI语法收集在附录A.
字符
URI语法提供了一个编码数据的方法, 想必为了识别一个资源的缘故, 如同一系列字符. URI字符顺序和经常性地被编码成八位字节用于传输或展示. 本协议不强制任何特定的在URI字符和用于存储或传输那些字符的八位字节之间的字符编码映射表. 当一个URI出现在一个协议元素中, 字符编码由协议定义; 没有这里定义, 则一个URI被假定其字符编码和周围的文本相同.
ABNF字符定义它的终端值为非负整数(码点)基于US-ASCII编码字符集ASCII. 因为一个URI是一系列字符, 我们必须倒置关系以理解该URI语法. 所以, ABNF使用的整数值必须通过US-ASCII映射回它们的相应字符以完成该语法规则.
一个URI由一个包含数字,字母,和一些图形符号的有限集合的字符组成. 一个那些字符的保留子集可能被用于限定一个URI中的语法部件使用保留字符, 同时包括未保留集合和那些不作为分隔符的字符, 定义每个部件的识别数据.
百分号编码
一个百分号编码机制被用于在一个部件里展示一个八位字节码数据,当那个八位字节码相应的字符超出了允许的集合或被用作分隔符,或在部件之内的时候. 一个百分号编码的八位字节码被编码成一个三重字符, 包括百分号字符 "%" 和随后的两个十六进制数字展示那个八位字节的数值. 例如, "%20" 是二进制字节 "00100000" (ABNF: %x20)的百分号编码, 它在US-ASCII中对应空格字符(SP). 2.4描述了何时应用百分号编码和解码.
pct-encoded = "%" HEXDIG HEXDIG
大写的十六进制数字 'A' 到 'F' 分别等价于小写的数字 'a' 到 'f' . 如果两个URIs在百分号编码字节中使用的十六进制数字的大小写不同, 它们是等价的. 为了了一致性, URI制作造和正规化者应该对所有百分号编码使用大写十六进制数字.
保留字符
包含部件和子部件的的URIs由 "保留" 集合中的字符来分隔. 这些字符被成为保留( "reserved" )是因为它们可能(也可能不)被一个URI的解参考算法的通用语法,每个scheme特有的语法,或实现特有的语法定义成分隔符. 如果用于一个URI的数据和一个保留的作为分隔符的字符发生冲突, 那么该冲突的数据必须在该URI被格式化之前进行百分号编码.
reserved = gen-delims / sub-delims gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
保留字符的目的是提供一组分隔符号来把一个URI从其他数据中区分开. URIs把保留字符替换成相应的百分号编码字节之后就就和原来的URIs不等价了. 百分号编码一个保留字符, 或解码一个对应某个保留字符的百分号编码字节, 将改变该URI如何被大部分应用理解. 因此, 在保留集合中的字符被从标准化中保护并且因而可以安全地用于scheme特有和制作者特有的算法以从一个URI分隔出数据子部件.
一个保留字符的子集(gen-delims)被用作通用URI部件的分割符,如第3章所述. 一个部件的ABNF语法规则将不直接使用保留的或gen-delims规则名; 反之, 每个语法规则列出那个部件允许的字符(即, 不分隔它), 并且那些也在保留集合中的任何字符都会是保留的 "reserved" ,被用作该部件中的子部件分隔符. 只有大多数常用子部件由本协议定义; 其他子部件可以由一个URI scheme的协议, 或由一个URI的解参考算法的实现特有的语法定义, 这样这类子部件就由那个部件中允许的保留集合中字符来分隔了.
制作URI的应用应该对在保留集合中的字符的数据字节做百分号编码以在那个组件展示数据,除非这些字符被URI scheme特别允许. 如果在一个URI部件中发现一个保留字符并且那个字符没有已知的分隔规则, 那么它必须被解释成展示相对应的那个字符的US-ASCII编码的数据字节.
非保留字符
被允许出现在一个URI中的字符但是没有被保留用途,被成为非保留(unreserved). 这包含大写和小写字母, 小数数字, 连字符, 句号, 下划线, 以及波浪号.
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
URIs中的一个非保留字符被替换成相应的百分号编码的US-ASCII字节之后和原来是等价的: 它们标识相同的资源. 无论如何, URI比较的实现不总是在比较之前执行标准化(见第6章). 为了保持一致性, 在范围 ALPHA (%41-%5A 和 %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E), underscore (%5F), 或 tilde (%7E) 中的百分号编码不应该被URI制作者创建,并且当它们被在一个URI中发现了, 应该由URI标准化把它们解码成相应的非保留字符.
何时编码或解码
在正常情况下, 只有在从部件部分生成URI的过程中一个URI内的字节才有机会被百分号编码. 正是在这个时候一个实现会决定哪个保留字符被用作子部件分隔符以及哪个能被安全地用作数据. 一旦生成了, 一个URI总是保持它的百分号格式.
当一个URI被解参考的时候, 对scheme特有的解参考过程(如果有的话)重要的部件和子部件在那些部件中的百分号编码字节能被解码之前必须被解析和分离, 因为否则数据会和部件分隔符搞错. 唯一的例外是百分号编码的字节相应的字符在非保留集合中, 它能在任何时候被解码. 例如, 在旧的URI处理实现中, 对应于波浪号("~")字符的字节经常被编码成 "%7E" ; "%7E" 可以被 "~" 替代而不需要改变它的解释.
因为百分号("%")字符用作百分号编码字节的指针, 它必须被百分号编码成 "%25" 才能把那个字节用作一个URI的数据. 实现不能多次百分号编码或解码相同的字符串, 因为解码一个现有的已解码字符串可能导致把一个百分号数据字节错误地解释成一个百分号编码的开始, 反之亦然,百分号编码一个已做百分号编码的字符串.
识别数据
URI字符为该URI部件的每一部分提供识别数据, 作用看起来像一个用于系统之间识别身份的外部接口. 尽管URI制作接口的出现和类型被使用它的URIs的客户端隐藏了(并且因而超出了本协议定义的互操作性需求的范围), 它经常是对URI字符发表的解释出现混淆和错误的来源. 实现不得不注意到有多重字符编码被卷入到URIs的制作和传输中: 本地名称和数据编码, 公共接口编码, URI字符编码, 数据格式编码, 以及协议编码.
本地名称, 类似文件系统名称, 被以一个本地字符编码存储. URI制作应用(例如, 起源服务器)典型地将使用本地编码作为基础用于制作有意义的名称. 该URI制作者将把本地编码转换成一个适合公共接口的东西,接着把该公共接口编码转换成URI字符的受限集合(保留, 非保留, 以及百分号编码). 那些字符顺序地被编码成八位字节以用于带有一个数据格式的参数(例如, 一个文档字符集), 并且这类数据格式经常随后被编码以在互联网协议上传输.
对大多数系统, 出现在一个URI部件中的非保留字符被视为展示对应于那个字符的US-ASCII编码的数据字节. URIs的消费者假定字符 "X" 对应于字节 "01011000", 并且甚至当那个假定不正确时, 这么对待它也没有害处. 一个内部提供不同的字符编码的标识符的系统, 类似 EBCDIC, 通常将在一个内部接口对原文的标识符执行字符翻译成UTF-8STD63 (或一些其他的US-ASCII字符编码的超集) , 从而提供更有意义的标识符而不是那些从简单地对原始字节做百分号编码而来的结果.
例如, 考虑一个提供数据的信息服务, 在本地使用基于EBCDIC的文件系统存储, 客户端则在互联网上通过一个HTTP服务器访问它. 当一个作者在那个文件系统上创建一个名为 "Laguna Beach" 的文件, "http" URI对应的那个资源被期望包含有意义的字符串 "Laguna%20Beach". 如果, 无论如何, 那个服务器使用一个过于简单的纯字节映射来制作URIs, 那么该结果将是一个包含 "%D3%81%87%A4%95%81@%C2%85%81%83%88" 的URI. 一个内部转码接口通过在制作该UIR之前把本地名称转码成一个US-ASCII的超集修复了这个问题. 自然的, 一个在这样一个接口上的入站URI的正确解释需要在该反向转换被应用之前把那个百分号编码字节解码(例如, 把 "%20" 解码成 SP)以获得本地名称.
在某些情况下, 在一个URI部件和识别数据之间的已经被手工制作的内部接口展现得还不如一个字符编码翻译直接. 例如, 一个URI的一部分可能反映一个对非ASCII数据的查询, 或一个地图上的数字坐标. 同样的, 一个URI scheme可能定义部件时要求在格式化该部件并制作该URI之前进行编码.
当一个新的URI scheme定义一个展示包含来自统一字符集UCS的字符的原文数据的部件, 该数据应该首先根据UTF-8字符编码STD63被编码成字节; 然后只有那些在非保留集合中没有对应字符的字节应该被百分号编码. 例如, 字符 A 将被展示成 "A", 字符 LATIN CAPITAL LETTER A WITH GRAVE 将被展示成 "%C3%80", 而字母 KATAKANA LETTER A 将被展示成 "%E3%82%A2".
语法部件
通用URI语法包括一系列分层的由 scheme, authority, path, query, 和 fragment 参考的部件.
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] hier-part = "//" authority path-abempty / path-absolute / path-rootless / path-empty
部件的scheme和path部件是必需的, 尽管path可能是空的(无字符). 当authority是现成的, path必须要么是空的要么是以一个斜杠("/")开始的字符串. 当authority不是当前的, path不能以两个斜杠("//")开始. 这些约束导致5个用于path的不同ABNF规则(3.3), 它们中只有一个将匹配任何给定的URI参数.
以下是两个示例URIs以及它们的部件部分:
foo://example.com:8042/over/there?name=ferret#nose \_/ \______________/\_________/ \_________/ \__/ | | | | | scheme authority path query fragment | _____________________|__ / \ / \ urn:example:animal:ferret:nose
格式
每个URI以一个格式名称开始,它代表分配给带那个格式的标识符的一个协议. 因此, URI语法是一个联合的和可扩展的命名系统,这里每个格式的协议可以进一步约束使用那个格式的标识符的语法和语义.
格式名称包含一系列以一个字母开始的并跟随任意字母,数字,加号("+"), 句号("."), 或连字符("-")的组合. 尽管格式是大小写敏感的, 权威的格式是小写的且那些定义格式的文档必须以小写字符来定义. 为了鲁棒性的目的,一个实现应该接受在格式名称中大写字符等价于小写字符(例如, 除了 "http" 也允许 "HTTP" ) ,但是为了一致性,应该只制作小写格式名称.
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
本文没有定义独立的格式. 注册一个新URI格式的过程被独立地定义于BCP35. 格式注册表维护了格式名称和它们的协议之间的对应关系. 给新URI格式设计者的建议可以在RFC2718找到. URI格式协议必须定义它们自己的语法,这样所有和它们的格式特有的语法匹配的字符串也将和<absolute-URI>语法匹配, 如4.3所述.
当一个URI发生了违反一个或多个格式特有的约束的情况, 该格式特有的解析过程应该把该参数标记为一个错误而不是忽略未使用的部分; 这样做减少了相当的URIs的数量并有助于检测对通用语法的滥用, 它可以预示构建的URI把用户带入歧途(7.6).
机构
一些URI schemes包含一个层次元素来描述一个命名的机构,这样由该URI的其他部分定义的命名空间的管理方式就被授予那个机构(它可以相应地进一步授予). 通用语法提供了一个常见方法来基于已注册的名称或服务器地址来区分一个机构, 以及可选的端口和用户信息.
机构部件的前面是一个双斜杠("//")并结束于下一个斜杠("/"), 问号("?"), 或井号("#")字符, 或随着该URI结束.
authority = [ userinfo "@" ] host [ ":" port ]
如果端口部件是空的,URI制作者和正规化者应该省略把端口和主机分隔开的 ":" 分隔符. 一些schemes不允许用户信息 和/或 端口子部件.
如果一个URI包含一个机构部件, 那么路径部件必须要么是空的要么以一个斜杠("/")字符开始. 不检查的解析器(那些很少分隔一个URI参数到它的主要部件的解析器)将经常忽略机构的子部件结构, 把从双斜杠到第一个终止分隔符之间的东西当成一个不透明的字符串, 直到该URI被解析的时间.
用户信息
用户信息子部件可以包含一个用户名和, 可选的, 关于如何获得授权访问资源的格式特有的信息. 用户信息, 如果出现, 后面会跟随一个商标("@")以和主机分开.
userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
在用户信息字段对格式 "user:password" 的使用已经被废弃了. 应用不应该在一个用户信息子部件中找到的第一个冒号(":")字符后面提出任何明码文本数据,除非在该冒号后面的数据是空字符串(表示没有密码). 当收到作为一个参数的一部分的这样的数据的时候,应用可以选择忽略或拒绝这类数据并且应该拒绝这类数据以未加密方式存储. 以明码方式通过验证信息已经被证明在绝大多数情况使用它是一个安全风险.
为了用户反馈的目的而提供一个URI的应用, 类似图形化的超文本浏览, 应该以某种从一个URI的其他部分区分出来的方式提供用户信息, 在可能的时候. 当发生用户信息已经被手工误导看起来像一个被信任的域名的情况,这类表达可以帮助到用户(7.6).
主机
机构的主机子部件是由一个封装在方括号内的IP文字, 一个点分十进制格式的IPv4地址, 或一个注册名称来标识的. 主机子部件是大小写敏感的. 在一个URI中出现了主机子部件不意味着该格式要求访问互联网上的给定主机. 在一些情况下, 主机语法只被用于重用现有的用于DNS的创建和部署的注册流程, 从而获得一个全局唯一的名称而不用费劲部署另一个注册表. 无论如何, 这种用法有它自己的代价: 域名所有者可能因为URI制作者无法预计的原因而随时变更. 在另外一些情况下, 子部件内的数据标识一个注册名称但没有任何事情要在一个互联网主机上去做. 我们使用ABNF规则的名称 "host" 是因为那是它最常用的目的, 不是它仅有的目的.
host = IP-literal / IPv4address / reg-name
主机的语法规则是含糊的,因为不能完全区分一个IPv4地址和一个注册名称. 为了澄清语法, 我们应用 "先匹配者赢" 算法: 如果主机匹配IPv4地址规则, 那么它应该被认为是一个IPv4地址文字而不是一个注册名称. 尽管主机是大小写敏感的, 为了统一的目的制作者和正规化者应该对注册名和十六进制地址使用小写, 只在百分号编码使用大写字母.
由版本6RFC3513或更晚版本的互联网协议文字地址标识的主机, 是通过方括号("[" 和 "]")围起IP文字来区分的. 这是唯一一个被允许在URI语法中出现方括号字符的地方. 在可预见的将来, 还未定义的IP文字地址格式, 实现可以使用一个可选的版本标记来显式地指示这样一个格式而不是依靠启发式的决定.
IP-literal = "[" ( IPv6address / IPvFuture ) "]" IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
版本标记不指示IP版本; 而是指示该文字格式的未来版本. 同样的, 实现不能为下面描述的现有的IPv4和IPv6文字地址格式提供该版本标记. 如果一个URI包含一个以 "v" (大小写不敏感)开始的IP-文字, 标识那个版本标记是当前的, 当它由一个不理解该版本标记的含义的应用解参考的时候, 那么该应用返回一个适当的错误 "地址机制不支持".
一个由IPv6文字地址标识的主机被展现在不带前述的版本标记的方括号内. 这里的ABNF是对一个RFC3513提供的IPv6文字地址的文本定义的翻译. 这个语法不支持IPv6范围地址区域标识.
一个128位的IPv6地址被分为8个16位的片段. 每一片展现为大小写敏感的16进制数字, 使用一到四个十六进制数字(允许以零开头). 这8个编码的片首先是给定的最大有效数, 由冒号分开. 可选的, 最小有效的两个片段可以被替换来展现IPv4地址的文本格式. 地址中的一系列一个或多个连续的零值的16位片段可以被忽略, 省略它们的所有数字并确切地在它们的位置留下两个连续的冒号以标记该省略.
IPv6address = 6( h16 ":" ) ls32 / "::" 5( h16 ":" ) ls32 / [ h16 ] "::" 4( h16 ":" ) ls32 / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 / [ *4( h16 ":" ) h16 ] "::" ls32 / [ *5( h16 ":" ) h16 ] "::" h16 / [ *6( h16 ":" ) h16 ] "::" ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal
一个由IPv4文字地址标识的主机被展现为点分十进制表示法(一系列四个范围为0到255的十进制数字, 以 "." 分隔), 如 RFC1123所述,参考 RFC0952. 注意点符号的其他格式可以在一些平台上被解释执行, 如7.4所述, 但本语法只允许四个八位字节的点分十进制格式.
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet dec-octet = DIGIT ; 0-9 / %x31-39 DIGIT ; 10-99 / "1" 2DIGIT ; 100-199 / "2" %x30-34 DIGIT ; 200-249 / "25" %x30-35 ; 250-255
一个被注册名标识的主机是一系列字符,常常要用它在一个本地定义的主机或服务名称注册表中来查找, 尽管该URI的格式特有的语义可以要求替换成一个特有的注册表(或固定的名称表). 大部分常用名称注册表机制是域名系统(DNS). 在DNS中查询到一个注册名要使用RFC1034的3.5节和RFC1123的2.1节中定义的语法. 这样一个名称包含了一系列以 "." 分隔的域标签, 每个域标签以一个文字数字字符开始和结束,也可能包含 "-" 字符. 一个在DNS中完全合格的域名的最右边的域标签可以在后面带一个单独的"." ,并且如果有必要把完整域名和一些本地域名去分开的话则必须带它.
reg-name = *( unreserved / pct-encoded / sub-delims )
如果URI格式为主机定义了一个缺省值, 那么当该主机子部件未定义或当注册名称为空(零长度)的时候那个缺省值适用. 例如, "file" URI格式被定义成没有机构, 空主机, 且 "localhost" 全都代表最终用户的机器, 反之一个缺少机构或空主机的 "http" 格式被认为是非法的.
本协议不强制一个特定的注册名称查询技术并且因而如果不在互操作性的必要情况之上约束reg-name语法. 反之, 它把注册名称语法一致性问题委托给每个执行URI解析的应用的操作系统, 并且那个and操作系统来决定允许使用哪种技术来确定主机身份. 一个URI解析实现可能使用DNS, 主机表, 黄页, NetInfo, WINS, 或任何其他系统来查询注册名称. 然而, 一个全局范围的命名系统, 类似DNS完全合格的域名, 对打算全局范围使用的URIs是必要的. URI制作者应该按照DNS语法使用名称, 即使当DNS的使用不会立刻被看到的时候, 并且应该限制这些名称的长度不超过255个字符.
reg-name语法允许百分号编码字节,这是为了以一个独立于基本的名称解析技术的统一的方法来展现非ASCII注册名称. 非ASCII字符必须首先被根据UTF-8 STD63编码, 并且接着每个UTF-8序列相应的八位字节必须被百分号编码以展现成URI字符. URI制作应用不能对主机使用百分号编码,除非它被用于展示一个UTF-8字符序列. 当一个非ASCII注册名称展现了一个打算通过DNS解析的国际化域名, 该名称必须在名称查询之前被转换成IDNA编码RFC3490. URI制作者应该以IDNA编码提供这些注册名称, 而不是一个百分号编码, 如果他们希望最大化和外部URI解析者的互操作能力.
端口
机构的端口子部件由一个可选的跟随在主机后面并且从单个的冒号 (":") 字符之后开始的十进制端口号来指定.
port = *DIGIT
一个格式可以定义一个缺省端口. 例如, "http" 格式定义了一个缺省端口 "80", 和它的保留TCP端口号码一致. 由端口号指定的端口的类型(例如, TCP, UDP, SCTP)是由该URI格式定义的. URI制作者和正规化者应该省略端口部件和它的 ":" 边界,如果端口是空的或它的值和该格式的缺省值相同的话.
路径
路径部件包含数据, 常常组织成层次格式, 它连同非层次化的查询部件(3.4)的数据, 服务于识别在该URI的格式和命名机构(如果有)范围内的一个资源. 路径由第一个问号("?")或数字符号("#")字符或该URI的结尾来终止.
如果一个URI包含一个机构部件, 那么该路径部件必须要么是空的要么以一个斜杠("/")字符开始. 如果一个URI不包含一个机构部件, 那么路径不能以双斜杠字符("//")开始. 此外, 一个URI引用(4.1)可以是一个和路径相关的引用, 在那种情况下第一个路径片段不能包含一个冒号(":")字符. ABNF要求五个独立的规则来消除这些情况的歧义, 只有其中一种情况将会在一个给定的URI引用中和该路径子字符串匹配. 我们使用通用术语 "路径部件" 来描述由这些规则的解析器匹配的该URI子字符串.
path = path-abempty ; begins with "/" or is empty / path-absolute ; begins with "/" but not "//" / path-noscheme ; begins with a non-colon segment / path-rootless ; begins with a segment / path-empty ; zero characters path-abempty = *( "/" segment ) path-absolute = "/" [ segment-nz *( "/" segment ) ] path-noscheme = segment-nz-nc *( "/" segment ) path-rootless = segment-nz *( "/" segment ) path-empty = 0<pchar> segment = *pchar segment-nz = 1*pchar segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":" pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
一个路径包含一系列由斜杠("/")字符分隔的路径片段. 对于一个URI来说总是会定义一个路径, 尽管定义的路径可能是空(零长度). 指示层次用的斜杠字符之在一个URI用作指示相对引用的时候是必需的. 例如, URI <mailto:fred@example.com> 有一个路径 "fred@example.com", 反之 URI <foo://info.example.com?fred> 有一个空路径.
路径片段 "." 和 "..", 也就是点片段, 被定义用于路径名称层次的相对引用. 它们被用于相对路径引用的开始([RFCSection 4.2]) 以指示名称的层次树中的相对位置. 这和某些操作系统的文件目录结构分别指示当前目录和父目录的它们的角色类似. 无论如何, 和在一个文件系统中不一样, 这些点片段只在URI层次中被这样理解并且被从解析过程的一部分中移除(5.2).
除了层次路径中的点片段之外, 一个路径片段被通用语法当成不透明的. URI制作应用经常在一个片段中使用保留字符以界定格式特有的或解参考处理者特有的子部件. 例如, 分号(";")和等号("=")保留字符经常被用于界定适用于那个片段的参数和参数值. 逗号(",")保留字符经常被用于类似的目的. 例如, 一个URI制作者可以使用一个类似 "name;v=1.1" 的片段来指示一个对 "name" 的1.1版的引用, 反之另一个可能使用类似 "name,1.1" 的片段来指示相同的东西. 参数类型可以由格式特有的语义来定义, 但是在大多数情况下一个参数的语法对该URI的解参考算法的实现来说是特有的.
查询
查询部件包含非层次化的数据, 以及路径部件中的数据(3.3), 用来从该URI的格式和命名机构(如果有)中识别一个资源. 查询部件由第一个问号("?")字符指示并由一个数字符号("#")字符或该URI的结尾终止.
query = *( pchar / "/" / "?" )
字符斜杠("/")和问号("?")可以展现该查询部件内的数据. 注意当它被用于相对引用的基础URI的时候5.1,一些旧的, 错误的实现可能没有正确处理这类数据, 显然是因为它们在查找层次分隔符的时候未能把查询部件从路径数据中区分开来. 无论如何, 因为查询部件经常被用于以 "键=值" 对的格式携带识别信息并且一个常被使用的值是对另一个URI的引用, 它经常比百分号编码那些字符有更好的可用性.
片段
一个URI的片段标识符部件允许对一个主要资源引用的次要资源的非直接身份认证以及额外的识别信息. 被识别的次要资源可以是该主要资源的一部分或子集, 或一些由那些陈述所定义或描述的资源. 一个片段标识符部件是通过一个数字符号("#")字符的出现来指示的并由该URI的结尾来终止.
fragment = *( pchar / "/" / "?" )
一个片段标识符的语义可以是由一组从主要资源的获取动作导致的表现来定义的. 所以片段的格式和解析依赖于一个潜在的获取展现的媒体类型RFC2046, 即使这样一个获取只在URI被解参考的时候执行. 如果没有这类体现存在, 那么该片段的语义被认为是未知的并且是有效的无限制. 片段标识符语义独立于URI格式并且因而不能被格式协议重新定义.
单独的媒体类型可以在片段标识符语法内定义它们自己的约束或结构用于指定不同类型的子集,视图或对那个媒体类型可识别的次要资源的外部引用. 如果主要资源有多个展现,这中情况常常是资源的展现是该获取请求的属性的可选的基础(又名, 内容协商), 那么无论该片段识别的什么都应该和那些展现一致. 每个展现应该要么定义片段这样它对应相同的次要资源,无论它是如何展现的, 要么应该让该片段未定义(即, 未找到).
对任何URI, 片段标识符部件的使用不代表将执行一个获取动作. 一个带了片段标识符的URI可被用于指向次要资源而不代表任何主要资源是可访问的或永远无法访问.
片段标识符在信息获取系统中有一个特别的作用是作为客户端间接引用的主要格式, 允许一个作者特别地识别一个现有的只由该资源所有者间接提供的资源的样子. 同样, 片段标识符不被用于一个URI的格式特有的处理; 反之, 在解参考之前片段标识符被从URI的其他部分分离出来, 因而该片段本身的识别信息的解参考由用户代理执行, 和该URI格式无关. 尽管这个独立的处理经常被认为缺少信息, 特别是当引用随时间移动的时候引用的准确重定向, 它也用于防止信息提供者禁止引用作者在一个可选的资源中引用信息. 间接引用也为系统使用URIs提供额外的可伸缩性和可扩展性, 因为定义和部署新媒体类型比新身份格式更容易.
斜杠("/")和问号("?")被允许在片段标识符内展示数据. 注意一些旧的, 错误的实现可能未正确处理这个数据,当它被用作相对引用的基础URI的时候(5.1).
用法
当应用引用一个URI的时候, 它们不总是使用该 "URI" 语法规则定义的引用的完整格式. 为了节省空间和方便层次化区域, 一些互联网协议元素和媒体类型格式允许一个URI的缩写, 反之其他则把语法限制到URI的某个特定格式. 我们在本文定义引用语法的最常见格式,因为它们影响和以来通用语法的设计, 为了解释的一致性而要求一个通用解析算法.
URI引用
URI引用常常代表一个资源标识符的最常见的用法.
URI-reference = URI / relative-ref
一个URI引用要么是一个URI要么是一个相对引用. 如果该URI引用的前缀和跟随在它的冒号后面的格式的语法不匹配, 那么该URI引用是一个相对引用.
一个URI引用典型地首先被解析成5个URI部件, 以确定什么组件是当前的以及该引用是否是相对的. 接着, 每个部件被解析为子部件和它们的校验. URI引用的ABNF, 遵循 "先匹配者赢" 的消除歧义规则, 足够为通用语法定义一个校验解析器. 对正则表达式熟悉的读者们应该去看附录B的一个非校验的URI引用解析器的例子,它将使用任何给定的字符串并提取该URI部件.
相对引用
一个相对引用可以方便地用层次化语法(1.2.3) 表达和另一个层次化URI的命名空间相关的一个URI引用.
relative-ref = relative-part [ "?" query ] [ "#" fragment ] relative-part = "//" authority path-abempty / path-absolute / path-noscheme / path-empty
由一个相对引用所指向的URI, 也被认为是目标URI, 通过应用第5章的引用解析算法来获得.
一个开始于两个斜杠字符的相对引用被称为一个网络路径引用; 这类引用很少被使用. 一个开始于单个斜杠字符的相对引用被称为一个绝对路径引用. 一个不以斜杠字符开始的相对引用被称为一个相对路径引用.
一个包含一个冒号字符的路径段落(例如, "this:that")不能被用作一个相对路径引用的第一个段落, 因为它会被误认为一个格式名称. 这样一个段落必须在它前面放一个点段落(例如, "./this:that")以制作一个相对路径引用.
绝对URI
一些协议元素仅被一个不带片段标识符的URI的绝对格式允许. 例如, 定义一个符合不允许片段的绝对URI语法规则的基础URI用于晚些时候供相对引用调用.
absolute-URI = scheme ":" hier-part [ "?" query ]
URI格式协议必须定义它们自己的语法,这样所有和它们的格式特有的语法匹配的字符也将和<绝对URI>语法匹配. 格式协议将不定义片段标识符语法或用法, 不管它对来自那个格式的资源标识符的适用性如何, 因为片段标识符直交于格式定义. 无论如何, 鼓励格式协议包含大范围的示例, 包括展示携带了片段标识符的格式的URI如何使用,当这类用法是恰当的时候.
同文引用
当一个URI引用指向这样一个URI, 除了它的片段部件以外(如果有的话), 和基础URI(5.1)完全一样, 那个引用被称为一个 "同文" 引用. 同文引用的最常用的例子是空的或只包含一个后面跟随着片段标识符的数字标记("#")分隔符的相对引用.
当一个同文引用为了一个获取动作而被解参考, 那个引用的目标被定义成在该引用的同一个实体(展示, 文档, 或消息)中; 所以, 一个解参考不应改导致一个新的获取动作.
在基础的和目标的URIs的比较之前进行正规化, 如6.2.2和6.2.3所述, 是被允许的,但是实践中很少被执行. 正规化可以增加同文引用集合, 这有利于某些缓存引用. 同样, 引用作者不应该假定那是轻微的不同, 甚至等价, 引用URI将(或将不)被任何给定应用理解为一个同文引用.
后缀引用
URI语法是为通过URI格式明确地引用资源和可扩展性而定义的. 然而, 因为URI识别和用法以及普及了, 传统媒体(电视, 广播, 报纸, 公告牌, 等等.)已经越来越多地使用一个URI的后缀来作为一个引用, 只包含URI的机构和路径部分, 类似
www.w3.org/Addressing/
或简单的一个它自己的DNS注册名称. 这类引用主要是为了自然人而不是机器理解, 同时假定基于上下文的启发对完成该URI是足够的(例如, 大部分以 "www" 开始的注册名似乎有一个URI前缀 "http://"). 尽管没有对一个URI后缀消除歧义的启发的标准集合, 一些客户端实现云讯它们由用户输入并且启发式地理解.
尽管使用后缀引用在实践中很常见, 应该避免任何的可能并且应该永远不在被期望长期使用的引用的情况下使用. 上面提到的启发将随事件改变, 特别是当一个新的URI格式流行的时候, 并且当超出上下文使用的时候经常是不正确的. 进一步的, 按照RFC1535的那些描述,它们能导致安全性问题.
因为一个URI后缀和一个相对路径引用有相同的语法, 一个后缀引用不能被用于被期望使用相对引用的上下文中. 结果是, 后缀引用被限制于没有定义基础URI的地方, 类似对话框和离线广告.
引用解析
本章定义解析一个允许相对引用的上下文中的URI引用的过程,结果是一个和第3章的<URI>语法规则匹配的字符串.
建立基础URI
术语 "相对" 暗示存在一个应用于相对引用的 "基础URI". 除了仅片段引用(4.4), 相对引用只在已知一个基础URI的时候有用. 一个基础URI必须在解析可能被引用的URI之前由解析器建立. 一个基础URI必须遵循<absolute-URI>语法规则(4.3). 如果该基础是从一个URI引用URI获得的, 那么该引用必须在它被用作一个基础URI之前被转化成绝对格式并剥离任何片段部件.
一个引用的基础URI可以以四种方法之一来建立, 在下面按优先级逐一讨论. 优先级的顺序以术语的层级来考虑, 被定义得最靠近基础URI的就是最高优先级. 图形化显示如下:
.----------------------------------------------------------. | .----------------------------------------------------. | | | .----------------------------------------------. | | | | | .----------------------------------------. | | | | | | | .----------------------------------. | | | | | | | | | <relative-reference> | | | | | | | | | `----------------------------------' | | | | | | | | (5.1.1) Base URI embedded in content | | | | | | | `----------------------------------------' | | | | | | (5.1.2) Base URI of the encapsulating entity | | | | | | (message, representation, or none) | | | | | `----------------------------------------------' | | | | (5.1.3) URI used to retrieve the entity | | | `----------------------------------------------------' | | (5.1.4) Default Base URI (application-dependent) | `----------------------------------------------------------'
嵌入在内容里的基础URI
在特定的媒体类型中, 一个用于相对引用的基础URI可能被封装在内容本身之中,这它能很容易地被一个解析器获得. 这对于描述性文档是很有用的, 类似内容表格等, 它可以通过协议被转换成其他东西而不是它们常常被获取的时候的上下文(例如, email 或 USENET 新闻).
指定一个基础URI如何被每个媒体类型嵌入超出了本协议的范围. 当有适当的语法可用的时候, 它由和每个媒体类型相关的数据格式协议来描述.
来自封装实体的基础URI
如果没有基础URI被封装, 则该基础URI由展示的获取上下文来定义. 对一个封闭在另一个实体中的文档来说, 这样一个消息或存档, 获取上下文就是那个实体. 所以, 一个展示的缺省基础URI就是把展现封装在其中的那个实体的基础URI.
在一个MIME容器类型(例如, 消息和多部件类型)中封装一个基础URI的机制由MHTMLRFC2557定义. 协议不使用MIME消息头语法, 但是的确允许某些标记元数据的格式被包含在消息中, 可能为了定义一个基础URI成为一个消息的一部分来定义它们自己的语法.
来自检索URI的基础URI
如果没有内嵌的基础URI并且表现未封装到一些其他实体中, 那么, 如果一个URI被用来获取表现, 那个URI将被认为是基础URI. 注意如果获取动作是一个重定向请求导致的, 最后被使用URI(即, 导致实际的获取表现的动作的URI)是基础URI.
缺省基础URI
如果没有适用以上描述的条件, 那么基础URI由应用的上下文定义. 因为这个定义需要依赖应用, 如果使用其他方法之一未能定义基础URI可能导致同样的内容在不同类型的应用中有不同的解释.
一个包含相对引用的表现的发送者要负责确保那些应用的一个基础URI能被建立. 除了仅片段的引用, 相对引用只能被用于基础URI被良好定义的可靠情形之下.
相对解析
本章描述了把一个和给定基础URI相关的URI引用转换成引用目标的已解析部件的算法. 然后该部件被重写成目标URI的格式, 如5.3所述. 这个算法提供最后的结果,能被用于测试其他实现的输出. 应用可以使用某些其他算法来实现相对引用解析, 提供的结果将和本算法的结果吻合.
预解析基础URI
基础URI (Base) 是根据5.1的步骤建立的并被解析成3描述的五个主要的部件. 注意在一个基础URI中只有格式部件是必需的; 其他部件可以是空的或未定义的. 如果一个部件的相关分割符未出现在该URI引用之中那么该部件是未定义的; 路径部件永远不会是未定义的, 虽然它可以是空的.
基础URI的正规化, 如6.2.2和6.2.3所述, 是可选的. 一个URI引用必须在它能被正规化之前被转换成它的目标URI.
转换引用
对每个URI引用(R)来说, 以下伪码描述了一个把R转换成它的目标URI(T)的算法:
-- URI引用被解析成5个URI部件 -- (R.scheme, R.authority, R.path, R.query, R.fragment) = parse(R); -- 一个非约束的解析器可以忽略引用中的格式,如果该引用和基础URI的格式是相同的 -- if ((not strict) and (R.scheme == Base.scheme)) then undefine(R.scheme); endif; if defined(R.scheme) then T.scheme = R.scheme; T.authority = R.authority; T.path = remove_dot_segments(R.path); T.query = R.query; else if defined(R.authority) then T.authority = R.authority; T.path = remove_dot_segments(R.path); T.query = R.query; else if (R.path == "") then T.path = Base.path; if defined(R.query) then T.query = R.query; else T.query = Base.query; endif; else if (R.path starts-with "/") then T.path = remove_dot_segments(R.path); else T.path = merge(Base.path, R.path); T.path = remove_dot_segments(T.path); endif; T.query = R.query; endif; T.authority = Base.authority; endif; T.scheme = Base.scheme; endif; T.fragment = R.fragment;
合并路径
上面的伪码适用于一个 "合并" 程序,用于合并相对路径引用和基础URI路径. 它是如下完成的:
- 如果基础URI有一个已定义的机构部件和一个空路径, 则返回一个包含和引用路径一起的 "/" 的字符串; 否则,
- 返回一个字符串,包含该引用的路径部件加上除了该基础URI的路径的最后一个片段的所有其他片段(即, 除基础URI路径最右边的 "/" 后面的任何符号, 或除了完整的基础URI路径(如果该基础URI不包含任何 "/" 符号)).
移除点片段
该伪码也适用于一个 "移除点片段" 程序,用于从一个被引用路径解释和移除特定的 "." and ".." 完整路径片段. 这要在该路径被从一个引用提取出来之后来做, 无论该路径是否相对的, 目的是在格式化目标URI之前移除任何非法或无关的点片段. 尽管有很多方法可以完成这个移除过程, 我们描述一个使用两个字符串缓冲的简单方法.
1. 输入缓冲以刚加上的路径部件初始化,而输出缓冲被初始化成空字符串.
2. 当输入缓冲是空的时候, 按以下回路:
- A. 如果输入缓冲以前缀 "../" 或 "./" 开始, 那么从输入缓冲移除该前缀; 否则,
- B. 如果输入缓冲以前缀 "/./" 或 "/." 开始, 这里 "." 是一个完整的路径片段, 那么在输入缓冲中把那个前缀替换成 "/" ; 否则,
- C. 如果输入缓冲以前缀 "/../" 或 "/.." 开始, 这里 ".." 是一个完整路径片段, 那么在输入缓冲中把该前缀替换成 "/" 并从输出缓冲中移除最后的片段以及它前面的 "/" (如果有的话); 否则,
- D. 如果输入缓冲仅包含 "." 或 ".." , 那么从输入缓冲移除它; 否则,
- E. 在输入缓冲中把第一个路径片段移动到输出缓冲的尾部, 包括初始的 "/" 符号(如果有的话)和任何随后的符号, 但不包括下一个 "/" 符号或输入缓冲的结尾.
3. 最后, 输出缓冲被返回一个 移除了点片段 的结果.
注意点片段的目的是在URI引用中用于在基础URI中表达一个和名称的层次相对的标识符. 移除点片段 算法通过移除额外的点片段来尊重层次而不是把它们当成一个错误或留着它们去被解参考实现误解.
接下来演示上述步骤如何应用于合并路径的两个例子, 展示每个步骤之后两个缓冲的状态.
STEP OUTPUT BUFFER INPUT BUFFER 1 : /a/b/c/./../../g 2E: /a /b/c/./../../g 2E: /a/b /c/./../../g 2E: /a/b/c /./../../g 2B: /a/b/c /../../g 2C: /a/b /../g 2C: /a /g 2E: /a/g STEP OUTPUT BUFFER INPUT BUFFER 1 : mid/content=5/../6 2E: mid /content=5/../6 2E: mid/content=5 /../6 2C: mid /6 2E: mid/6
一些应用可能发现通过使用两个片段栈而不是字符串来实现移除点片段算法更加高效.
- 注意: 当心一些旧的, 错误的实现将无法在合并基础和引用路径之前从它的路径部件分离出引用的查询部件, 如果该查询部件包含字符串 "/../" 或 "/./" 会导致互操作性失败.
部件重写
已解析的URI部件可以被重写以获得相应的URI引用字符串. 使用伪码, 如下:
result = "" if defined(scheme) then append scheme to result; append ":" to result; endif; if defined(authority) then append "//" to result; append authority to result; endif; append path to result; if defined(query) then append "?" to result; append query to result; endif; if defined(fragment) then append "#" to result; append fragment to result; endif; return result;
注意我们要小心地保持一个未定义的部件和一个空部件之间的区别。未定义的部件, 意味着在当前的引用中它的分隔符不是当前的;空部件, 意味着该分隔符是当前的但是被下一个部件分隔符紧接跟在它后面或者在该引用的尾部.
引用解析示例
对于有良好定义的基础URI的以下URI的陈述中
http://a/b/c/d;p?q
一个相对引用被转换成它的目标URI如下.
常规示例
"g:h" = "g:h" "g" = "http://a/b/c/g" "./g" = "http://a/b/c/g" "g/" = "http://a/b/c/g/" "/g" = "http://a/g" "//g" = "http://g" "?y" = "http://a/b/c/d;p?y" "g?y" = "http://a/b/c/g?y" "#s" = "http://a/b/c/d;p?q#s" "g#s" = "http://a/b/c/g#s" "g?y#s" = "http://a/b/c/g?y#s" ";x" = "http://a/b/c/;x" "g;x" = "http://a/b/c/g;x" "g;x?y#s" = "http://a/b/c/g;x?y#s" "" = "http://a/b/c/d;p?q" "." = "http://a/b/c/" "./" = "http://a/b/c/" ".." = "http://a/b/" "../" = "http://a/b/" "../g" = "http://a/b/g" "../.." = "http://a/" "../../" = "http://a/" "../../g" = "http://a/g"
反常示例
尽管以下反常示例未必发生在常规的实践中, 所有URI解析器应该有能力一致性地解析它们. 每个例子使用和上述相同的基础.
比起在基础URI的路径中有多个层次,解析器必须更加小心地处理在一个相对路径引用中有多个 ".." 分段的情况. 注意 ".." 语法不能被用于改变一个URI的机构部件.
"../../../g" = "http://a/g" "../../../../g" = "http://a/g"
类似的, 解析器必须移除点片段 "." 和 ".." ,当它们是一个路径的完整部件的时候, 但不是当它们是一个片段的仅有的部分的时候.
"/./g" = "http://a/g" "/../g" = "http://a/g" "g." = "http://a/b/c/g." ".g" = "http://a/b/c/.g" "g.." = "http://a/b/c/g.." "..g" = "http://a/b/c/..g"
更罕见的情况是相对引用使用 "." 和 ".." 的不必要或无意义的格式完成路径片段.
"./../g" = "http://a/b/g" "./g/." = "http://a/b/c/g/" "g/./h" = "http://a/b/c/g/h" "g/../h" = "http://a/b/c/h" "g;x=1/./y" = "http://a/b/c/g;x=1/y" "g;x=1/../y" = "http://a/b/c/y"
一些应用在合并路径部件和基础路径并移除点片段之前未能从该路径部件分离出该引用的查询 和/或 片段部件. 这个错误很少被注意到, 因为一个片段的典型用法永远不会包含层次("/")符号并且查询部件通常不被用在相对引用中.
"g?y/./x" = "http://a/b/c/g?y/./x" "g?y/../x" = "http://a/b/c/g?y/../x" "g#s/./x" = "http://a/b/c/g#s/./x" "g#s/../x" = "http://a/b/c/g#s/../x"
一些解析器允许格式名在相对引用中使用,如果它和基础URI格式相同. 在之前的部分URI协议[RFC1630 http://tools.ietf.org/html/rfc1630]中它被认为是一个漏洞. 应该避免它的使用但是允许向后兼容.
"http:g" = "http:g" ; for strict parsers / "http://a/b/c/g" ; for backward compatibility
正规化和比较
在URIs上最常见的操作之一是简单比较:在不使用URIs访问它们各自的资源的情况下确定两个URIs是否等价. 每次一个应答缓存被访问的时候就执行比较动作, 一个浏览器检查它的历史来给一个链接着色, 或一个XML解析器处理一个命名空间中的标签. 在比较URIs之前进行广泛的正规化经常被用于蜘蛛和索引引擎以精简搜索空间或减少重复的请求动作和应答存储.
URI比较被执行用于一些特别的目的. 为不同目的而比较URIs的协议或实现将经常受限于不同的设计去权衡应该花多少努力去减少别名标识符. 这一章描述可以用于比较URIs的各种方法, 它们之间的权衡, 以及可以使用它们的应用的类型.
等价
因为URIs存在识别资源, 当它们标识到相同的资源想必它们应该认为它们是等价的. 无论如何, 等价的这一定义实际上没有用得那么多, 因为对一个实现来说除非有它们的全部只是或控制没办法比较两个资源. 基于这个原因, URIs的等价或不同的确定是基于字符串比较的, 可能被URI格式定义提供的额外规则的引用所增强. 我们使用术语 "不同" 和 "等价" 来描述这以比较的可能的结果, 但是有很多依赖于应用的版本的等价.
即使有可能确定两个URIs是等价的, URI比较不足以确定两个URIs是否标识不同的资源. 例如, 两个不同的域名的同一个所有者可能决定两者同时服务于相同的资源, 导致两个不同的URIs. 所以, 比较方法被定义用来最小化错误的否定从而严格地避免错误的肯定.
对于等价的测试, 应用应该不直接比较相对引用; 引用应该在比较之前被转换成它们各自的目标URIs. 当URIs被比较以选择(或避免)一个网络动作, 类似展示的获取, 片段部件(如果有)应该从比较中排除.
比较阶梯
实践中用来测试URI等价的方法有很多变种. 这些方法落在一个范围里, 需要多少过程来区分和哪种方法更能减少错误的否定. 如上所述, 错误的否定无法避免. 实践中, 它们的可能性能被减少, 但是这个减少要求更多的处理并且不是对所有应用都合算的.
如果比较实践的范围被看作一个阶梯, 接下来的讨论将攀登这个阶梯, 开始的实践是廉价但是有相对高的机会产生错误的否定, 然后去到那些有更高计算成本和更低的错误否定风险的实践.
简单字符串比较
如果两个URIs, 当被认为是字符串, 并且相同, 那么断定它们等价是安全的. 这类等价的测试的计算成本非常低并且广泛用于各种应用, 特别是在解析域名中.
测试字符串的等价性需要一些基本的注意事项. 这个过程经常被称为 "每比特" 或 "每字节" 比较, 它是潜在的误导. 测试字符串的等价性通常是基于构成该字符串的字符对, 从第一个开始一直到两个字符串结束且发现所有字符都等价, 或直到一对字符比较不等价, 或字符串之一在另一个之前结束.
这一字符比较需要每一对字符是以可比较的格式存放的. 例如, 一个URI应该被存储成一个以EBCDIC编码的字节数组而第二个URI被存储成一个Java字符串对象(UTF-16), 天真地应用每比特比较将产生错误. 等价比较的更好的说法是基于每字符而不是每字节或每比特. 实际上, 每字符比较应该是在转换成通用字符编码之后的每码点比较. 错误的否定是由URI别名的生产和使用造成的. 无关比较方法, 通过在一个已常规化的格式中一致性地提供URI引用(即, 和应用常规化之后被生产的格式相同的一个格式, 如下所述)可以减少不必要的别名.
协议和数据格式经常限制了一些URI比较去简化字符串比较, 基于这个理论人们和应用将出于他们自己的最佳利益, 一致性地提供URI引用, 或至少一致到足够否决任何从更多常规化中获得的效率.
基于语法的常规化
实现可以使用基于本协议提供的定义的逻辑来降低错误否定的可能性. 这个过程的开销适度地高于每字符的字符串比较. 例如, 一个使用这个方法的应用可能合理地认为以下两个URIs是等价的:
example://a/b/c/%7Bfoo%7D eXAMPLE://a/./b/../b/%63/%7bfoo%7d
Web用户代理, 类似浏览器, 当决定是否一个缓存的应答可用的时候典型地应用这类URI常规化. 基于语法的常规化包括这类技术,如案例常规化, 百分号常规化, 以及移除点片段.
大写常规化
对于所有URIs来说, 一个百分号编码的三连符中的十六进制数字(例如, "%3a" 和 "%3A")是大小写不敏感的并且因此应该使用大写字母来常规化数字 A-F.
当一个URI使用通用语法部件, 该部件语法语法等价规则总是适用; 亦即, 格式和主机是大小写不敏感的并且因此应该被常规化成小写. 例如, URI <HTTP://www.EXAMPLE.com/> 等价于 <http://www.example.com/>. 其他通用语法部件被假定是大小写敏感的,除非由格式特别定义(见 6.2.3).
百分号编码常规化
百分号编码机制(2.1)在其他相同的URIs中是一个频繁出现的来源. 除了上面注意到的大写常规化问题, 一些百分号编码八位字节的URI生产者不必需百分号编码, 结果是那些URIs等价于它们的未编码部分. 这些URIs应该被解码任何百分号编码来常规化以对应一个非保留的字符, 如2.3所述.
路径片段常规化
完整的路径片段 "." 和 ".." 只打算用在相对引用中(4.1)并且会作为引用解析过程的一部分被移除(5.2). 无论如何, 一些已部署的实现不正确地假定当引用已经是一个URI的时候引用解析是不必要的,使得当它们遇到非相对路径时未能移除点片段. URI常规化者应该通过应用 移除点片段 算法来从该路径移除点片段, 如5.2.4所述.