• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[/
2    Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3
4    Distributed under the Boost Software License, Version 1.0. (See accompanying
5    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7    Official repository: https://github.com/boostorg/beast
8
9<iframe width="560" height="315" src="https://www.youtube.com/embed/WsUnnYEKPnI?rel=0" frameborder="0" allowfullscreen></iframe>
10]
11
12[section:http_message_container HTTP Message Container __video__]
13
14The following video presentation was delivered at
15[@https://cppcon.org/ CppCon]
16in 2017. The presentation provides a simplified explanation of the design
17process for the HTTP message container used in Beast. The slides and code
18used are available in the
19[@https://github.com/vinniefalco/CppCon2017 GitHub repository].
20
21[/ "Make Classes Great Again! (Using Concepts for Customization Points)"]
22[block'''
23<mediaobject>
24  <videoobject>
25    <videodata fileref="https://www.youtube.com/embed/WsUnnYEKPnI?rel=0"
26        align="center" contentwidth="560" contentdepth="315"/>
27  </videoobject>
28</mediaobject>
29''']
30
31In this section we describe the problem of modeling HTTP messages and explain
32how the library arrived at its solution, with a discussion of the benefits
33and drawbacks of the design choices. The goal for creating a message model
34is to create a container with value semantics, possibly movable and/or
35copyable, that contains all the information needed to serialize, or all
36of the information captured during parsing. More formally, given:
37
38* `m` is an instance of an HTTP message container
39
40* `x` is a series of octets describing a valid HTTP message in
41  the serialized format described in __rfc7230__.
42
43* `S(m)` is a serialization function which produces a series of octets
44  from a message container.
45
46* `P(x)` is a parsing function which produces a message container from
47  a series of octets.
48
49These relations are true:
50
51* `S(m) == x`
52
53* `P(S(m)) == m`
54
55We would also like our message container to have customization points
56permitting the following: allocator awareness, user-defined containers
57to represent header fields, and user-defined types and algorithms to
58represent the body. And finally, because requests and responses have
59different fields in the ['start-line], we would like the containers for
60requests and responses to be represented by different types for function
61overloading.
62
63Here is our first attempt at declaring some message containers:
64
65[table
66[[
67```
68/// An HTTP request
69template<class Fields, class Body>
70struct request
71{
72    int         version;
73    std::string method;
74    std::string target;
75    Fields      fields;
76
77    typename Body::value_type body;
78};
79```
80][
81```
82/// An HTTP response
83template<class Fields, class Body>
84struct response
85{
86    int         version;
87    int         status;
88    std::string reason;
89    Fields      fields;
90
91    typename Body::value_type body;
92};
93```
94]]
95]
96
97These containers are capable of representing everything in the model
98of HTTP requests and responses described in __rfc7230__. Request and
99response objects are different types. The user can choose the container
100used to represent the fields. And the user can choose the [*Body] type,
101which is a concept defining not only the type of `body` member but also
102the algorithms used to transfer information in and out of that member
103when performing serialization and parsing.
104
105However, a problem arises. How do we write a function which can accept
106an object that is either a request or a response? As written, the only
107obvious solution is to make the message a template type. Additional traits
108classes would then be needed to make sure that the passed object has a
109valid type which meets the requirements. These unnecessary complexities
110are bypassed by making each container a partial specialization:
111```
112/// An HTTP message
113template<bool isRequest, class Fields, class Body>
114struct message;
115
116/// An HTTP request
117template<class Fields, class Body>
118struct message<true, Fields, Body>
119{
120    int         version;
121    std::string method;
122    std::string target;
123    Fields      fields;
124
125    typename Body::value_type body;
126};
127
128/// An HTTP response
129template<bool isRequest, class Fields, class Body>
130struct message<false, Fields, Body>
131{
132    int         version;
133    int         status;
134    std::string reason;
135    Fields      fields;
136
137    typename Body::value_type body;
138};
139```
140
141Now we can declare a function which takes any message as a parameter:
142```
143template<bool isRequest, class Fields, class Body>
144void f(message<isRequest, Fields, Body>& msg);
145```
146
147This function can manipulate the fields common to requests and responses.
148If it needs to access the other fields, it can use overloads with
149partial specialization, or in C++17 a `constexpr` expression:
150```
151template<bool isRequest, class Fields, class Body>
152void f(message<isRequest, Fields, Body>& msg)
153{
154    if constexpr(isRequest)
155    {
156        // call msg.method(), msg.target()
157    }
158    else
159    {
160        // call msg.result(), msg.reason()
161    }
162}
163```
164
165Often, in non-trivial HTTP applications, we want to read the HTTP header
166and examine its contents before choosing a type for [*Body]. To accomplish
167this, there needs to be a way to model the header portion of a message.
168And we'd like to do this in a way that allows functions which take the
169header as a parameter, to also accept a type representing the whole
170message (the function will see just the header part). This suggests
171inheritance, by splitting a new base class off of the message:
172```
173/// An HTTP message header
174template<bool isRequest, class Fields>
175struct header;
176```
177
178Code which accesses the fields has to laboriously mention the `fields`
179member, so we'll not only make `header` a base class but we'll make
180a quality of life improvement and derive the header from the fields
181for notational convenience. In order to properly support all forms
182of construction of [*Fields] there will need to be a set of suitable
183constructor overloads (not shown):
184```
185/// An HTTP request header
186template<class Fields>
187struct header<true, Fields> : Fields
188{
189    int         version;
190    std::string method;
191    std::string target;
192};
193
194/// An HTTP response header
195template<class Fields>
196struct header<false, Fields> : Fields
197{
198    int         version;
199    int         status;
200    std::string reason;
201};
202
203/// An HTTP message
204template<bool isRequest, class Fields, class Body>
205struct message : header<isRequest, Fields>
206{
207    typename Body::value_type body;
208
209    /// Construct from a `header`
210    message(header<isRequest, Fields>&& h);
211};
212
213```
214
215Note that the `message` class now has a constructor allowing messages
216to be constructed from a similarly typed `header`. This handles the case
217where the user already has the header and wants to make a commitment to the
218type for [*Body]. A function can be declared which accepts any header:
219```
220template<bool isRequest, class Fields>
221void f(header<isRequest, Fields>& msg);
222```
223
224Until now we have not given significant consideration to the constructors
225of the `message` class. But to achieve all our goals we will need to make
226sure that there are enough constructor overloads to not only provide for
227the special copy and move members if the instantiated types support it,
228but also allow the fields container and body container to be constructed
229with arbitrary variadic lists of parameters. This allows the container
230to fully support allocators.
231
232The solution used in the library is to treat the message like a `std::pair`
233for the purposes of construction, except that instead of `first` and `second`
234we have the `Fields` base class and `message::body` member. This means that
235single-argument constructors for those fields should be accessible as they
236are with `std::pair`, and that a mechanism identical to the pair's use of
237`std::piecewise_construct` should be provided. Those constructors are too
238complex to repeat here, but interested readers can view the declarations
239in the corresponding header file.
240
241There is now significant progress with our message container but a stumbling
242block remains. There is no way to control the allocator for the `std::string`
243members. We could add an allocator to the template parameter list of the
244header and message classes, use it for those strings. This is unsatisfying
245because of the combinatorial explosion of constructor variations needed to
246support the scheme. It also means that request messages could have [*four]
247different allocators: two for the fields and body, and two for the method
248and target strings. A better solution is needed.
249
250To get around this we make an interface modification and then add
251a requirement to the [*Fields] type. First, the interface change:
252```
253/// An HTTP request header
254template<class Fields>
255struct header<true, Fields> : Fields
256{
257    int         version;
258
259    verb        method() const;
260    string_view method_string() const;
261    void        method(verb);
262    void        method(string_view);
263
264    string_view target(); const;
265    void        target(string_view);
266
267private:
268    verb method_;
269};
270
271/// An HTTP response header
272template<class Fields>
273struct header<false, Fields> : Fields
274{
275    int         version;
276    int         result;
277    string_view reason() const;
278    void        reason(string_view);
279};
280```
281
282The start-line data members are replaced by traditional accessors
283using non-owning references to string buffers. The method is stored
284using a simple integer instead of the entire string, for the case
285where the method is recognized from the set of known verb strings.
286
287Now we add a requirement to the fields type: management of the
288corresponding string is delegated to the [*Fields] container, which can
289already be allocator aware and constructed with the necessary allocator
290parameter via the provided constructor overloads for `message`. The
291delegation implementation looks like this (only the response header
292specialization is shown):
293```
294/// An HTTP response header
295template<class Fields>
296struct header<false, Fields> : Fields
297{
298    int     version;
299    int     status;
300
301    string_view
302    reason() const
303    {
304        return this->reason_impl(); // protected member of Fields
305    }
306
307    void
308    reason(string_view s)
309    {
310        this->reason_impl(s);       // protected member of Fields
311    }
312};
313```
314
315Now that we've accomplished our initial goals and more, there are a few
316more quality of life improvements to make. Users will choose different
317types for `Body` far more often than they will for `Fields`. Thus, we
318swap the order of these types and provide a default. Then, we provide
319type aliases for requests and responses to soften the impact of using
320`bool` to choose the specialization:
321
322```
323/// An HTTP header
324template<bool isRequest, class Body, class Fields = fields>
325struct header;
326
327/// An HTTP message
328template<bool isRequest, class Body, class Fields = fields>
329struct message;
330
331/// An HTTP request
332template<class Body, class Fields = fields>
333using request = message<true, Body, Fields>;
334
335/// An HTTP response
336template<class Body, class Fields = fields>
337using response = message<false, Body, Fields>;
338```
339
340This allows concise specification for the common cases, while
341allowing for maximum customization for edge cases:
342```
343request<string_body> req;
344
345response<file_body> res;
346```
347
348This container is also capable of representing complete HTTP/2 messages.
349Not because it was explicitly designed for, but because the IETF wanted to
350preserve message compatibility with HTTP/1. Aside from version specific
351fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are
352identical even though their serialized representation is considerably
353different. The message model presented in this library is ready for HTTP/2.
354
355In conclusion, this representation for the message container is well thought
356out, provides comprehensive flexibility, and avoids the necessity of defining
357additional traits classes. User declarations of functions that accept headers
358or messages as parameters are easy to write in a variety of ways to accomplish
359different results, without forcing cumbersome SFINAE declarations everywhere.
360
361[endsect]
362