跳到主要内容

5. 字段

HTTP 使用 "字段" (fields) 以可扩展名称/值对的形式提供数据, 这些名称/值对具有已注册的键命名空间. 字段在消息 (Section 6) 的头部区段和尾部区段中发送和接收.

5.1. 字段名​

字段名把对应字段值标记为具有该名称所定义的语义. 例如, Date 头字段在 Section 6.6.1 中被定义为包含其所在消息的发起时间戳.

field-name     = token

字段名不区分大小写, 并且应注册到 "Hypertext Transfer Protocol (HTTP) Field Name Registry"; 见 Section 16.3.1.

字段的解释不会在同一 HTTP 主版本的次版本之间改变, 不过当缺少该字段时, 接收方的默认行为可以改变. 除非另有规定, 字段为所有 HTTP 版本定义. 特别是, 无论是否宣告符合 HTTP/1.1, 所有 HTTP 实现都应识别 Host 和 Connection 字段.

如果新字段定义的语义允许不识别它们的接收方安全地忽略它们, 则可以在不改变协议版本的情况下引入新字段; 见 Section 16.3.

代理必须转发未识别的头字段, 除非该字段名列在 Connection 头字段 (Section 7.6.1) 中, 或者该代理被专门配置为阻止或以其他方式转换此类字段. 其他接收方应忽略未识别的头字段和尾字段. 遵守这些要求使 HTTP 功能能够扩展, 而无需更新或移除已部署的中介.

5.2. 字段行和组合字段值​

字段区段由任意数量的 "字段行" (field lines) 组成, 每个字段行都有一个用于标识字段的 "字段名" (见 Section 5.1), 以及一个为该字段实例传达数据的 "字段行值" (field line value).

当某个字段名在一个区段中只出现一次时, 该字段的组合 "字段值" (field value) 就由对应的字段行值组成. 当某个字段名在一个区段中重复出现时, 其组合字段值由该区段内对应字段行值按顺序连接而成的列表组成, 每个字段行值之间以逗号分隔.

例如, 此区段:

Example-Field: Foo, Bar
Example-Field: Baz

包含两个字段行, 二者字段名均为 "Example-Field".第一个字段行的字段行值为 "Foo, Bar", 第二个字段行值为 "Baz"."Example-Field" 的字段值是列表 "Foo, Bar, Baz".

5.3. 字段顺序​

接收方可以把字段区段内具有相同字段名的多个字段行合并为一个字段行, 而不改变消息语义; 方法是把每个后续字段行值按顺序追加到初始字段行值之后, 中间以逗号 (",") 和可选空白 (OWS, 定义于 Section 5.6.3) 分隔. 为保持一致性, 使用 comma SP.

因此, 具有相同名称的字段行的接收顺序对字段值解释是有意义的; 代理在转发消息时不得改变这些字段行值的顺序.

这意味着, 除了下文提到的众所周知例外, 发送方不得在消息 (无论头部还是尾部) 中生成多个同名字段行, 也不得在消息中已经存在同名字段行时追加字段行, 除非该字段定义允许把多个字段行值重新组合为逗号分隔列表 (即字段定义中至少有一个替代形式允许逗号分隔列表, 例如 Section 5.6.1 中定义的 #(values) ABNF 规则).

Note: 实践中, "Set-Cookie" 头字段 ([COOKIE]) 经常在响应消息中跨多个字段行出现, 且不使用列表语法, 违反了上述关于同名多个字段行的要求. 由于它不能被合并为单个字段值, 接收方在处理字段时应将 "Set-Cookie" 作为特殊情况处理.(详情见 [Kri2001] Appendix A.2.3.)

一个区段中不同字段名的字段行的接收顺序没有意义. 不过, 最佳实践是先发送包含额外控制数据的头字段, 如请求中的 Host 和响应中的 Date, 以便实现尽早决定何时不处理消息.

服务器在收到完整请求头部区段之前, 不得把请求应用于目标资源, 因为后续头字段行可能包含条件, 认证凭据或故意误导的重复头字段, 从而影响请求处理.

5.4. 字段限制​

如 Section 2 所述, HTTP 不对每个字段行, 字段值或整个头部区段/尾部区段的长度预先设置限制. 实践中可以看到针对单个长度的各种临时限制, 通常取决于具体字段的语义.

服务器如果收到大于其愿意处理范围的请求头字段行, 字段值或字段集合, 必须以适当的 4xx (Client Error) 状态码响应. 忽略此类头字段会增加服务器遭受请求走私攻击 (Section 11.2 of [HTTP/1.1]) 的脆弱性.

如果字段语义允许安全地忽略被丢弃的值而不改变消息定帧或响应语义, 客户端可以丢弃或截断其不愿处理的过大字段行.

5.5. 字段值​

HTTP 字段值由一串字符组成, 其格式由该字段的语法定义. 每个字段的语法通常使用 ABNF ([RFC5234]) 定义.

field-value    = *field-content
field-content = field-vchar
[ 1*( SP / HTAB / field-vchar ) field-vchar ]
field-vchar = VCHAR / obs-text
obs-text = %x80-FF

字段值不包含前导或尾随空白. 当某个具体 HTTP 版本允许此类空白出现在消息中时, 字段解析实现必须在评估字段值之前排除此类空白.

字段值通常被限制在 US-ASCII 字符 [USASCII] 范围内. 需要更大字符范围的字段可以使用编码, 如 [RFC8187] 中定义的编码. 历史上, HTTP 曾允许字段内容包含 ISO-8859-1 charset [ISO-8859-1] 中的文本, 而其他 charset 只能通过使用 [RFC2047] 编码来支持. 新定义字段的规范应将其值限制为可见 US-ASCII 八位字节 (VCHAR), SP 和 HTAB. 接收方应把字段内容中其他允许的八位字节 (即 obs-text) 视为不透明数据.

包含 CR, LF 或 NUL 字符的字段值无效且危险, 因为实现可能以不同方式解析和解释这些字符; 字段值中收到 CR, LF 或 NUL 的接收方必须拒绝该消息, 或在进一步处理或转发该消息之前把这些字符分别替换为 SP. 包含其他 CTL 字符的字段值也无效; 然而, 为了健壮性, 当这些字符出现在安全上下文中时 (例如不会被任何下游 HTTP 解析器处理的应用特定 quoted string), 接收方可以保留这些字符.

只预期字段值包含单个成员的字段称为 "单例字段" (singleton fields).

允许字段值包含多个成员的字段称为 "基于列表的字段" (list-based fields). Section 5.6.1 的列表操作符扩展用作定义可包含多个成员的字段值的通用表示法.

由于逗号 (",") 被用作成员之间的分隔符, 如果成员内部允许逗号作为数据, 则需要谨慎处理. 这对基于列表的字段和单例字段都成立, 因为单例字段可能被错误地发送为多个成员, 检测此类错误有助于提高互操作性. 预期在成员内部包含逗号的字段, 例如 HTTP date 或 URI-reference 元素, 应围绕该元素定义定界符, 以区分该数据内部的逗号和潜在列表分隔符.

例如, 文本日期和 URI (二者都可能包含逗号) 可以像下面这样安全地承载在基于列表的字段值中:

Example-URIs: "http://example.com/a.html,foo",
"http://without-a-comma.example.com/"
Example-Dates: "Sat, 04 May 1996", "Wed, 14 Sep 2005"

注意, 双引号定界符几乎总是与 quoted-string 产生式 (Section 5.6.4) 一起使用; 在双引号内部使用不同语法很可能造成不必要的混淆.

许多字段 (例如 Section 8.3 中定义的 Content-Type) 对参数使用通用语法, 该语法允许参数值 (Section 5.6.6) 同时采用不加引号 (token) 和加引号 (quoted-string) 的形式. 使用通用语法允许接收方复用现有解析器组件. 当允许两种形式时, 参数值的含义应独立于传达该值所使用的形式.

Note: 为定义字段值语法, 本规范使用以字段名命名的 ABNF 规则来定义该字段值允许的语法 (在从底层消息语法中提取字段值并将多个实例组合为列表之后).

5.6. 定义字段值的通用规则​

5.6.1. 列表 (#rule ABNF 扩展)​

ABNF 规则 [RFC5234] 的 #rule 扩展用于提高某些基于列表字段值定义的可读性.

结构 "#" 被定义为类似于 "*", 用于定义以逗号分隔的元素列表. 完整形式为 "<n>#<m>element", 表示至少 <n> 个且至多 <m> 个元素, 每个元素之间以单个逗号 (",") 和可选空白 (OWS, 定义于 Section 5.6.3) 分隔.

5.6.1.1. 发送方要求​

在任何使用列表结构的产生式中, 发送方不得生成空列表元素. 换言之, 发送方必须生成满足以下语法的列表:

1#element => element *( OWS "," OWS element )

以及:

#element => [ 1#element ]

并且当 n >= 1 且 m > 1 时:

<n>#<m>element => element `<n-1>`*`<m-1>`( OWS "," OWS element )

Appendix A 展示了列表结构展开后发送方使用的汇总 ABNF.

5.6.1.2. 接收方要求​

空元素不计入存在元素的数量. 接收方必须解析并忽略合理数量的空列表元素: 该数量应足以处理发送方合并值时的常见错误, 但不应多到可被用作拒绝服务机制. 换言之, 接收方必须接受满足以下语法的列表:

#element => [ element ] *( OWS "," OWS [ element ] )

注意, 由于可能存在空列表元素, RFC 5234 ABNF 无法强制列表元素基数, 因此所有情况都被映射为好像没有指定基数一样.

例如, 给定这些 ABNF 产生式:

example-list      = 1#example-list-elmt
example-list-elmt = token ; see Section 5.6.2

则以下是 example-list 的有效值 (不包括双引号, 双引号仅用于定界):

"foo,bar"
"foo ,bar,"
"foo , ,bar,charlie"

相比之下, 以下值无效, 因为 example-list 产生式要求至少一个非空元素:

""
","
", ,"

5.6.2. 令牌​

令牌 (tokens) 是不包含空白或定界符的短文本标识符.

token          = 1*tchar

tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/ "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/ DIGIT / ALPHA
; any VCHAR, except delimiters

许多 HTTP 字段值使用通用语法组件定义, 这些组件由空白或特定定界字符分隔. 定界符从不允许出现在 token 中的 US-ASCII 可视字符集合中选择 (DQUOTE 和 "(),/:;<=>?@[\]{}").

5.6.3. 空白​

本规范使用三条规则表示线性空白的使用: OWS (optional whitespace, 可选空白), RWS (required whitespace, 必需空白) 和 BWS ("bad" whitespace, "坏" 空白).

OWS 规则用于可能出现零个或多个线性空白八位字节的位置. 对于为提高可读性而偏好可选空白的协议元素, 发送方应把可选空白生成为单个 SP; 否则, 除非在就地消息过滤期间需要过滤无效或不需要的协议元素, 发送方不应生成可选空白.

RWS 规则用于需要至少一个线性空白八位字节来分隔字段令牌的位置. 发送方应把 RWS 生成为单个 SP.

OWS 和 RWS 规则与单个 SP 具有相同语义. 任何已知定义为 OWS 或 RWS 的内容, 都可以在解释它或向下游转发消息之前替换为单个 SP.

BWS 规则用于语法仅因历史原因允许可选空白的位置. 发送方不得在消息中生成 BWS. 接收方必须解析此类坏空白, 并在解释协议元素之前移除它.

BWS 没有语义. 任何已知定义为 BWS 的内容, 都可以在解释它或向下游转发消息之前移除.

OWS            = *( SP / HTAB )
; optional whitespace
RWS = 1*( SP / HTAB )
; required whitespace
BWS = OWS
; "bad" whitespace

5.6.4. 引号字符串​

如果文本字符串使用双引号括起, 则它被解析为单个值.

quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text

反斜杠八位字节 ("") 可在 quoted-string 和 comment 结构中用作单八位字节引用机制. 处理 quoted-string 值的接收方必须把 quoted-pair 当作被反斜杠后的八位字节替换来处理.

quoted-pair    = "\" ( HTAB / SP / VCHAR / obs-text )

除非有必要引用该字符串中出现的 DQUOTE 和反斜杠八位字节, 发送方不应在 quoted-string 中生成 quoted-pair. 除非有必要引用 comment 中出现的括号 ["(" 和 ")"] 以及反斜杠八位字节, 发送方不应在 comment 中生成 quoted-pair.

5.6.5. 注释​

某些 HTTP 字段可以通过用圆括号包围注释文本来包含注释. 只有在字段值定义中包含 "comment" 的字段中才允许注释.

comment        = "(" *( ctext / quoted-pair / comment ) ")"
ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text

5.6.6. 参数​

参数是名称/值对的实例; 它们常在字段值中用作向某个项追加辅助信息的通用语法. 每个参数通常由紧邻其前的分号定界.

parameters      = *( OWS ";" OWS [ parameter ] )
parameter = parameter-name "=" parameter-value
parameter-name = token
parameter-value = ( token / quoted-string )

参数名不区分大小写. 参数值可能区分大小写, 也可能不区分大小写, 具体取决于参数名的语义. 参数示例及若干等价形式可见于媒体类型 (Section 8.3.1) 和 Accept 头字段 (Section 12.5.1).

匹配 token 产生式的参数值可以作为 token 传输, 也可以在 quoted-string 中传输. 加引号值和未加引号值是等价的.

Note: 参数不允许在 "=" 字符周围出现空白 (甚至不允许 "bad" whitespace).

5.6.7. 日期/时间格式​

1995 年以前, 服务器常用三种不同格式传达时间戳. 为兼容旧实现, 这里定义了三种格式. 首选格式是 Internet Message Format [RFC5322] 所用日期和时间规范的固定长度, 单时区子集.

HTTP-date    = IMF-fixdate / obs-date

首选格式示例为

Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate

两个废弃格式示例为

Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format
Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format

解析 HTTP 字段中时间戳值的接收方必须接受全部三种 HTTP-date 格式. 当发送方生成包含一个或多个定义为 HTTP-date 的时间戳的字段时, 发送方必须以 IMF-fixdate 格式生成这些时间戳.

HTTP-date 值表示 Coordinated Universal Time (UTC) 中的一个时间实例. 前两种格式用 Greenwich Mean Time 的三字母缩写 "GMT" 表示 UTC, GMT 是 UTC 名称的前身; asctime 格式中的值被假定为 UTC.

"时钟" (clock) 是一种能够提供当前 UTC 时刻合理近似值的实现. 时钟实现应使用 NTP ([RFC5905]) 或某种类似协议与 UTC 同步.

首选格式:

IMF-fixdate  = day-name "," SP date1 SP time-of-day SP GMT
; fixed length/zone/capitalization subset of the format
; see Section 3.3 of [RFC5322]

day-name = %s"Mon" / %s"Tue" / %s"Wed"
/ %s"Thu" / %s"Fri" / %s"Sat" / %s"Sun"

date1 = day SP month SP year
; e.g., 02 Jun 1982

day = 2DIGIT
month = %s"Jan" / %s"Feb" / %s"Mar" / %s"Apr"
/ %s"May" / %s"Jun" / %s"Jul" / %s"Aug"
/ %s"Sep" / %s"Oct" / %s"Nov" / %s"Dec"
year = 4DIGIT

GMT = %s"GMT"

time-of-day = hour ":" minute ":" second
; 00:00:00 - 23:59:60 (leap second)

hour = 2DIGIT
minute = 2DIGIT
second = 2DIGIT

废弃格式:

obs-date     = rfc850-date / asctime-date

rfc850-date = day-name-l "," SP date2 SP time-of-day SP GMT
date2 = day "-" month "-" 2DIGIT
; e.g., 02-Jun-82

day-name-l = %s"Monday" / %s"Tuesday" / %s"Wednesday"
/ %s"Thursday" / %s"Friday" / %s"Saturday"
/ %s"Sunday"

asctime-date = day-name SP date3 SP time-of-day SP year
date3 = month SP ( 2DIGIT / ( SP 1DIGIT ))
; e.g., Jun 2

HTTP-date 区分大小写. 注意, [CACHING] Section 4.2 对缓存接收方放宽了这一点.

发送方不得在 HTTP-date 中生成超出语法中明确包含为 SP 的额外空白.day-name, day, month, year 和 time-of-day 的语义与 Internet Message Format 中同名结构定义的语义相同 ([RFC5322], Section 3.3).

rfc850-date 格式使用两位年份, 接收方收到这种格式的时间戳值时, 如果某个时间戳看起来超过未来 50 年, 则必须将其解释为过去最近的, 最后两位数字相同的年份.

除非字段定义另有限制, 鼓励时间戳值接收方以健壮方式解析时间戳. 例如, 消息偶尔会从非 HTTP 来源经 HTTP 转发, 而该来源可能生成 Internet Message Format 所定义的任意日期和时间规范.

Note: HTTP 对日期/时间戳格式的要求仅适用于它们在协议流中的使用. 实现不需要在用户呈现, 请求日志记录等场景中使用这些格式.