3. 表示
考虑到资源可以是任何事物, 而 HTTP 提供的统一接口又像一扇窗口, 我们只能通过向另一侧某个独立参与者通信消息来观察并作用于这样的事物, 因此需要一种抽象, 用于在通信中代表 ("take the place of") 该事物的当前状态或期望状态. 这种抽象称为表示 (representation) [REST].
就 HTTP 而言, "representation" 是旨在反映给定资源过去、当前或期望状态的信息, 其格式可以通过协议方便地通信, 并由一组表示元数据和一个可能无界的表示数据流组成.
源服务器可能被提供多个表示, 或能够生成多个表示, 每个表示都旨在反映目标资源的当前状态. 在这种情况下, 源服务器会使用某种算法从这些表示中选择一个最适用于给定请求的表示, 通常基于内容协商. 这个"选定表示"用于提供数据和元数据, 以评估条件请求 [RFC7232], 并构造对 GET 的 200 (OK) 和 304 (Not Modified) 响应负载 [Section 4.3.1].
3.1. 表示元数据
表示头部字段提供有关表示的元数据. 当消息包含负载体时, 表示头部字段描述如何解释封装在负载体中的表示数据. 在对 HEAD 请求的响应中, 表示头部字段描述如果同一请求是 GET 时本会封装在负载体中的表示数据.
3.1.1. 处理表示数据
表示元数据的目的是让接收者知道它正在处理什么格式、该表示使用什么语言、以及介入的中间方已对表示数据应用了哪些转换.
3.1.1.1. 媒体类型
HTTP 在 Content-Type (Section 3.1.1.5) 和 Accept (Section 5.3.2) 头部字段中使用 Internet 媒体类型 [RFC2046], 以提供开放且可扩展的数据类型化和类型协商. 媒体类型既定义数据格式, 也定义各种处理模型: 即在接收该数据的每个上下文中如何处理该数据.
media-type = type "/" subtype *( OWS ";" OWS parameter )
type = token
subtype = token
type/subtype 后面 MAY 跟随 name=value 对形式的参数.
parameter = token "=" ( token / quoted-string )
type、subtype, 和 parameter name 令牌不区分大小写. 参数值可能区分大小写, 也可能不区分大小写, 这取决于参数名的语义. 参数是否存在可能对 media-type 的处理有意义, 具体取决于其在媒体类型注册表中的定义.
匹配 token 产生式的参数值可以作为 token 传输, 也可以放在 quoted-string 中传输. 带引号和值不带引号的值是等价的. 例如, 以下示例都等价, 但为保持一致, 首选第一个:
text/html;charset=utf-8
text/html;charset=UTF-8
text/HTML;charset="utf-8"
text/html; charset="utf-8"
Internet 媒体类型应按照 [BCP13] 定义的流程向 IANA 注册.
3.1.1.2. Charset
HTTP 使用 charset 名称指示或协商文本表示的字符编码方案 [RFC6365]. charset 由不区分大小写的令牌标识.
charset = token
Charset 名称应按照 [RFC2978] 定义的流程注册到 IANA "Character Sets" 注册表 (http://www.iana.org/assignments/character-sets).
3.1.1.3. 规范化和文本默认值
Internet 媒体类型以规范形式注册, 以便在具有不同原生编码格式的系统之间互操作. 通过 HTTP 选择或传输的表示应采用规范形式, 其原因与 Multipurpose Internet Mail Extensions (MIME) [RFC2045] 中描述的许多原因相同. 不过, 电子邮件部署的性能特征 (即, 存储消息并转发给对等方) 与 HTTP 和 Web 常见的性能特征 (基于服务器的信息服务) 显著不同. 此外, MIME 为兼容较旧邮件传输协议而施加的约束不适用于 HTTP (见 Appendix A).
MIME 的规范形式要求 "text" 类型的媒体子类型使用 CRLF 作为文本换行. 当某类换行在整个表示中保持一致时, HTTP 允许传输仅用普通 CR 或 LF 表示换行的文本媒体. HTTP 发送者 MAY 生成由 CRLF、裸 CR, 或裸 LF 组成的文本媒体换行, 接收者 MUST 能够解析这些换行. 此外, HTTP 中的文本媒体不限于分别使用八位字节 13 和 10 作为 CR 和 LF 的 charset. 关于换行的这种灵活性只适用于被分配了 "text" 媒体类型的表示中的文本; 它不适用于 "multipart" 类型或负载体之外的 HTTP 元素 (例如头部字段).
如果表示使用 content-coding 编码, 则底层数据在编码之前应采用上述定义的形式.
3.1.1.4. Multipart 类型
MIME 提供了多种 "multipart" 类型 -- 即在单个消息体中封装一个或多个表示. 所有 multipart 类型共享通用语法, 如 [RFC2046] Section 5.1.1 所定义, 并把 boundary 参数作为媒体类型值的一部分. 消息体本身是一个协议元素; 发送者 MUST 只生成 CRLF 来表示 body part 之间的换行.
HTTP 消息分帧不使用 multipart boundary 作为消息体长度的指示符, 尽管生成或处理负载的实现可能会使用它. 例如, "multipart/byteranges" 类型由 HTTP 定义, 用于某些 206 (Partial Content) 响应 [RFC7233].
3.1.1.5. Content-Type
Content-Type 头部字段指示关联表示的媒体类型: 该表示要么封装在消息负载中, 要么是由消息语义确定的选定表示. 所指示的媒体类型既定义数据格式, 也定义在接收消息语义范围内, 在解码 Content-Encoding 所指示的任何内容编码之后, 接收者应如何处理这些数据.
Content-Type = media-type
媒体类型定义于 Section 3.1.1.1. 该字段示例如下:
Content-Type: text/html; charset=ISO-8859-4
生成包含负载体的消息的发送者 SHOULD 在该消息中生成 Content-Type 头部字段, 除非发送者不知道所封装表示的预期媒体类型. 如果不存在 Content-Type 头部字段, 接收者 MAY 假定媒体类型为 "application/octet-stream" ([RFC2046] Section 4.5.1), 或检查数据以确定其类型.
实践中, 资源所有者并不总是正确配置其源服务器来为给定表示提供正确的 Content-Type, 结果是一些客户端会检查负载内容并覆盖指定类型. 这样做的客户端有得出错误结论的风险, 这可能暴露额外安全风险 (例如 "privilege escalation"). 此外, 仅通过检查数据格式不可能确定发送者意图: 许多数据格式匹配多个媒体类型, 它们之间只在处理语义上不同. 如果使用这种 "content sniffing", 鼓励实现者提供禁用它的手段.
3.1.2. 用于压缩或完整性的编码
3.1.2.1. 内容编码
内容编码值指示已经或可以应用于表示的编码转换. 内容编码主要用于允许表示被压缩或以其他有用方式转换, 同时不丢失其底层媒体类型的身份, 且不丢失信息. 表示常常以编码形式存储、直接传输, 并且只由接收者解码.
content-coding = token
所有 content-coding 值都不区分大小写, 并应注册到 "HTTP Content Coding Registry" 中, 如 Section 8.4 所定义. 它们用于 Accept-Encoding (Section 5.3.4) 和 Content-Encoding (Section 3.1.2.2) 头部字段.
本规范定义以下 content-coding 值:
-
compress (and x-compress): UNIX "compress" 数据格式 [Welch]. 历史值; 不建议在新应用程序中使用.
-
deflate: "zlib" 格式 [RFC1950] 与 "deflate" 压缩机制 [RFC1951] 的组合.
-
gzip (and x-gzip): 带 32-bit CRC 的 LZ77 编码 [RFC1952].
-
identity: "identity" 编码 (无转换). 此编码只用于
Accept-Encoding头部字段, SHOULD NOT 用于Content-Encoding头部字段.
3.1.2.2. Content-Encoding
Content-Encoding 头部字段指示已对表示应用了哪些内容编码, 这些编码超出媒体类型固有的编码; 因此, 为获得 Content-Type 头部字段所引用媒体类型中的数据, 必须应用相应解码机制. Content-Encoding 主要用于允许压缩表示数据, 同时不丢失其底层媒体类型的身份.
Content-Encoding = 1#content-coding
其使用示例如下:
Content-Encoding: gzip
如果对表示应用了一个或多个编码, 应用这些编码的发送者 MUST 生成 Content-Encoding 头部字段, 按应用顺序列出内容编码. 有关编码参数的附加信息可以由本规范未定义的其他头部字段提供.
不同于 Transfer-Encoding ([RFC7230] Section 3.3), Content-Encoding 中列出的编码是表示的特征; 表示按编码形式定义, 除非元数据定义另有说明, 关于表示的所有其他元数据都关于编码形式. 通常, 表示只会在渲染或类似使用之前才被解码.
如果媒体类型包含固有编码, 例如始终被压缩的数据格式, 那么即使该编码恰好与某个内容编码使用相同算法, 也不会在 Content-Encoding 中重述. 只有在由于某种奇怪原因, 为形成表示而第二次应用该内容编码时, 才会列出它. 同样, 源服务器可以选择把相同数据发布为多个表示, 这些表示只在该编码被定义为 Content-Type 的一部分还是 Content-Encoding 的一部分上不同, 因为一些用户代理在处理每个响应时会表现不同 (例如打开 "Save as ..." 对话框, 而不是自动解压并渲染内容).
如果请求消息中的表示具有不可接受的内容编码, 源服务器 MAY 以 415 (Unsupported Media Type) 状态码响应.
3.1.3. 受众语言
3.1.3.1. 语言标签
语言标签按 [RFC5646] 定义, 标识人类为向其他人传达信息而说、写或以其他方式传达的自然语言. 计算机语言被明确排除.
HTTP 在 Accept-Language 和 Content-Language 头部字段中使用语言标签. Accept-Language 使用 Section 5.3.5 中定义的更宽泛 language-range 产生式, 而 Content-Language 使用下面定义的 language-tag 产生式.
language-tag = <Language-Tag, see [RFC5646], Section 2.1>
语言标签是一个或多个不区分大小写的子标签序列, 每个子标签由连字符 ("-", %x2D) 分隔. 在大多数情况下, 语言标签由一个主语言子标签组成, 用于标识相关语言的广泛族群 (例如 "en" = English), 后面可选跟随一系列子标签, 这些子标签细化或缩小该语言的范围 (例如 "en-CA" = 在加拿大传达的英语变体). 语言标签内不允许空白. 示例标签包括:
en, en-US, en-cockney, i-cherokee, x-pig-latin
更多信息见 [RFC5646].
3.1.3.2. Content-Language
Content-Language 头部字段描述表示预期受众的自然语言. 注意, 这可能不等同于表示内部使用的所有语言.
Content-Language = 1#language-tag
语言标签定义于 Section 3.1.3.1. Content-Language 的主要目的是允许用户按照自己的首选语言识别和区分表示. 因此, 如果内容只面向能读丹麦语的受众, 适当字段为:
Content-Language: da
如果未指定 Content-Language, 默认表示内容面向所有语言受众. 这可能意味着发送者认为内容不专属于任何自然语言, 或发送者不知道它面向哪种语言.
对于面向多个受众的内容, MAY 列出多种语言. 例如, 同时呈现毛利语原文和英语版本的 "Treaty of Waitangi" 译本需要:
Content-Language: mi, en
但是, 表示中存在多种语言并不意味着它面向多个语言受众. 一个例子是初学者语言入门教材, 如 "A First Lesson in Latin", 它显然面向能读英语的受众. 在这种情况下, Content-Language 只应包含 "en".
Content-Language MAY 应用于任何媒体类型 -- 它不限于文本文档.
3.1.4. 标识
3.1.4.1. 标识表示
当完整或部分表示在消息负载中传输时, 发送者提供一个与该表示对应资源的标识符, 或接收者确定这样的标识符, 往往是有用的.
对于请求消息:
-
如果请求具有
Content-Location头部字段, 则发送者断言该负载是由Content-Location字段值标识的资源的表示. 但是, 除非可通过其他方式验证 (本规范未定义), 否则不能信任这种断言. 该信息对于修订历史链接仍可能有用. -
否则, 负载未被标识.
对于响应消息, 按顺序应用以下规则, 直到找到匹配项:
-
如果请求方法是 GET 或 HEAD, 且响应状态码为 200 (OK)、204 (No Content)、206 (Partial Content), 或 304 (Not Modified), 则负载是由有效请求 URI 标识的资源的表示 ([RFC7230] Section 5.5).
-
如果请求方法是 GET 或 HEAD, 且响应状态码为 203 (Non-Authoritative Information), 则负载是中间方提供的目标资源的潜在修改或增强表示.
-
如果响应具有
Content-Location头部字段, 且其字段值是对与有效请求 URI 相同 URI 的引用, 则负载是由有效请求 URI 标识的资源的表示. -
如果响应具有
Content-Location头部字段, 且其字段值是对不同于有效请求 URI 的 URI 的引用, 则发送者断言该负载是由Content-Location字段值标识的资源的表示. 但是, 除非可通过其他方式验证 (本规范未定义), 否则不能信任这种断言. -
否则, 负载未被标识.
3.1.4.2. Content-Location
Content-Location 头部字段引用一个 URI, 该 URI 可用作与此消息负载中的表示对应的特定资源的标识符. 换言之, 如果在生成此消息时对该 URI 执行 GET 请求, 则 200 (OK) 响应将包含与此消息中作为负载封装的表示相同的表示.
Content-Location = absolute-URI / partial-URI
Content-Location 值不是有效请求 URI ([RFC7230] Section 5.5) 的替代品. 它是表示元数据. 它与 [RFC2557] Section 4 中为 MIME body part 定义的同名头部字段具有相同语法和语义. 不过, 它出现在 HTTP 消息中时, 对 HTTP 接收者有一些特殊含义.
如果 Content-Location 包含在 2xx (Successful) 响应消息中, 且其值 (转换为绝对形式后) 引用与有效请求 URI 相同的 URI, 则接收者 MAY 认为负载是在消息发起日期所指示时间该资源的当前表示. 对于 GET (Section 4.3.1) 或 HEAD (Section 4.3.2) 请求, 这与服务器未提供 Content-Location 时的默认语义相同. 对于像 PUT (Section 4.3.4) 或 POST (Section 4.3.3) 这样的改变状态的请求, 它意味着服务器的响应包含该资源的新表示, 从而区别于可能只是报告动作的表示 (例如 "It worked!"). 这允许创作应用程序更新其本地副本, 而无需后续 GET 请求.
如果 Content-Location 包含在 2xx (Successful) 响应消息中, 且其字段值引用不同于有效请求 URI 的 URI, 则源服务器声称该 URI 是与封装表示对应的另一个资源的标识符. 只有当两个标识符共享同一资源所有者时, 这种声明才可信, 而这一点无法通过 HTTP 以编程方式确定.
-
对于 GET 或 HEAD 请求的响应, 这表示有效请求 URI 引用的资源受内容协商约束, 而
Content-Location字段值是所选表示的更具体标识符. -
对于 POST 请求的 201 (Created) 响应,
Content-Location字段值是对与已提交数据对应的资源的引用 (即, 与有效请求 URI 标识的资源不同的资源). -
否则, 这样的
Content-Location表明此负载是某个不同于请求消息目标的资源的表示, 对 HTTP 没有其他意义.
如果 Content-Location 包含在响应消息中, 且表示由多个部分组成, 如 Content-Type 为 "multipart/*" 所指示, 则每个部分 MAY 有自己的 Content-Location 来指示与该部分对应的资源. 否则, Content-Location 只适用于整个负载.
3.2. 表示数据
与 HTTP 消息关联的表示数据要么作为消息的负载体提供, 要么由消息语义和有效请求 URI 引用. 表示数据采用由表示元数据头部字段定义的格式和编码.
表示数据的数据类型通过 Content-Type 和 Content-Encoding 头部字段确定. 它们定义了一个双层、有序的编码模型:
representation-data := Content-Encoding( Content-Type( bits ) )
3.3. 负载语义
某些 HTTP 消息将完整或部分表示作为消息"负载"传输. 在某些情况下, 负载可能只包含关联表示的头部字段 (例如对 HEAD 的响应), 或只包含表示数据的某些部分 (例如 206 (Partial Content) 状态码).
请求中负载的目的由方法语义定义. 例如, PUT 请求负载中的表示 (Section 4.3.4) 表示如果请求成功应用, 目标资源的期望状态; 而 POST 请求负载中的表示 (Section 4.3.3) 表示要由目标资源处理的信息.
在响应中, 负载的目的由请求方法和响应状态码共同定义. 例如, 对 GET 的 200 (OK) 响应的负载 (Section 4.3.1) 表示在消息发起日期 (Section 7.1.1.2) 观察到的目标资源当前状态; 而对 POST 的同一状态码响应中的负载, 可能表示处理结果, 也可能表示应用处理后的目标资源新状态.
在以下情况下, 负载被认为是"完整的":
-
已收到整个头部节, 且所指示的负载体长度为零;
-
当 chunked-body 部分完整时 (由零长度 chunk 和终止 CRLF 指示), chunked 负载即完整;
-
未知负载长度在连接关闭时完整.
3.4. 内容协商
当响应传达负载信息时, 无论指示成功还是错误, 源服务器通常有不同方式来表示该信息; 例如, 以不同格式、语言或编码表示. 同样, 不同用户或用户代理可能具有不同能力、特征或偏好, 这些因素会影响在可用表示中哪一个最适合交付. 因此, HTTP 提供了内容协商机制.
本规范定义三种可在协议中显露的内容协商模式: "proactive", 即服务器根据用户代理陈述的偏好选择表示; "reactive", 即服务器提供表示列表供用户代理选择; 以及 "request content", 即用户代理为请求负载选择表示. 其他内容协商模式包括 "conditional content", 其中表示由多个部分组成, 并基于用户代理参数有选择地渲染; "active content", 其中表示包含脚本, 该脚本基于用户代理特征发出额外 (更具体) 请求; 以及 "Transparent Content Negotiation" ([RFC2295]), 其中内容选择由中间方执行. 这些模式并不相互排斥, 且各自在适用性和实用性上都有取舍.
注意, 在所有情况下, HTTP 都不知道资源语义. 源服务器随时间、以及在内容协商的不同维度上对请求作出响应的一致性, 从而一个资源被观察到的表示随时间的"相同性", 完全由选择或生成这些响应的实体或算法决定. HTTP 不关注幕后的人.
3.4.1. 主动协商
当用户代理在请求中发送内容协商偏好, 以促使服务器上的算法选择首选表示时, 这称为主动协商 (也称服务器驱动协商). 选择基于响应的可用表示 (它可能变化的维度, 如语言、content-coding 等), 并与请求中提供的各种信息比较, 包括 Section 5.3 的显式协商字段以及隐式特征, 如客户端网络地址或 User-Agent 字段的某些部分.
当从可用表示中选择的算法难以向用户代理描述时, 或当服务器希望随第一个响应向用户代理发送其"最佳猜测" (希望如果该"最佳猜测"对用户足够好, 就能避免后续请求的往返延迟) 时, 主动协商是有利的. 为改进服务器的猜测, 用户代理 MAY 发送描述其偏好的请求头部字段.
主动协商有严重缺点:
-
服务器不可能准确确定对任何给定用户而言什么可能是"最佳的", 因为这需要完整了解用户代理的能力以及响应的预期用途 (例如, 用户是想在屏幕上查看, 还是打印在纸上?);
-
让用户代理在每个请求中描述其能力既可能非常低效 (因为只有很小比例的响应具有多个表示), 也可能给用户隐私带来风险;
-
它使源服务器实现以及为请求生成响应的算法复杂化; 以及,
-
它限制了响应对共享缓存的可复用性.
用户代理不能依赖主动协商偏好会被一致遵循, 因为源服务器可能未对所请求资源实现主动协商, 或者可能认为发送不符合用户代理偏好的响应比发送 406 (Not Acceptable) 响应更好.
受主动协商影响的响应通常会发送 Vary 头部字段 (Section 7.1.4), 以指示选择算法使用了请求信息的哪些部分.
3.4.2. 响应式协商
在响应式协商 (也称代理驱动协商) 中, 用户代理在收到来自源服务器的初始响应之后, 负责选择最佳响应表示 (不论状态码如何); 该初始响应包含替代表示的资源列表. 如果用户代理对初始响应表示不满意, 它可以基于列表中包含的元数据选择一个或多个替代资源并对其执行 GET 请求, 以获得该响应的另一种表示形式. 替代项的选择可以由用户代理自动执行, 也可以由用户从生成的 (可能是超文本的) 菜单中手动选择.
注意, 上述内容通常指响应的表示, 而不是资源的表示. 替代表示不一定是同一资源的表示, 尽管它们可能是.
服务器可以选择不发送除替代项列表之外的初始表示, 从而指示首选由用户代理执行响应式协商. 例如, 300 (Multiple Choices) 和 406 (Not Acceptable) 状态码响应中列出的替代项包含有关可用表示的信息, 使用户或用户代理可以通过作出选择来响应.
当响应会在常用维度 (如类型、语言或编码) 上变化、源服务器无法通过检查请求确定用户代理能力, 以及通常使用公共缓存来分担服务器负载并减少网络使用时, 响应式协商是有利的.
响应式协商的缺点是需要向用户代理传输替代项列表, 如果在头部节中传输会降低用户感知延迟, 并且需要第二个请求才能获得替代表示. 此外, 本规范未定义支持自动选择的机制, 尽管它不阻止将这种机制作为扩展来开发.
3.4.3. 请求内容协商
当内容协商偏好在服务器对客户端的响应中发送时, 客户端可以使用这些偏好来确定对该资源的后续请求最适合使用哪种表示. 这称为请求内容协商 (也称 "content negotiation as a quality of service").
例如, 415 (Unsupported Media Type) 响应中的 Accept 头部字段可能指示对该资源的 PUT 请求接受哪些媒体类型, 或响应中的 Accept-Language 字段可能指示服务的语言偏好.