Passa al contenuto principale

4. Working with Structured Fields in HTTP (在HTTP中使用结构化字段)

本节定义如何在文本HTTP字段值和与其兼容的其他编码(例如,在使用HPACK [RFC7541]压缩之前的HTTP/2 [RFC7540]中)中序列化和解析结构化字段。


4.1 Serializing Structured Fields (序列化结构化字段)

给定本规范中定义的结构,返回适合在HTTP字段值中使用的ASCII字符串。

主算法

  1. 如果结构是Dictionary或List且其值为空(即没有成员),则根本不序列化该字段(即省略field-name和field-value)。

  2. 如果结构是List,令output_string为运行"序列化List"(第4.1.1节)的结果。

  3. 否则,如果结构是Dictionary,令output_string为运行"序列化Dictionary"(第4.1.2节)的结果。

  4. 否则,如果结构是Item,令output_string为运行"序列化Item"(第4.1.3节)的结果。

  5. 否则,序列化失败。

  6. 使用ASCII编码[RFC0020]将output_string转换为字节数组并返回。


4.1.1 Serializing a List (序列化List)

给定(member_value, parameters)元组数组作为input_list,返回适合在HTTP字段值中使用的ASCII字符串。

算法

  1. 令output为空字符串。

  2. 对于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
  3. 返回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字符串。

算法

  1. 令output为字符串"("。

  2. 对于inner_list中的每个(member_value, parameters):

    • 追加运行"序列化Item"的结果到output
    • 如果inner_list中还有更多值,追加单个空格到output
  3. 追加")"到output。

  4. 追加运行"序列化Parameters"(第4.1.1.2节)的结果到output。

  5. 返回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字符串。

算法

  1. 令output为空字符串。

  2. 对于input_parameters中每个值为param_value的param_key:

    • 追加";"到output
    • 追加运行"序列化Key"(第4.1.1.3节)的结果到output
    • 如果param_value不是Boolean true:
      • 追加"="到output
      • 追加运行"序列化bare Item"的结果到output
  3. 返回output。

示例

输入: {a: 1, b: true, c: "value"}
输出: ";a=1;b;c=\"value\""

4.1.1.3 Serializing a Key (序列化Key)

给定key作为input_key,返回ASCII字符串。

算法

  1. 将input_key转换为ASCII字符序列;如果转换失败,序列化失败。

  2. 如果input_key包含不在lcalpha、DIGIT、"_"、"-"、"."或"*"中的字符,序列化失败。

  3. 如果input_key的第一个字符不是lcalpha或"*",序列化失败。

  4. 返回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字符串。

算法

  1. 令output为空字符串。

  2. 对于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
  3. 返回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字符串。

算法

  1. 令output为空字符串。
  2. 追加运行"序列化Bare Item"(第4.1.3.1节)的结果到output。
  3. 追加运行"序列化Parameters"的结果到output。
  4. 返回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)

算法

  1. 如果input_integer不在范围-999,999,999,999,999到999,999,999,999,999(包括端点)内,序列化失败。
  2. 如果input_integer < 0,追加"-"。
  3. 追加input_integer的十进制表示。

示例

42      → "42"
-100 → "-100"
0 → "0"

4.1.5 Serializing a Decimal (序列化Decimal)

算法

  1. 如果不是小数,序列化失败。
  2. 如果小数点右侧有超过3位有效数字,四舍五入到3位。
  3. 如果小数点左侧有超过12位有效数字,序列化失败。
  4. 如果input_decimal < 0,追加"-"。
  5. 追加整数部分的十进制表示。
  6. 追加"."。
  7. 追加小数部分(如果为0则追加"0")。

示例

4.5       → "4.5"
-0.123 → "-0.123"
3.14159 → "3.142" (四舍五入)

4.1.6 Serializing a String (序列化String)

算法

  1. 转换为ASCII字符序列;如果失败,序列化失败。
  2. 如果包含%x00-1f或%x7f-ff范围的字符,序列化失败。
  3. 追加DQUOTE (")。
  4. 对于每个字符:
    • 如果是""或DQUOTE,追加""
    • 追加该字符
  5. 追加DQUOTE。

示例

hello world   → "\"hello world\""
say "hi" → "\"say \\\"hi\\\"\""

4.1.7 Serializing a Token (序列化Token)

算法

  1. 转换为ASCII字符序列;如果失败,序列化失败。
  2. 验证第一个字符是ALPHA或"*"。
  3. 验证其余字符在tchar、":"或"/"中。
  4. 返回input_token。

示例

foo123/456     → "foo123/456"
application/json → "application/json"
* → "*"

4.1.8 Serializing a Byte Sequence (序列化Byte Sequence)

算法

  1. 如果不是字节序列,序列化失败。
  2. 追加":"。
  3. 追加base64编码的input_bytes(根据[RFC4648]第4节)。
  4. 追加":"。

示例

[0x48, 0x65, 0x6c, 0x6c, 0x6f]  → ":SGVsbG8=:"

4.1.9 Serializing a Boolean (序列化Boolean)

算法

  1. 如果不是boolean,序列化失败。
  2. 追加"?"。
  3. 如果true,追加"1";如果false,追加"0"。

示例

true   → "?1"
false → "?0"

4.2 Parsing Structured Fields (解析结构化字段)

当接收实现解析已知为结构化字段的HTTP字段时,务必要小心,因为有许多边界情况可能导致互操作性甚至安全问题。本节指定执行此操作的算法。

主算法

给定表示所选字段的field-value的字节数组input_bytes(如果该字段不存在则为空)和field_type("dictionary"、"list"或"item"之一),返回解析后的头部值。

  1. 将input_bytes转换为ASCII字符串input_string;如果转换失败,解析失败。

  2. 丢弃input_string开头的所有SP字符。

  3. 如果field_type是"list",令output为运行"解析List"(第4.2.1节)的结果。

  4. 如果field_type是"dictionary",令output为运行"解析Dictionary"(第4.2.2节)的结果。

  5. 如果field_type是"item",令output为运行"解析Item"(第4.2.3节)的结果。

  6. 丢弃input_string开头的所有SP字符。

  7. 如果input_string不为空,解析失败。

  8. 否则,返回output。

关键原则

  • 严格解析: 任何错误都导致整个字段被忽略
  • 无容错: 不尝试"修复"格式错误的输入
  • 安全优先: 防止注入攻击和歧义
  • 字段合并: 解析器必须将同一节(头部或尾部)中不区分大小写匹配字段名称的所有字段行组合成一个逗号分隔的field-value

重要说明

对于Lists和Dictionaries,只要顶级数据结构的各个成员未跨多个头部实例拆分,这就能正确连接该字段的所有行。

如果解析失败(包括调用其他算法时),整个字段值必须 (MUST) 被忽略(即视为该字段在该节中不存在)。这是有意严格的,以提高互操作性和安全性,引用本文档的规范不允许放宽此要求。


4.2.1 Parsing a List (解析List)

给定ASCII字符串input_string,返回(item_or_inner_list, parameters)元组的数组。input_string被修改以移除已解析的值。

算法

  1. 令members为空数组。

  2. 当input_string不为空时:

    • 将运行"解析Item或Inner List"(第4.2.1.1节)的结果追加到members
    • 丢弃input_string开头的所有OWS字符
    • 如果input_string为空,返回members
    • 消费input_string的第一个字符;如果不是",",解析失败
    • 丢弃input_string开头的所有OWS字符
    • 如果input_string为空,存在尾随逗号;解析失败
  3. 未找到结构化数据;返回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被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符是"(",返回运行"解析Inner List"(第4.2.1.2节)的结果。

  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被修改以移除已解析的值。

算法

  1. 消费input_string的第一个字符;如果不是"(",解析失败。

  2. 令inner_list为空数组。

  3. 当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或")",解析失败
  4. 未找到Inner List的结尾;解析失败。


4.2.2 Parsing a Dictionary (解析Dictionary)

给定ASCII字符串input_string,返回值为(item_or_inner_list, parameters)元组的有序映射。input_string被修改以移除已解析的值。

算法

  1. 令dictionary为空的有序映射。

  2. 当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为空,存在尾随逗号;解析失败
  3. 未找到结构化数据;返回dictionary(为空)。

注意: 当遇到重复的Dictionary键时,除最后一个实例外的所有实例都被忽略。


4.2.3 Parsing an Item (解析Item)

给定ASCII字符串input_string,返回(bare_item, parameters)元组。input_string被修改以移除已解析的值。

算法

  1. 令bare_item为运行"解析Bare Item"(第4.2.3.1节)的结果。

  2. 令parameters为运行"解析Parameters"(第4.2.3.2节)的结果。

  3. 返回元组(bare_item, parameters)。


4.2.3.1 Parsing a Bare Item (解析Bare Item)

给定ASCII字符串input_string,返回bare Item。input_string被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符是"-"或DIGIT,返回运行"解析Integer或Decimal"(第4.2.4节)的结果。

  2. 如果input_string的第一个字符是DQUOTE,返回运行"解析String"(第4.2.5节)的结果。

  3. 如果input_string的第一个字符是ALPHA或"*",返回运行"解析Token"(第4.2.6节)的结果。

  4. 如果input_string的第一个字符是":",返回运行"解析Byte Sequence"(第4.2.7节)的结果。

  5. 如果input_string的第一个字符是"?",返回运行"解析Boolean"(第4.2.8节)的结果。

  6. 否则,item类型无法识别;解析失败。


4.2.3.2 Parsing Parameters (解析Parameters)

给定ASCII字符串input_string,返回值为bare Items的有序映射。input_string被修改以移除已解析的值。

算法

  1. 令parameters为空的有序映射。

  2. 当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
  3. 返回parameters。

注意: 当遇到重复的参数键时,除最后一个实例外的所有实例都被忽略。


4.2.3.3 Parsing a Key (解析Key)

给定ASCII字符串input_string,返回key。input_string被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符不是lcalpha或"*",解析失败。

  2. 令output_string为空字符串。

  3. 当input_string不为空时:

    • 如果input_string的第一个字符不是lcalpha、DIGIT、"_"、"-"、"."或"*"之一,返回output_string
    • 令char为消费input_string第一个字符的结果
    • 将char追加到output_string
  4. 返回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节),并返回相应的结构。

算法

  1. 令type为"integer"。

  2. 令sign为1。

  3. 令input_number为空字符串。

  4. 如果input_string的第一个字符是"-",消费它并将sign设为-1。

  5. 如果input_string为空,存在空整数;解析失败。

  6. 如果input_string的第一个字符不是DIGIT,解析失败。

  7. 当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个字符,解析失败
  8. 如果type是"integer":

    • 将input_number解析为整数,令output_number为结果与sign的乘积
  9. 否则:

    • 如果input_number的最后一个字符是".",解析失败
    • 如果input_number中"."之后的字符数大于3,解析失败
    • 将input_number解析为十进制数,令output_number为结果与sign的乘积
  10. 返回output_number。


4.2.5 Parsing a String (解析String)

给定ASCII字符串input_string,返回未加引号的String。input_string被修改以移除已解析的值。

算法

  1. 令output_string为空字符串。

  2. 如果input_string的第一个字符不是DQUOTE,解析失败。

  3. 丢弃input_string的第一个字符。

  4. 当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
  5. 到达input_string末尾而未找到结束DQUOTE;解析失败。


4.2.6 Parsing a Token (解析Token)

给定ASCII字符串input_string,返回Token。input_string被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符不是ALPHA或"*",解析失败。

  2. 令output_string为空字符串。

  3. 当input_string不为空时:

    • 如果input_string的第一个字符不在tchar、":"或"/"中,返回output_string
    • 令char为消费input_string第一个字符的结果
    • 将char追加到output_string
  4. 返回output_string。


4.2.7 Parsing a Byte Sequence (解析Byte Sequence)

给定ASCII字符串input_string,返回Byte Sequence。input_string被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符不是":",解析失败。

  2. 丢弃input_string的第一个字符。

  3. 如果在input_string末尾之前没有":"字符,解析失败。

  4. 令b64_content为消费input_string内容直到但不包括第一个":"字符实例的结果。

  5. 消费input_string开头的":"字符。

  6. 如果b64_content包含不在ALPHA、DIGIT、"+"、"/"和"="中的字符,解析失败。

  7. 令binary_content为base64解码[RFC4648] b64_content的结果,必要时合成填充(注意下面关于接收方行为的要求)。如果base64解码失败,解析失败。

  8. 返回binary_content。

实现注意事项:

  • 由于某些base64实现不允许拒绝未正确填充"="的编码数据,解析器应该 (SHOULD NOT) 在"="填充不存在时失败,除非无法配置为这样做。
  • 由于某些base64实现不允许拒绝具有非零填充位的编码数据,解析器应该 (SHOULD NOT) 在存在非零填充位时失败,除非无法配置为这样做。
  • 解析器必须 (MUST) 在base64字母表之外的字符和编码数据中的换行符上失败。

4.2.8 Parsing a Boolean (解析Boolean)

给定ASCII字符串input_string,返回Boolean。input_string被修改以移除已解析的值。

算法

  1. 如果input_string的第一个字符不是"?",解析失败。

  2. 丢弃input_string的第一个字符。

  3. 如果input_string的第一个字符匹配"1",丢弃第一个字符,并返回true。

  4. 如果input_string的第一个字符匹配"0",丢弃第一个字符,并返回false。

  5. 无值匹配;解析失败。


完整解析示例

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)

序列化

  1. 空值处理: 空List和Dictionary不序列化(省略整个字段)
  2. Boolean简写: true值省略"=value"部分
  3. 严格格式: 每种类型有精确的格式要求
  4. 失败即停: 任何验证失败立即使整个序列化失败

实现建议

// 伪代码示例
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规范以确保完全合规。