en	HTTP Message Container __video__
zh-hans	HTTP 消息容器 __video__

en	The following video presentation was delivered at [@https://cppcon.org/ CppCon] in 2017. The presentation provides a simplified explanation of the design process for the HTTP message container used in Beast. The slides and code used are available in the [@https://github.com/vinniefalco/CppCon2017 GitHub repository].
zh-hans	以下视频演示是在 2017 年的 [@https://cppcon.org/ CppCon] 上发表的。该演示简要介绍了 Beast 中使用的 HTTP 消息容器的设计过程。演示所用的幻灯片和代码可在 [@https://github.com/vinniefalco/CppCon2017 GitHub 仓库] 中找到。

en	In this section we describe the problem of modeling HTTP messages and explain how the library arrived at its solution, with a discussion of the benefits and drawbacks of the design choices. The goal for creating a message model is to create a container with value semantics, possibly movable and/or copyable, that contains all the information needed to serialize, or all of the information captured during parsing. More formally, given:
zh-hans	在本节中，我们将探讨 HTTP 消息建模所面临的挑战，并阐述该库是如何推导出最终解决方案的，同时也会深入剖析这些设计选择的利弊。构建消息模型的核心目标，是打造一个具备值语义（value semantics）的容器（支持移动和/或复制），使其能够囊括序列化所需的全部信息，或是完整保留解析过程中捕获的所有数据。从更严谨的层面来说，给定：

en	* `m` is an instance of an HTTP message container
zh-hans	* `m` 是 HTTP 消息容器的一个实例

en	* `x` is a series of octets describing a valid HTTP message in
zh-hans	* `x` 是一系列字节（octets），用于描述一个有效的 HTTP 消息

en	* `S(m)` is a serialization function which produces a series of octets
zh-hans	* `S(m)` 是一个序列化函数，用于生成一系列字节（octets）

en	* `P(x)` is a parsing function which produces a message container from
zh-hans	* `P(x)` 是一个解析函数，用于从……生成一个消息容器

en	These relations are true:
zh-hans	以下关系成立：

en	* `S(m) == x`

en	* `P(S(m)) == m`

en	We would also like our message container to have customization points permitting the following: allocator awareness, user-defined containers to represent header fields, and user-defined types and algorithms to represent the body. And finally, because requests and responses have different fields in the ['start-line], we would like the containers for requests and responses to be represented by different types for function overloading.
zh-hans	此外，我们还希望消息容器提供自定义扩展点，以实现以下功能：支持分配器感知（allocator awareness）、允许使用用户自定义的容器来表示首部字段，以及支持使用用户自定义的类型和算法来表示消息体。最后，由于请求和响应的起始行（start-line）包含不同的字段，我们希望分别使用不同的类型来表示请求和响应的容器，从而支持函数重载。

en	Here is our first attempt at declaring some message containers:
zh-hans	这是我们首次尝试声明一些消息容器：

en	These containers are capable of representing everything in the model of HTTP requests and responses described in __rfc7230__. Request and response objects are different types. The user can choose the container used to represent the fields. And the user can choose the [*Body] type, which is a concept defining not only the type of `body` member but also the algorithms used to transfer information in and out of that member when performing serialization and parsing.
zh-hans	这些容器能够完整地表达 RFC 7230 中描述的 HTTP 请求和响应模型。请求对象和响应对象是两种不同的类型。用户可以选择用于表示首部字段的容器。此外，用户还可以选择 `Body` 类型，该类型不仅定义了 `body` 成员的类型，还规定了在序列化和解析过程中，信息进出该成员所使用的算法。

en	However, a problem arises. How do we write a function which can accept an object that is either a request or a response? As written, the only obvious solution is to make the message a template type. Additional traits classes would then be needed to make sure that the passed object has a valid type which meets the requirements. These unnecessary complexities are bypassed by making each container a partial specialization: ``` /// An HTTP message template<bool isRequest, class Fields, class Body> struct message;
zh-hans	然而，问题随之而来。如何编写一个既能接受请求对象又能接受响应对象的函数呢？按照当前的写法，最显而易见的解决方案是将消息类型设为模板参数。这样一来，还需要额外的特征类（traits classes）来确保传入的对象具有符合要求的有效类型。为了避免这些不必要的复杂性，我们可以将每个容器设计为偏特化（partial specialization）：```/// An HTTP message template<bool isRequest, class Fields, class Body> struct message;

en	/// An HTTP request template<class Fields, class Body> struct message<true, Fields, Body> {
zh-hans	/// 一个 HTTP 请求模板<class Fields, class Body> struct message<true, Fields, Body> {

en	};

en	/// An HTTP response template<bool isRequest, class Fields, class Body> struct message<false, Fields, Body> {
zh-hans	/// 一个 HTTP 响应模板<bool isRequest, class Fields, class Body> struct message<false, Fields, Body> {

en	}; ```

en	Now we can declare a function which takes any message as a parameter: ``` template<bool isRequest, class Fields, class Body> void f(message<isRequest, Fields, Body>& msg); ```
zh-hans	现在，我们可以声明一个能够接受任意消息作为参数的函数了：``` template<bool isRequest, class Fields, class Body> void f(message<isRequest, Fields, Body>& msg); ```

en	This function can manipulate the fields common to requests and responses. If it needs to access the other fields, it can use overloads with partial specialization, or in C++17 a `constexpr` expression: ``` template<bool isRequest, class Fields, class Body> void f(message<isRequest, Fields, Body>& msg) {
zh-hans	该函数可以处理请求和响应共有的字段。如果需要访问其他字段，则可以使用基于偏特化的重载，或者在 C++17 中使用 constexpr 表达式：``` template<bool isRequest, class Fields, class Body> void f(message<isRequest, Fields, Body>& msg) {

en	} ```

en	Often, in non-trivial HTTP applications, we want to read the HTTP header and examine its contents before choosing a type for [*Body]. To accomplish this, there needs to be a way to model the header portion of a message. And we'd like to do this in a way that allows functions which take the header as a parameter, to also accept a type representing the whole message (the function will see just the header part). This suggests inheritance, by splitting a new base class off of the message: ``` /// An HTTP message header template<bool isRequest, class Fields> struct header; ```
zh-hans	通常，在非平凡的 HTTP 应用程序中，我们希望在为 [*Body] 选择类型之前，先读取 HTTP 头部并检查其内容。为了实现这一点，需要有一种方法来对消息的头部部分进行建模。并且，我们希望以这样的方式来实现：让那些将头部作为参数的函数，也能够接受代表整个消息的类型（函数只会看到头部部分）。这表明可以通过从 message 中分离出一个新的基类来使用继承：``` /// An HTTP message header template<bool isRequest, class Fields> struct header; ```

en	Code which accesses the fields has to laboriously mention the `fields` member, so we'll not only make `header` a base class but we'll make a quality of life improvement and derive the header from the fields for notational convenience. In order to properly support all forms of construction of [*Fields] there will need to be a set of suitable constructor overloads (not shown): ``` /// An HTTP request header template<class Fields> struct header<true, Fields> : Fields {
zh-hans	访问字段的代码不得不反复显式引用 `fields` 成员，这显得十分繁琐。因此，我们不仅将 `header` 作为基类，还会为了书写便利进行一项优化：让 `header` 直接从 `fields` 派生。为了能够正确支持对 [*Fields] 的所有构造形式，还需要提供一组合适的构造函数重载（此处未展示）：``` /// An HTTP request header template<class Fields> struct header<true, Fields> : Fields {

en	};

en	/// An HTTP response header template<class Fields> struct header<false, Fields> : Fields {
zh-hans	/// 一个 HTTP 响应头部 template struct header<false, Fields> : Fields {

en	};

en	/// An HTTP message template<bool isRequest, class Fields, class Body> struct message : header<isRequest, Fields> {
zh-hans	/// 一个 HTTP 消息 template<bool isRequest, class Fields, class Body> struct message : header<isRequest, Fields> {

en	};

en	```

en	Note that the `message` class now has a constructor allowing messages to be constructed from a similarly typed `header`. This handles the case where the user already has the header and wants to make a commitment to the type for [*Body]. A function can be declared which accepts any header: ``` template<bool isRequest, class Fields> void f(header<isRequest, Fields>& msg); ```
zh-hans	请注意，`message` 类现在拥有一个构造函数，允许从类型相同的 `header` 构造消息。这解决了用户已经拥有头部，并希望为 [*Body] 确定具体类型的情况。我们可以声明一个能够接受任意头部的函数： ``` template<bool isRequest, class Fields> void f(header<isRequest, Fields>& msg); ```

en	Until now we have not given significant consideration to the constructors of the `message` class. But to achieve all our goals we will need to make sure that there are enough constructor overloads to not only provide for the special copy and move members if the instantiated types support it, but also allow the fields container and body container to be constructed with arbitrary variadic lists of parameters. This allows the container to fully support allocators.
zh-hans	到目前为止，我们还没有对 `message` 类的构造函数给予足够的重视。但为了实现所有目标，我们必须确保提供足够多的构造函数重载。这不仅需要在实例化的类型支持时提供特殊的拷贝和移动成员，还需要允许使用任意的可变参数列表来构造字段容器和主体容器。这使得容器能够完全支持分配器。

en	The solution used in the library is to treat the message like a `std::pair` for the purposes of construction, except that instead of `first` and `second` we have the `Fields` base class and `message::body` member. This means that single-argument constructors for those fields should be accessible as they are with `std::pair`, and that a mechanism identical to the pair's use of `std::piecewise_construct` should be provided. Those constructors are too complex to repeat here, but interested readers can view the declarations in the corresponding header file.
zh-hans	库中采用的解决方案是：在构造 `message` 时，将其视作 `std::pair` 来处理。不同的是，原本 `std::pair` 中的 `first` 和 `second` 在这里被替换成了 `Fields` 基类和 `message::body` 成员。这意味着，这些字段的单参数构造函数应当像 `std::pair` 一样易于调用，并且需要提供一套与 `std::pair` 的 `std::piecewise_construct` 完全相同的机制。由于这些构造函数的实现较为复杂，这里不再赘述，感兴趣的读者可以直接查阅相应头文件中的声明。

en	There is now significant progress with our message container but a stumbling block remains. There is no way to control the allocator for the `std::string` members. We could add an allocator to the template parameter list of the header and message classes, use it for those strings. This is unsatisfying because of the combinatorial explosion of constructor variations needed to support the scheme. It also means that request messages could have [*four] different allocators: two for the fields and body, and two for the method and target strings. A better solution is needed.
zh-hans	尽管我们的消息容器已经取得了显著进展，但仍面临一个棘手的问题：目前无法控制 `std::string` 成员的分配器。虽然可以在 `header` 和 `message` 类的模板参数列表中添加一个分配器来管理这些字符串，但这并不是一个令人满意的方案。因为这需要引入数量庞大的构造函数重载来支持该机制，导致组合爆炸。此外，这也意味着请求消息可能会拥有多达四种不同的分配器：其中两个分别用于字段和主体，另外两个则用于方法（method）和目标（target）字符串。因此，我们需要寻找一个更好的解决方案。

en	To get around this we make an interface modification and then add a requirement to the [*Fields] type. First, the interface change: ``` /// An HTTP request header template<class Fields> struct header<true, Fields> : Fields {
zh-hans	为了解决这个问题，我们首先对接口进行修改，然后对 `Fields` 类型增加一项要求。首先是接口层面的改动：``` /// 一个 HTTP 请求头部 template<class Fields> struct header<true, Fields> : Fields {

en	private:

en	};

en	/// An HTTP response header template<class Fields> struct header<false, Fields> : Fields {
zh-hans	/// 一个 HTTP 响应头部 template struct header<false, Fields> : Fields {

en	}; ```

en	The start-line data members are replaced by traditional accessors using non-owning references to string buffers. The method is stored using a simple integer instead of the entire string, for the case where the method is recognized from the set of known verb strings.
zh-hans	起始行（start-line）的数据成员被替换成传统的访问器（accessors），它们通过不持有所有权的引用来指向字符串缓冲区。此外，对于能从已知动词字符串集合中识别出的方法（method），系统直接使用一个简单的整数来存储，而不再保存完整的字符串。

en	Now we add a requirement to the fields type: management of the corresponding string is delegated to the [*Fields] container, which can already be allocator aware and constructed with the necessary allocator parameter via the provided constructor overloads for `message`. The delegation implementation looks like this (only the response header specialization is shown): ``` /// An HTTP response header template<class Fields> struct header<false, Fields> : Fields {
zh-hans	对 `Fields` 类型增加一项要求：将对应字符串的管理委托给 `Fields` 容器。该容器本身已具备分配器感知能力，且可通过 `message` 提供的构造函数重载，使用必要的分配器参数进行构造。委托的实现方式如下（仅展示响应头部的特化版本）：``` /// 一个 HTTP 响应头部 template<class Fields> struct header<false, Fields> : Fields {

en	}; ```

en	Now that we've accomplished our initial goals and more, there are a few more quality of life improvements to make. Users will choose different types for `Body` far more often than they will for `Fields`. Thus, we swap the order of these types and provide a default. Then, we provide type aliases for requests and responses to soften the impact of using `bool` to choose the specialization:
zh-hans	初步目标已超额达成，接下来还需进行一些提升易用性的改进。由于用户为 `Body` 选择自定义类型的频率远高于为 `Fields` 选择，因此将这两者的模板参数顺序互换，并为 `Fields` 提供默认类型。随后，为请求和响应提供类型别名，以缓解直接使用 `bool` 来选择特化版本带来的不便：

en	``` /// An HTTP header template<bool isRequest, class Body, class Fields = fields> struct header;
zh-hans	``` /// 一个 HTTP 头部 template<bool isRequest, class Body, class Fields = fields> struct header;

en	/// An HTTP message template<bool isRequest, class Body, class Fields = fields> struct message;
zh-hans	/// 一个 HTTP 消息 template<bool isRequest, class Body, class Fields = fields> struct message;

en	/// An HTTP request template<class Body, class Fields = fields> using request = message<true, Body, Fields>;
zh-hans	/// 一个 HTTP 请求 template<class Body, class Fields = fields> using request = message<true, Body, Fields>;

en	/// An HTTP response template<class Body, class Fields = fields> using response = message<false, Body, Fields>; ```
zh-hans	/// 一个 HTTP 响应 template<class Body, class Fields = fields> using response = message<false, Body, Fields>; ```

en	This allows concise specification for the common cases, while allowing for maximum customization for edge cases: ``` request<string_body> req;
zh-hans	这样既能以简洁的方式满足常见需求，又能为边缘情况提供最大程度的定制空间：``` request<string_body> req;

en	response<file_body> res; ```

en	This container is also capable of representing complete HTTP/2 messages. Not because it was explicitly designed for, but because the IETF wanted to preserve message compatibility with HTTP/1. Aside from version specific fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are identical even though their serialized representation is considerably different. The message model presented in this library is ready for HTTP/2.
zh-hans	该容器同样能够表示完整的 HTTP/2 消息。这并非因为它是为此而专门设计的，而是因为 IETF 希望保持与 HTTP/1 的消息兼容性。除了像 `Connection` 这类特定于版本的字段之外，HTTP/1 和 HTTP/2 消息的内容是完全相同的，尽管它们的序列化表示形式存在显著差异。因此，本库中展示的消息模型已经为 HTTP/2 做好了准备。

en	In conclusion, this representation for the message container is well thought out, provides comprehensive flexibility, and avoids the necessity of defining additional traits classes. User declarations of functions that accept headers or messages as parameters are easy to write in a variety of ways to accomplish different results, without forcing cumbersome SFINAE declarations everywhere.
zh-hans	综上所述，这种消息容器的表示方式经过深思熟虑，提供了全面的灵活性，并且免去了定义额外特征类（traits classes）的必要。用户声明接受头部或消息作为参数的函数时，可以通过多种方式轻松编写，以实现不同的目的，而无需在代码中到处使用繁琐的 SFINAE 声明。