<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="">HTTP 消息容器 __video__</string>
    <string name="">以下视频演示是在 2017 年的 [@https://cppcon.org/ CppCon] 上发表的。该演示简要介绍了 Beast 中使用的 HTTP 消息容器的设计过程。演示所用的幻灯片和代码可在 [@https://github.com/vinniefalco/CppCon2017 GitHub 仓库] 中找到。</string>
    <string name="">在本节中，我们将探讨 HTTP 消息建模所面临的挑战，并阐述该库是如何推导出最终解决方案的，同时也会深入剖析这些设计选择的利弊。构建消息模型的核心目标，是打造一个具备值语义（value semantics）的容器（支持移动和/或复制），使其能够囊括序列化所需的全部信息，或是完整保留解析过程中捕获的所有数据。从更严谨的层面来说，给定：</string>
    <string name="">* `m` 是 HTTP 消息容器的一个实例</string>
    <string name="">* `x` 是一系列字节（octets），用于描述一个有效的 HTTP 消息</string>
    <string name="">* `S(m)` 是一个序列化函数，用于生成一系列字节（octets）</string>
    <string name="">* `P(x)` 是一个解析函数，用于从……生成一个消息容器</string>
    <string name="">以下关系成立：</string>
    <string name="">* `S(m) == x`</string>
    <string name="">* `P(S(m)) == m`</string>
    <string name="">此外，我们还希望消息容器提供自定义扩展点，以实现以下功能：支持分配器感知（allocator awareness）、允许使用用户自定义的容器来表示首部字段，以及支持使用用户自定义的类型和算法来表示消息体。最后，由于请求和响应的起始行（start-line）包含不同的字段，我们希望分别使用不同的类型来表示请求和响应的容器，从而支持函数重载。</string>
    <string name="">这是我们首次尝试声明一些消息容器：</string>
    <string name="">这些容器能够完整地表达 RFC 7230 中描述的 HTTP 请求和响应模型。请求对象和响应对象是两种不同的类型。用户可以选择用于表示首部字段的容器。此外，用户还可以选择 `Body` 类型，该类型不仅定义了 `body` 成员的类型，还规定了在序列化和解析过程中，信息进出该成员所使用的算法。</string>
    <string name="">然而，问题随之而来。如何编写一个既能接受请求对象又能接受响应对象的函数呢？按照当前的写法，最显而易见的解决方案是将消息类型设为模板参数。这样一来，还需要额外的特征类（traits classes）来确保传入的对象具有符合要求的有效类型。为了避免这些不必要的复杂性，我们可以将每个容器设计为偏特化（partial specialization）：```/// An HTTP message template&lt;bool isRequest, class Fields, class Body&gt; struct message;</string>
    <string name="">/// 一个 HTTP 请求模板&lt;class Fields, class Body&gt; struct message&lt;true, Fields, Body&gt; {</string>
    <string name="126">};</string>
    <string name="">/// 一个 HTTP 响应模板&lt;bool isRequest, class Fields, class Body&gt; struct message&lt;false, Fields, Body&gt; {</string>
    <string name="138">}; ```</string>
    <string name="">现在，我们可以声明一个能够接受任意消息作为参数的函数了：``` template&lt;bool isRequest, class Fields, class Body&gt; void f(message&lt;isRequest, Fields, Body&gt;&amp; msg); ```</string>
    <string name="">该函数可以处理请求和响应共有的字段。如果需要访问其他字段，则可以使用基于偏特化的重载，或者在 C++17 中使用 constexpr 表达式：``` template&lt;bool isRequest, class Fields, class Body&gt; void f(message&lt;isRequest, Fields, Body&gt;&amp; msg) {</string>
    <string name="">} ```</string>
    <string name="">通常，在非平凡的 HTTP 应用程序中，我们希望在为 [*Body] 选择类型之前，先读取 HTTP 头部并检查其内容。为了实现这一点，需要有一种方法来对消息的头部部分进行建模。并且，我们希望以这样的方式来实现：让那些将头部作为参数的函数，也能够接受代表整个消息的类型（函数只会看到头部部分）。这表明可以通过从 message 中分离出一个新的基类来使用继承：``` /// An HTTP message header template&lt;bool isRequest, class Fields&gt; struct header; ```</string>
    <string name="">访问字段的代码不得不反复显式引用 `fields` 成员，这显得十分繁琐。因此，我们不仅将 `header` 作为基类，还会为了书写便利进行一项优化：让 `header` 直接从 `fields` 派生。为了能够正确支持对 [*Fields] 的所有构造形式，还需要提供一组合适的构造函数重载（此处未展示）：``` /// An HTTP request header template&lt;class Fields&gt; struct header&lt;true, Fields&gt; : Fields {</string>
    <string name="192">};</string>
    <string name="194">/// 一个 HTTP 响应头部 template struct header&lt;false, Fields&gt; : Fields {</string>
    <string name="201">};</string>
    <string name="">/// 一个 HTTP 消息 template&lt;bool isRequest, class Fields, class Body&gt; struct message : header&lt;isRequest, Fields&gt; {</string>
    <string name="211">};</string>
    <string name="">```</string>
    <string name="">请注意，`message` 类现在拥有一个构造函数，允许从类型相同的 `header` 构造消息。这解决了用户已经拥有头部，并希望为 [*Body] 确定具体类型的情况。我们可以声明一个能够接受任意头部的函数： ``` template&lt;bool isRequest, class Fields&gt; void f(header&lt;isRequest, Fields&gt;&amp; msg); ```</string>
    <string name="">到目前为止，我们还没有对 `message` 类的构造函数给予足够的重视。但为了实现所有目标，我们必须确保提供足够多的构造函数重载。这不仅需要在实例化的类型支持时提供特殊的拷贝和移动成员，还需要允许使用任意的可变参数列表来构造字段容器和主体容器。这使得容器能够完全支持分配器。</string>
    <string name="">库中采用的解决方案是：在构造 `message` 时，将其视作 `std::pair` 来处理。不同的是，原本 `std::pair` 中的 `first` 和 `second` 在这里被替换成了 `Fields` 基类和 `message::body` 成员。这意味着，这些字段的单参数构造函数应当像 `std::pair` 一样易于调用，并且需要提供一套与 `std::pair` 的 `std::piecewise_construct` 完全相同的机制。由于这些构造函数的实现较为复杂，这里不再赘述，感兴趣的读者可以直接查阅相应头文件中的声明。</string>
    <string name="">尽管我们的消息容器已经取得了显著进展，但仍面临一个棘手的问题：目前无法控制 `std::string` 成员的分配器。虽然可以在 `header` 和 `message` 类的模板参数列表中添加一个分配器来管理这些字符串，但这并不是一个令人满意的方案。因为这需要引入数量庞大的构造函数重载来支持该机制，导致组合爆炸。此外，这也意味着请求消息可能会拥有多达四种不同的分配器：其中两个分别用于字段和主体，另外两个则用于方法（method）和目标（target）字符串。因此，我们需要寻找一个更好的解决方案。</string>
    <string name="">为了解决这个问题，我们首先对接口进行修改，然后对 `Fields` 类型增加一项要求。首先是接口层面的改动：``` /// 一个 HTTP 请求头部 template&lt;class Fields&gt; struct header&lt;true, Fields&gt; : Fields {</string>
    <string name="">private:</string>
    <string name="269">};</string>
    <string name="271">/// 一个 HTTP 响应头部 template struct header&lt;false, Fields&gt; : Fields {</string>
    <string name="279">}; ```</string>
    <string name="">起始行（start-line）的数据成员被替换成传统的访问器（accessors），它们通过不持有所有权的引用来指向字符串缓冲区。此外，对于能从已知动词字符串集合中识别出的方法（method），系统直接使用一个简单的整数来存储，而不再保存完整的字符串。</string>
    <string name="">对 `Fields` 类型增加一项要求：将对应字符串的管理委托给 `Fields` 容器。该容器本身已具备分配器感知能力，且可通过 `message` 提供的构造函数重载，使用必要的分配器参数进行构造。委托的实现方式如下（仅展示响应头部的特化版本）：``` /// 一个 HTTP 响应头部 template&lt;class Fields&gt; struct header&lt;false, Fields&gt; : Fields {</string>
    <string name="312">}; ```</string>
    <string name="">初步目标已超额达成，接下来还需进行一些提升易用性的改进。由于用户为 `Body` 选择自定义类型的频率远高于为 `Fields` 选择，因此将这两者的模板参数顺序互换，并为 `Fields` 提供默认类型。随后，为请求和响应提供类型别名，以缓解直接使用 `bool` 来选择特化版本带来的不便：</string>
    <string name="">``` /// 一个 HTTP 头部 template&lt;bool isRequest, class Body, class Fields = fields&gt; struct header;</string>
    <string name="">/// 一个 HTTP 消息 template&lt;bool isRequest, class Body, class Fields = fields&gt; struct message;</string>
    <string name="">/// 一个 HTTP 请求 template&lt;class Body, class Fields = fields&gt; using request = message&lt;true, Body, Fields&gt;;</string>
    <string name="">/// 一个 HTTP 响应 template&lt;class Body, class Fields = fields&gt; using response = message&lt;false, Body, Fields&gt;; ```</string>
    <string name="">这样既能以简洁的方式满足常见需求，又能为边缘情况提供最大程度的定制空间：``` request&lt;string_body&gt; req;</string>
    <string name="">response&lt;file_body&gt; res; ```</string>
    <string name="">该容器同样能够表示完整的 HTTP/2 消息。这并非因为它是为此而专门设计的，而是因为 IETF 希望保持与 HTTP/1 的消息兼容性。除了像 `Connection` 这类特定于版本的字段之外，HTTP/1 和 HTTP/2 消息的内容是完全相同的，尽管它们的序列化表示形式存在显著差异。因此，本库中展示的消息模型已经为 HTTP/2 做好了准备。</string>
    <string name="">综上所述，这种消息容器的表示方式经过深思熟虑，提供了全面的灵活性，并且免去了定义额外特征类（traits classes）的必要。用户声明接受头部或消息作为参数的函数时，可以通过多种方式轻松编写，以实现不同的目的，而无需在代码中到处使用繁琐的 SFINAE 声明。</string>
</resources>
