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 Template (URI模板) 是一个紧凑的字符序列,用于通过变量扩展描述一系列统一资源标识符。

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模板提供了URI空间的结构描述,并且在提供变量值时,提供了关于如何根据这些值构建URI的机器可读指令。URI模板通过用表达式类型和表达式中命名变量的值定义的值替换每个分隔表达式,转换为URI引用。表达式类型范围从简单的字符串扩展到多个name=value列表。这些扩展基于URI通用语法,允许实现处理任何URI模板,而无需知道每个可能的结果URI的特定于方案的要求。

例如,以下URI模板包含一个表单样式参数表达式,如变量名称前的"?"操作符所示。

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语法用于结果,但模板字符串允许包含更广泛的字符集,这些字符可以在国际化资源标识符(IRI)引用[RFC3987]中找到。因此,URI模板也是IRI模板,模板处理的结果可以通过遵循[RFC3987]第3.2节中定义的过程转换为IRI。

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

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

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

.-----------------------------------------------------------------.
| Level 1 示例,变量值为 |
| |
| var := "value" |
| hello := "Hello World!" |
| |
|-----------------------------------------------------------------|
| Op Expression Expansion |
|-----------------------------------------------------------------|
| 简单字符串扩展 (Sec 3.2.2) |
| |
| {var} value |
| {hello} Hello%20World%21 |
`-----------------------------------------------------------------'

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

.-----------------------------------------------------------------.
| Level 2 示例,变量值为 |
| |
| var := "value" |
| hello := "Hello World!" |
| path := "/foo/bar" |
| |
|-----------------------------------------------------------------|
| Op Expression Expansion |
|-----------------------------------------------------------------|
| + 保留字符串扩展 (Sec 3.2.3) |
| |
| {+var} value |
| {+hello} Hello%20World! |
| {+path}/here /foo/bar/here |
| here?ref={+path} here?ref=/foo/bar |
|-----+---------------------------------------------------------------|
| # 片段扩展,井号前缀 (Sec 3.2.4) |
| |
| X{#var} X#value |
| X{#hello} X#Hello%20World! |
`-----------------------------------------------------------------'

Level 3模板允许每个表达式包含多个变量,每个变量用逗号分隔,并添加了更复杂的操作符,用于点前缀标签、斜杠前缀路径段、分号前缀路径参数,以及由&字符分隔的name=value对组成的查询语法的表单样式构造。

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

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

.-----------------------------------------------------------------.
| Level 4 示例,变量值为 |
| |
| var := "value" |
| hello := "Hello World!" |
| path := "/foo/bar" |
| list := ("red", "green", "blue") |
| keys := [("semi",";"),("dot","."),("comma",",")] |
| |
| Op Expression Expansion |
|-----------------------------------------------------------------|
| 带值修饰符的字符串扩展 (Sec 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 |
| |
|-----+---------------------------------------------------------------|
| + 带值修饰符的保留扩展 (Sec 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=, |
| |
|-----+---------------------------------------------------------------|
| # 带值修饰符的片段扩展 (Sec 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=, |
| |
|-----+---------------------------------------------------------------|
| . 标签扩展,点前缀 (Sec 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 |
| |
|-----+---------------------------------------------------------------|
| / 路径段,斜杠前缀 (Sec 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 |
| |
|-----+---------------------------------------------------------------|
| ; 路径样式参数,分号前缀 (Sec 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 |
| |
|-----+---------------------------------------------------------------|
| ? 表单样式查询,&符号分隔 (Sec 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 |
| |
|-----+---------------------------------------------------------------|
| & 表单样式查询继续 (Sec 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模板可以在多个Internet应用程序和Internet消息字段中一致使用,同时保持与这些早期定义的兼容性。

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

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

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

1.4. Limitations (限制)

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

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

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

1.5. Notational Conventions (符号约定)

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

本规范使用[RFC5234]的增强巴科斯-瑙尔范式(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模板中的字符串才需要作为Unicode代码点序列处理。

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

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