4. Working with Structured Fields in HTTP (在HTTP中使用结构化字段)
本节定义如何在文本HTTP字段值和与其兼容的其他编码(例如,在使用HPACK [RFC7541]压缩之前的HTTP/2 [RFC7540]中)中序列化和解析结构化字段。
4.1 Serializing Structured Fields (序列化结构化字段)
给定本规范中定义的结构,返回适合在HTTP字段值中使用的ASCII字符串。
主算法
-
如果结构是Dictionary或List且其值为空(即没有成员),则根本不序列化该字段(即省略field-name和field-value)。
-
如果结构是List,令output_string为运行"序列化List"(第4.1.1节)的结果。
-
否则,如果结构是Dictionary,令output_string为运行"序列化Dictionary"(第4.1.2节)的结果。
-
否则,如果结构是Item,令output_string为运行"序列化Item"(第4.1.3节)的结果。
-
否则,序列化失败。
-
使用ASCII编码[RFC0020]将output_string转换为字节数组并返回。
4.1.1 Serializing a List (序列化List)
给定(member_value, parameters)元组数组作为input_list,返回适合在HTTP字段值中使用的ASCII字符串。
算法
-
令output为空字符串。
-
对于input_list中的每个(member_value, parameters):
- 如果member_value是数组,将运行"序列化Inner List"(第4.1.1.1节)的结果追加到output
- 否则,将运行"序列化Item"(第4.1.3节)的结果追加到output
- 如果input_list中还有更多member_values:
- 追加","到output
- 追加单个空格(SP)到output
-
返回output。
示例
输入: [(1, {}), (2, {}), (3, {})]
输出: "1, 2, 3"
输入: [("foo", {}), ("bar", {a: 1})]
输出: "foo, bar;a=1"
4.1.1.1 Serializing an Inner List (序列化Inner List)
给定(member_value, parameters)元组数组作为inner_list和参数作为list_parameters,返回ASCII字符串。
算法
-
令output为字符串"("。
-
对于inner_list中的每个(member_value, parameters):
- 追加运行"序列化Item"的结果到output
- 如果inner_list中还有更多值,追加单个空格到output
-
追加")"到output。
-
追加运行"序列化Parameters"(第4.1.1.2节)的结果到output。
-
返回output。
示例
输入: [(1, {}), (2, {})] with parameters {lvl: 5}
输出: "(1 2);lvl=5"
4.1.1.2 Serializing Parameters (序列化Parameters)
给定有序Dictionary作为input_parameters(每个成员有param_key和param_value),返回ASCII字符串。
算法
-
令output为空字符串。
-
对于input_parameters中每个值为param_value的param_key:
- 追加";"到output
- 追加运行"序列化Key"(第4.1.1.3节)的结果到output
- 如果param_value不是Boolean true:
- 追加"="到output
- 追加运行"序列化bare Item"的结果到output
-
返回output。
示例
输入: {a: 1, b: true, c: "value"}
输出: ";a=1;b;c=\"value\""
4.1.1.3 Serializing a Key (序列化Key)
给定key作为input_key,返回ASCII字符串。
算法
-
将input_key转换为ASCII字符序列;如果转换失败,序列化失败。
-
如果input_key包含不在lcalpha、DIGIT、"_"、"-"、"."或"*"中的字符,序列化失败。
-
如果input_key的第一个字符不是lcalpha或"*",序列化失败。
-
返回input_key。
有效Key示例
✓ "foo"
✓ "foo_bar"
✓ "foo-bar"
✓ "*star"
✗ "Foo" (大写)
✗ "123foo" (数字开头)
4.1.2 Serializing a Dictionary (序列化Dictionary)
给定有序Dictionary作为input_dictionary(每个成员有member_key和元组值(member_value, parameters)),返回ASCII字符串。
算法
-
令output为空字符串。
-
对于input_dictionary中每个值为(member_value, parameters)的member_key:
- 追加运行"序列化Key"的结果到output
- 如果member_value是Boolean true:
- 追加运行"序列化Parameters"的结果到output
- 否则:
- 追加"="到output
- 如果member_value是数组,追加运行"序列化Inner List"的结果
- 否则,追加运行"序列化Item"的结果
- 如果input_dictionary中还有更多成员:
- 追加","到output
- 追加单个空格到output
-
返回output。
示例
输入: {a: 1, b: true, c: (2, 3)}
输出: "a=1, b, c=(2 3)"
4.1.3 Serializing an Item (序列化Item)
给定Item作为bare_item和Parameters作为item_parameters,返回ASCII字符串。
算法
- 令output为空字符串。
- 追加运行"序列化Bare Item"(第4.1.3.1节)的结果到output。
- 追加运行"序列化Parameters"的结果到output。
- 返回output。
4.1.3.1 Serializing a Bare Item (序列化Bare Item)
给定Item作为input_item,根据其类型调用相应的序列化函数:
- Integer → 第4.1.4节
- Decimal → 第4.1.5节
- String → 第4.1.6节
- Token → 第4.1.7节
- Byte Sequence → 第4.1.8节
- Boolean → 第4.1.9节
4.1.4 Serializing an Integer (序列化Integer)
算法
- 如果input_integer不在范围-999,999,999,999,999到999,999,999,999,999(包括端点)内,序列化失败。
- 如果input_integer < 0,追加"-"。
- 追加input_integer的十进制表示。
示例
42 → "42"
-100 → "-100"
0 → "0"
4.1.5 Serializing a Decimal (序列化Decimal)
算法
- 如果不是小数,序列化失败。
- 如果小数点右侧有超过3位有效数字,四舍五入到3位。
- 如果小数点左侧有超过12位有效数字,序列化失败。
- 如果input_decimal < 0,追加"-"。
- 追加整数部分的十进制表示。
- 追加"."。
- 追加小数部分(如果为0则追加"0")。
示例
4.5 → "4.5"
-0.123 → "-0.123"
3.14159 → "3.142" (四舍五入)
4.1.6 Serializing a String (序列化String)
算法
- 转换为ASCII字符序列;如果失败,序列化失败。
- 如果包含%x00-1f或%x7f-ff范围的字符,序列化失败。
- 追加DQUOTE (")。
- 对于每个字符:
- 如果是""或DQUOTE,追加""
- 追加该字符
- 追加DQUOTE。
示例
hello world → "\"hello world\""
say "hi" → "\"say \\\"hi\\\"\""
4.1.7 Serializing a Token (序列化Token)
算法
- 转换为ASCII字符序列;如果失败,序列化失败。
- 验证第一个字符是ALPHA或"*"。
- 验证其余字符在tchar、":"或"/"中。
- 返回input_token。
示例
foo123/456 → "foo123/456"
application/json → "application/json"
* → "*"
4.1.8 Serializing a Byte Sequence (序列化Byte Sequence)
算法
- 如果不是字节序列,序列化失败。
- 追加":"。
- 追加base64编码的input_bytes(根据[RFC4648]第4节)。
- 追加":"。
示例
[0x48, 0x65, 0x6c, 0x6c, 0x6f] → ":SGVsbG8=:"
4.1.9 Serializing a Boolean (序列化Boolean)
算法
- 如果不是boolean,序列化失败。
- 追加"?"。
- 如果true,追加"1";如果false,追加"0"。
示例
true → "?1"
false → "?0"
4.2 Parsing Structured Fields (解析结构化字段)
当接收实现解析已知为结构化字段的HTTP字段时,务必要小心,因为有许多边界情况可能导致互操作性甚至安全问题。本节指定执行此操作的算法。
主算法
给定表示所选字段的field-value的字节数组input_bytes(如果该字段不存在则为空)和field_type("dictionary"、"list"或"item"之一),返回解析后的头部值。
-
将input_bytes转换为ASCII字符串input_string;如果转换失败,解析失败。
-
丢弃input_string开头的所有SP字符。
-
如果field_type是"list",令output为运行"解析List"(第4.2.1节)的结果。
-
如果field_type是"dictionary",令output为运行"解析Dictionary"(第4.2.2节)的结果。
-
如果field_type是"item",令output为运行"解析Item"(第4.2.3节)的结果。
-
丢弃input_string开头的所有SP字符。
-
如果input_string不为空,解析失败。
-
否则,返回output。
关键原则
- 严格解析: 任何错误都导致整个字段被忽略
- 无容错: 不尝试"修复"格式错误的输入
- 安全优先: 防止注入攻击和歧义
- 字段合并: 解析器必须将同一节(头部或尾部)中不区分大小写匹配字段名称的所有字段行组合成一个逗号分隔的field-value
重要说明
对于Lists和Dictionaries,只要顶级数据结构的各个成员未跨多个头部实例拆分,这就能正确连接该字段的所有行。
如果解析失败(包括调用其他算法时),整个字段值必须 (MUST) 被忽略(即视为该字段在该节中不存在)。这是有意严格的,以提高互操作性和安全性,引用本文档的规范不允许放宽此要求。
4.2.1 Parsing a List (解析List)
给定ASCII字符串input_string,返回(item_or_inner_list, parameters)元组的数组。input_string被修改以移除已解析的值。
算法
-
令members为空数组。
-
当input_string不为空时:
- 将运行"解析Item或Inner List"(第4.2.1.1节)的结果追加到members
- 丢弃input_string开头的所有OWS字符
- 如果input_string为空,返回members
- 消费input_string的第一个字符;如果不是",",解析失败
- 丢弃input_string开头的所有OWS字符
- 如果input_string为空,存在尾随逗号;解析失败
-
未找到结构化数据;返回members(为空)。
4.2.1.1 Parsing an Item or Inner List (解析Item或Inner List)
给定ASCII字符串input_string,返回元组(item_or_inner_list, parameters),其中item_or_inner_list可以是单个bare item或(bare_item, parameters)元组的数组。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符是"(",返回运行"解析Inner List"(第4.2.1.2节)的结果。
-
返回运行"解析Item"(第4.2.3节)的结果。
4.2.1.2 Parsing an Inner List (解析Inner List)
给定ASCII字符串input_string,返回元组(inner_list, parameters),其中inner_list是(bare_item, parameters)元组的数组。input_string被修改以移除已解析的值。
算法
-
消费input_string的第一个字符;如果不是"(",解析失败。
-
令inner_list为空数组。
-
当input_string不为空时:
- 丢弃input_string开头的所有SP字符
- 如果input_string的第一个字符是")":
- 消费input_string的第一个字符
- 令parameters为运行"解析Parameters"(第4.2.3.2节)的结果
- 返回元组(inner_list, parameters)
- 令item为运行"解析Item"(第4.2.3节)的结果
- 将item追加到inner_list
- 如果input_string的第一个字符不是SP或")",解析失败
-
未找到Inner List的结尾;解析失败。
4.2.2 Parsing a Dictionary (解析Dictionary)
给定ASCII字符串input_string,返回值为(item_or_inner_list, parameters)元组的有序映射。input_string被修改以移除已解析的值。
算法
-
令dictionary为空的有序映射。
-
当input_string不为空时:
- 令this_key为运行"解析Key"(第4.2.3.3节)的结果
- 如果input_string的第一个字符是"=":
- 消费input_string的第一个字符
- 令member为运行"解析Item或Inner List"(第4.2.1.1节)的结果
- 否则:
- 令value为Boolean true
- 令parameters为运行"解析Parameters"(第4.2.3.2节)的结果
- 令member为元组(value, parameters)
- 如果dictionary已包含键this_key(逐字符比较),用member覆盖其值
- 否则,将键this_key及其值member追加到dictionary
- 丢弃input_string开头的所有OWS字符
- 如果input_string为空,返回dictionary
- 消费input_string的第一个字符;如果不是",",解析失败
- 丢弃input_string开头的所有OWS字符
- 如果input_string为空,存在尾随逗号;解析失败
-
未找到结构化数据;返回dictionary(为空)。
注意: 当遇到重复的Dictionary键时,除最后一个实例外的所有实例都被忽略。
4.2.3 Parsing an Item (解析Item)
给定ASCII字符串input_string,返回(bare_item, parameters)元组。input_string被修改以移除已解析的值。
算法
-
令bare_item为运行"解析Bare Item"(第4.2.3.1节)的结果。
-
令parameters为运行"解析Parameters"(第4.2.3.2节)的结果。
-
返回元组(bare_item, parameters)。
4.2.3.1 Parsing a Bare Item (解析Bare Item)
给定ASCII字符串input_string,返回bare Item。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符是"-"或DIGIT,返回运行"解析Integer或Decimal"(第4.2.4节)的结果。
-
如果input_string的第一个字符是DQUOTE,返回运行"解析String"(第4.2.5节)的结果。
-
如果input_string的第一个字符是ALPHA或"*",返回运行"解析Token"(第4.2.6节)的结果。
-
如果input_string的第一个字符是":",返回运行"解析Byte Sequence"(第4.2.7节)的结果。
-
如果input_string的第一个字符是"?",返回运行"解析Boolean"(第4.2.8节)的结果。
-
否则,item类型无法识别;解析失败。
4.2.3.2 Parsing Parameters (解析Parameters)
给定ASCII字符串input_string,返回值为bare Items的有序映射。input_string被修改以移除已解析的值。
算法
-
令parameters为空的有序映射。
-
当input_string不为空时:
- 如果input_string的第一个字符不是";",退出循环
- 消费input_string开头的";"字符
- 丢弃input_string开头的所有SP字符
- 令param_key为运行"解析Key"(第4.2.3.3节)的结果
- 令param_value为Boolean true
- 如果input_string的第一个字符是"=":
- 消费input_string开头的"="字符
- 令param_value为运行"解析Bare Item"(第4.2.3.1节)的结果
- 如果parameters已包含键param_key(逐字符比较),用param_value覆盖其值
- 否则,将键param_key及其值param_value追加到parameters
-
返回parameters。
注意: 当遇到重复的参数键时,除最后一个实例外的所有实例都被忽略。
4.2.3.3 Parsing a Key (解析Key)
给定ASCII字符串input_string,返回key。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符不是lcalpha或"*",解析失败。
-
令output_string为空字符串。
-
当input_string不为空时:
- 如果input_string的第一个字符不是lcalpha、DIGIT、"_"、"-"、"."或"*"之一,返回output_string
- 令char为消费input_string第一个字符的结果
- 将char追加到output_string
-
返回output_string。
4.2.4 Parsing an Integer or Decimal (解析Integer或Decimal)
给定ASCII字符串input_string,返回Integer或Decimal。input_string被修改以移除已解析的值。
注意: 此算法同时解析Integers(第3.3.1节)和Decimals(第3.3.2节),并返回相应的结构。
算法
-
令type为"integer"。
-
令sign为1。
-
令input_number为空字符串。
-
如果input_string的第一个字符是"-",消费它并将sign设为-1。
-
如果input_string为空,存在空整数;解析失败。
-
如果input_string的第一个字符不是DIGIT,解析失败。
-
当input_string不为空时:
- 令char为消费input_string第一个字符的结果
- 如果char是DIGIT,将其追加到input_number
- 否则,如果type是"integer"且char是".":
- 如果input_number包含超过12个字符,解析失败
- 否则,将char追加到input_number并将type设为"decimal"
- 否则,将char前置到input_string,并退出循环
- 如果type是"integer"且input_number包含超过15个字符,解析失败
- 如果type是"decimal"且input_number包含超过16个字符,解析失败
-
如果type是"integer":
- 将input_number解析为整数,令output_number为结果与sign的乘积
-
否则:
- 如果input_number的最后一个字符是".",解析失败
- 如果input_number中"."之后的字符数大于3,解析失败
- 将input_number解析为十进制数,令output_number为结果与sign的乘积
-
返回output_number。
4.2.5 Parsing a String (解析String)
给定ASCII字符串input_string,返回未加引号的String。input_string被修改以移除已解析的值。
算法
-
令output_string为空字符串。
-
如果input_string的第一个字符不是DQUOTE,解析失败。
-
丢弃input_string的第一个字符。
-
当input_string不为空时:
- 令char为消费input_string第一个字符的结果
- 如果char是反斜杠("\"):
- 如果input_string现在为空,解析失败
- 令next_char为消费input_string第一个字符的结果
- 如果next_char不是DQUOTE或"\",解析失败
- 将next_char追加到output_string
- 否则,如果char是DQUOTE,返回output_string
- 否则,如果char在范围%x00-1f或%x7f-ff内(即不在VCHAR或SP中),解析失败
- 否则,将char追加到output_string
-
到达input_string末尾而未找到结束DQUOTE;解析失败。
4.2.6 Parsing a Token (解析Token)
给定ASCII字符串input_string,返回Token。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符不是ALPHA或"*",解析失败。
-
令output_string为空字符串。
-
当input_string不为空时:
- 如果input_string的第一个字符不在tchar、":"或"/"中,返回output_string
- 令char为消费input_string第一个字符的结果
- 将char追加到output_string
-
返回output_string。
4.2.7 Parsing a Byte Sequence (解析Byte Sequence)
给定ASCII字符串input_string,返回Byte Sequence。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符不是":",解析失败。
-
丢弃input_string的第一个字符。
-
如果在input_string末尾之前没有":"字符,解析失败。
-
令b64_content为消费input_string内容直到但不包括第一个":"字符实例的结果。
-
消费input_string开头的":"字符。
-
如果b64_content包含不在ALPHA、DIGIT、"+"、"/"和"="中的字符,解析失败。
-
令binary_content为base64解码[RFC4648] b64_content的结果,必要时合成填充(注意下面关于接收方行为的要求)。如果base64解码失败,解析失败。
-
返回binary_content。
实现注意事项:
- 由于某些base64实现不允许拒绝未正确填充"="的编码数据,解析器应该 (SHOULD NOT) 在"="填充不存在时失败,除非无法配置为这样做。
- 由于某些base64实现不允许拒绝具有非零填充位的编码数据,解析器应该 (SHOULD NOT) 在存在非零填充位时失败,除非无法配置为这样做。
- 解析器必须 (MUST) 在base64字母表之外的字符和编码数据中的换行符上失败。
4.2.8 Parsing a Boolean (解析Boolean)
给定ASCII字符串input_string,返回Boolean。input_string被修改以移除已解析的值。
算法
-
如果input_string的第一个字符不是"?",解析失败。
-
丢弃input_string的第一个字符。
-
如果input_string的第一个字符匹配"1",丢弃第一个字符,并返回true。
-
如果input_string的第一个字符匹配"0",丢弃第一个字符,并返回false。
-
无值匹配;解析失败。
完整解析示例
List解析示例
// 输入字符串
"42;a=1, foo, (1 2);b"
// 解析结果
[
(42, {a: 1}),
("foo", {}),
([1, 2], {b: true})
]
Dictionary解析示例
// 输入字符串
"key1=42;a=1, key2, key3=(1 2)"
// 解析结果
{
key1: (42, {a: 1}),
key2: (true, {}),
key3: ([1, 2], {})
}
Item解析示例
// 输入字符串
"42;foo=\"bar\";flag"
// 解析结果
(42, {foo: "bar", flag: true})
完整序列化示例
List示例
// 输入
[
(42, {a: 1}),
("foo", {}),
([1, 2], {b: true})
]
// 输出
"42;a=1, foo, (1 2);b"
Dictionary示例
// 输入
{
key1: (42, {a: 1}),
key2: (true, {}),
key3: ([1, 2], {})
}
// 输出
"key1=42;a=1, key2, key3=(1 2)"
Item示例
// 输入
(42, {foo: "bar", flag: true})
// 输出
"42;foo=\"bar\";flag"
关键要点 (Key Takeaways)
序列化
- 空值处理: 空List和Dictionary不序列化(省略整个字段)
- Boolean简写: true值省略"=value"部分
- 严格格式: 每种类型有精确的格式要求
- 失败即停: 任何验证失败立即使整个序列化失败
实现建议
// 伪代码示例
function serializeStructuredField(field) {
if (isEmpty(field)) {
return null; // 不序列化
}
try {
if (field.type === 'list') {
return serializeList(field.value);
} else if (field.type === 'dictionary') {
return serializeDictionary(field.value);
} else if (field.type === 'item') {
return serializeItem(field.value, field.parameters);
}
} catch (error) {
// 序列化失败,返回错误
throw new SerializationError(error);
}
}
下一节预告
第4.2节(解析算法)同样重要,定义了如何将HTTP字段值解析回结构化数据。解析算法确保了严格的验证和一致的错误处理。
注意: 由于解析算法非常详细且与序列化对称,实现时应参考完整的RFC 8941规范以确保完全合规。