Skip to main content

1. Introduction (简介)

1.1. Overview (概述)

统一资源标识符 (Uniform Resource Identifier, URI) [RFC3986] 通常用于在相似资源的公共空间(非正式地称为"URI空间")中标识特定资源。例如,个人网络空间通常使用公共模式进行委派,如:

http://example.com/~fred/
http://example.com/~mark/

或者,一组词典条目可能按术语的首字母在层次结构中分组,如:

http://example.com/dictionary/c/cat
http://example.com/dictionary/d/dog

或者,服务接口可能以公共模式通过各种用户输入调用,如:

http://example.com/search?q=cat&lang=en
http://example.com/search?q=chien&lang=fr

URI模板 (URI Template) 是一种紧凑的字符序列,用于通过变量扩展 (Variable Expansion) 描述一系列统一资源标识符。

URI模板提供了一种机制,用于抽象资源标识符空间,使得可以轻松识别和描述可变部分。URI模板有许多用途,包括发现可用服务、配置资源映射、定义计算链接、指定接口以及其他形式的资源编程交互。例如,上述资源可以通过以下URI模板描述:

http://example.com/~{username}/
http://example.com/dictionary/{term:1}/{term}
http://example.com/search{?q,lang}

我们定义以下术语:

expression (表达式):第2节中定义的 '' 之间的文本,包括封闭的花括号。

expansion (扩展):根据表达式类型、变量名列表和值修饰符处理模板表达式后获得的字符串结果,如第3节所定义。

template processor (模板处理器):一个程序或库,给定URI模板和一组带值的变量,通过解析模板中的表达式并用其对应的扩展替换每个表达式,将模板字符串转换为URI引用 (URI Reference)。

URI模板既提供了URI空间的结构描述,又在提供变量值时,提供了如何构造与这些值对应的URI的机器可读指令。URI模板通过用表达式类型定义的值以及表达式中命名的变量的值替换每个定界表达式,转换为URI引用。表达式类型范围从简单的字符串扩展到多个name=value列表。扩展基于URI通用语法,允许实现处理任何URI模板,而无需了解每个可能生成的URI的方案特定要求。

例如,以下URI模板包含一个表单风格参数表达式 (Form-Style Parameter Expression),由出现在变量名之前的 "?" 运算符指示:

http://www.example.com/foo{?query,number}

以问号 ("?") 运算符开头的表达式的扩展过程遵循与万维网上的表单风格接口相同的模式:

http://www.example.com/foo{?query,number}
\_____________/
|
|
对于 [ 'query', 'number' ] 中的每个已定义变量,
如果是第一次替换则替换为 "?",否则替换为 "&",
后跟变量名、'=' 和变量的值。

如果变量具有以下值:

query  := "mycelium"
number := 100

则上述URI模板的扩展为:

http://www.example.com/foo?query=mycelium&number=100

或者,如果 'query' 未定义,则扩展为:

http://www.example.com/foo?number=100

或者如果两个变量都未定义,则扩展为:

http://www.example.com/foo

URI模板可以以绝对形式提供(如上述示例),也可以以相对形式提供。模板在将结果引用从相对形式解析为绝对形式之前进行扩展。

虽然结果使用URI语法,但允许模板字符串包含国际化资源标识符 (Internationalized Resource Identifier, IRI) 引用 [RFC3987] 中可以找到的更广泛的字符集。因此,URI模板也是IRI模板,模板处理的结果可以通过遵循 [RFC3987] 第3.2节中定义的过程转换为IRI。

1.2. Levels and Expression Types (级别与表达式类型)

URI模板类似于具有固定宏定义集的宏语言:表达式类型 (Expression Type) 决定扩展过程。默认的表达式类型是简单字符串扩展 (Simple String Expansion),其中单个命名变量在对不属于未保留URI字符集 (Unreserved URI Characters) 的任何字符进行百分号编码 (Percent-Encoding) 后,被其字符串值替换(第1.5节)。

由于在本规范之前实现的大多数模板处理器仅实现了默认表达式类型,我们将这些称为Level 1模板。

.-----------------------------------------------------------------.
| Level 1 示例,变量值为: |
| |
| var := "value" |
| hello := "Hello World!" |
| |
|-----------------------------------------------------------------|
| 操作符 表达式 扩展结果 |
|-----------------------------------------------------------------|
| | 简单字符串扩展 (第3.2.2节) |
| | |
| | {var} value |
| | {hello} Hello%20World%21 |
'-----------------------------------------------------------------'

Level 2模板添加了加号 ("+") 运算符,用于扩展允许包含保留URI字符 (Reserved URI Characters) 的值(第1.5节),以及井号 ("#") 运算符,用于扩展片段标识符 (Fragment Identifiers)。

.-----------------------------------------------------------------.
| Level 2 示例,变量值为: |
| |
| var := "value" |
| hello := "Hello World!" |
| path := "/foo/bar" |
| |
|-----------------------------------------------------------------|
| 操作符 表达式 扩展结果 |
|-----------------------------------------------------------------|
| + | 保留字符串扩展 (第3.2.3节) |
| | |
| | {+var} value |
| | {+hello} Hello%20World! |
| | {+path}/here /foo/bar/here |
| | here?ref={+path} here?ref=/foo/bar |
|-----+-----------------------------------------------------------|
| # | 片段扩展,井号前缀 (第3.2.4节) |
| | |
| | X{#var} X#value |
| | X{#hello} X#Hello%20World! |
'-----------------------------------------------------------------'

Level 3模板允许每个表达式包含多个变量,每个变量用逗号分隔,并为点前缀标签 (Dot-Prefixed Labels)、斜杠前缀路径段 (Slash-Prefixed Path Segments)、分号前缀路径参数 (Semicolon-Prefixed Path Parameters) 以及由与号字符分隔的name=value对组成的查询语法的表单风格构造 (Form-Style Construction) 添加了更复杂的运算符。

.-----------------------------------------------------------------.
| Level 3 示例,变量值为: |
| |
| var := "value" |
| hello := "Hello World!" |
| empty := "" |
| path := "/foo/bar" |
| x := "1024" |
| y := "768" |
| |
|-----------------------------------------------------------------|
| 操作符 表达式 扩展结果 |
|-----------------------------------------------------------------|
| | 多变量字符串扩展 (第3.2.2节) |
| | |
| | map?{x,y} map?1024,768 |
| | {x,hello,y} 1024,Hello%20World%21,768 |
| | |
|-----+-----------------------------------------------------------|
| + | 多变量保留字符扩展 (第3.2.3节) |
| | |
| | {+x,hello,y} 1024,Hello%20World!,768 |
| | {+path,x}/here /foo/bar,1024/here |
| | |
|-----+-----------------------------------------------------------|
| # | 多变量片段扩展 (第3.2.4节) |
| | |
| | {#x,hello,y} #1024,Hello%20World!,768 |
| | {#path,x}/here #/foo/bar,1024/here |
| | |
|-----+-----------------------------------------------------------|
| . | 标签扩展,点前缀 (第3.2.5节) |
| | |
| | X{.var} X.value |
| | X{.x,y} X.1024.768 |
| | |
|-----+-----------------------------------------------------------|
| / | 路径段扩展,斜杠前缀 (第3.2.6节) |
| | |
| | {/var} /value |
| | {/var,x}/here /value/1024/here |
| | |
|-----+-----------------------------------------------------------|
| ; | 路径风格参数,分号前缀 (第3.2.7节) |
| | |
| | {;x,y} ;x=1024;y=768 |
| | {;x,y,empty} ;x=1024;y=768;empty |
| | |
|-----+-----------------------------------------------------------|
| ? | 表单风格查询,与号分隔 (第3.2.8节) |
| | |
| | {?x,y} ?x=1024&y=768 |
| | {?x,y,empty} ?x=1024&y=768&empty= |
| | |
|-----+-----------------------------------------------------------|
| & | 表单风格查询续接 (第3.2.9节) |
| | |
| | ?fixed=yes{&x} ?fixed=yes&x=1024 |
| | {&x,y,empty} &x=1024&y=768&empty= |
| | |
'-----------------------------------------------------------------'

最后,Level 4模板将值修饰符 (Value Modifiers) 作为可选后缀添加到每个变量名。前缀修饰符 (Prefix Modifier) (":") 表示扩展仅使用值开头的有限数量的字符(第2.4.1节)。展开修饰符 (Explode Modifier) ("*") 表示变量将被视为复合值 (Composite Value),由名称列表或 (name, value) 对的关联数组组成,并像每个成员是单独的变量一样进行扩展(第2.4.2节)。

.-----------------------------------------------------------------.
| Level 4 示例,变量值为: |
| |
| var := "value" |
| hello := "Hello World!" |
| path := "/foo/bar" |
| list := ("red", "green", "blue") |
| keys := [("semi",";"),("dot","."),("comma",",")] |
| |
| 操作符 表达式 扩展结果 |
|-----------------------------------------------------------------|
| | 带值修饰符的字符串扩展 (第3.2.2节) |
| | |
| | {var:3} val |
| | {var:30} value |
| | {list} red,green,blue |
| | {list*} red,green,blue |
| | {keys} semi,%3B,dot,.,comma,%2C |
| | {keys*} semi=%3B,dot=.,comma=%2C |
| | |
|-----+-----------------------------------------------------------|
| + | 带值修饰符的保留字符扩展 (第3.2.3节) |
| | |
| | {+path:6}/here /foo/b/here |
| | {+list} red,green,blue |
| | {+list*} red,green,blue |
| | {+keys} semi,;,dot,.,comma,, |
| | {+keys*} semi=;,dot=.,comma=, |
| | |
|-----+-----------------------------------------------------------|
| # | 带值修饰符的片段扩展 (第3.2.4节) |
| | |
| | {#path:6}/here #/foo/b/here |
| | {#list} #red,green,blue |
| | {#list*} #red,green,blue |
| | {#keys} #semi,;,dot,.,comma,, |
| | {#keys*} #semi=;,dot=.,comma=, |
| | |
|-----+-----------------------------------------------------------|
| . | 标签扩展,点前缀 (第3.2.5节) |
| | |
| | X{.var:3} X.val |
| | X{.list} X.red,green,blue |
| | X{.list*} X.red.green.blue |
| | X{.keys} X.semi,%3B,dot,.,comma,%2C |
| | X{.keys*} X.semi=%3B.dot=..comma=%2C |
| | |
|-----+-----------------------------------------------------------|
| / | 路径段,斜杠前缀 (第3.2.6节) |
| | |
| | {/var:1,var} /v/value |
| | {/list} /red,green,blue |
| | {/list*} /red/green/blue |
| | {/list*,path:4} /red/green/blue/%2Ffoo |
| | {/keys} /semi,%3B,dot,.,comma,%2C |
| | {/keys*} /semi=%3B/dot=./comma=%2C |
| | |
|-----+-----------------------------------------------------------|
| ; | 路径风格参数,分号前缀 (第3.2.7节) |
| | |
| | {;hello:5} ;hello=Hello |
| | {;list} ;list=red,green,blue |
| | {;list*} ;list=red;list=green;list=blue |
| | {;keys} ;keys=semi,%3B,dot,.,comma,%2C |
| | {;keys*} ;semi=%3B;dot=.;comma=%2C |
| | |
|-----+-----------------------------------------------------------|
| ? | 表单风格查询,与号分隔 (第3.2.8节) |
| | |
| | {?var:3} ?var=val |
| | {?list} ?list=red,green,blue |
| | {?list*} ?list=red&list=green&list=blue |
| | {?keys} ?keys=semi,%3B,dot,.,comma,%2C |
| | {?keys*} ?semi=%3B&dot=.&comma=%2C |
| | |
|-----+-----------------------------------------------------------|
| & | 表单风格查询续接 (第3.2.9节) |
| | |
| | {&var:3} &var=val |
| | {&list} &list=red,green,blue |
| | {&list*} &list=red&list=green&list=blue |
| | {&keys} &keys=semi,%3B,dot,.,comma,%2C |
| | {&keys*} &semi=%3B&dot=.&comma=%2C |
| | |
'-----------------------------------------------------------------'

1.3. Design Considerations (设计考虑)

类似于URI模板的机制已在多个规范中定义,包括WSDL [WSDL]、WADL [WADL] 和 OpenSearch [OpenSearch]。本规范扩展并正式定义了语法,使得URI模板可以在多个互联网应用程序和互联网消息字段中一致使用,同时保持与这些早期定义的兼容性。

URI模板语法经过精心设计,以在强大的扩展机制需求与易于实现的需求之间取得平衡。该语法被设计为易于解析,同时提供足够的灵活性来表达许多常见的模板场景。实现能够在单次遍历中解析模板并执行扩展。

当与常见示例一起使用时,模板简单易读,因为单字符运算符与URI通用语法定界符相匹配。当列出的变量都未定义时,运算符关联的定界符("."、";"、"/"、"?"、"&" 和 "#")将被省略。同样,当变量值为空时,";"(路径风格参数)的扩展过程将省略 "=",而 "?"(表单风格参数)的过程在值为空时不会省略 "="。如果运算符没有预定义的连接机制,多个变量和列表值将用 "," 连接它们的值。"+" 和 "#" 运算符将替换变量值内的未编码保留字符;其他运算符将在扩展之前对变量值中的保留字符进行百分号编码。

URI空间的最常见情况可以用Level 1模板表达式描述。如果我们只关心URI生成,那么模板语法可以仅限于简单的变量扩展,因为可以通过更改变量值来生成更复杂的形式。然而,URI模板还有一个额外的目标,即根据预先存在的数据值来描述标识符的布局。因此,模板语法包含反映资源标识符通常如何分配的运算符。同样,由于前缀子字符串经常用于划分大型资源空间,变量值上的修饰符提供了一种使用单个变量名同时指定子字符串和完整值字符串的方法。

1.4. Limitations (限制)

由于URI模板描述的是标识符的超集 (Superset),因此并不意味着每个定界变量表达式的每个可能扩展都对应于现有资源的URI。我们期望根据模板构造URI的应用程序将为被替换的变量提供一组适当的值,或者至少提供一种验证这些值的用户数据输入的方法。

URI模板不是URI:它们不标识抽象或物理资源,它们不作为URI被解析,并且除非模板表达式在使用之前由模板处理器进行扩展,否则不应在期望URI的地方使用它们。应使用不同的字段、元素或属性名称来区分携带URI模板的协议元素与期望URI引用的协议元素。

某些URI模板可以反向用于变量匹配 (Variable Matching) 的目的:将模板与完全形成的URI进行比较,以便从该URI中提取变量部分并将它们分配给命名变量。仅当模板表达式由URI的开头或结尾或不能成为扩展一部分的字符(例如围绕简单字符串表达式的保留字符)定界时,变量匹配才能很好地工作。一般来说,正则表达式语言更适合变量匹配。

1.5. Notational Conventions (符号约定)

本文档中的关键词 "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"MAY" 和 "OPTIONAL" 应按照 [RFC2119] 中描述的方式进行解释。

本规范使用 [RFC5234] 的增强巴科斯-瑙尔范式 (Augmented Backus-Naur Form, ABNF) 表示法。以下ABNF规则从规范性参考文献 [RFC5234]、[RFC3986] 和 [RFC3987] 中导入。

ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
; 大小写不敏感

pct-encoded = "%" HEXDIG HEXDIG
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="

ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
/ %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
/ %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
/ %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
/ %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
/ %xD0000-DFFFD / %xE1000-EFFFD

iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD

1.6. Character Encoding and Unicode Normalization (字符编码与Unicode规范化)

本规范使用术语 "character (字符)"、"character encoding scheme (字符编码方案)"、"code point (码点)"、"coded character set (编码字符集)"、"glyph (字形)"、"non-ASCII (非ASCII)"、"normalization (规范化)"、"protocol element (协议元素)" 和 "regular expression (正则表达式)",它们在 [RFC6365] 中定义。

ABNF表示法将其终端值定义为非负整数(码点),它们是US-ASCII编码字符集 [ASCII] 的超集。本规范将终端值定义为Unicode编码字符集 [UNIV6] 中的码点。

尽管语法和模板扩展过程是根据Unicode码点定义的,但应该理解,模板在实践中以字符序列的形式出现,无论以何种形式或编码适合它们出现的上下文,无论是嵌入在网络协议元素中的八位字节还是绘制在公共汽车侧面的字形。本规范不强制要求任何特定的字符编码方案来在URI模板字符和用于存储或传输这些字符的八位字节之间进行映射。当URI模板出现在协议元素中时,字符编码方案由该协议定义;在没有这样的定义的情况下,假定URI模板与周围文本使用相同的字符编码方案。只有在模板扩展过程中,URI模板中的字符串才必须 (REQUIRED) 作为Unicode码点序列进行处理。

Unicode标准 [UNIV6] 为各种目的定义了字符序列之间的各种等价关系。Unicode标准附录 #15 [UTR15] 为这些等价关系定义了各种规范化形式 (Normalization Forms)。规范化形式确定如何一致地编码等价字符串。理论上,所有URI处理实现(包括模板处理器)都应该使用相同的规范化形式来生成URI引用。实际上,它们并非如此。如果值是由与资源相同的服务器提供的,则可以假定该字符串已经是该服务器期望的形式。如果值是由用户提供的,例如通过数据输入对话框,则在模板处理器使用扩展之前,该字符串应该 (SHOULD) 被规范化为规范化形式C (NFC: Canonical Decomposition, followed by Canonical Composition,规范分解后跟规范组合)。

同样,当表示可读字符串的非ASCII数据被百分号编码用于URI引用时,模板处理器必须 (MUST) 首先将字符串编码为UTF-8 [RFC3629],然后对URI引用中不允许的任何八位字节进行百分号编码。