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