forked from boostorg/beast
The verb interfaces now use verb::unknown instead of boost::optional<verb> == boost::none to indicate that the request-method is an unrecognized string. The http::header interface is modified to focus more on the verb enum rather than the string. For recognized verbs, the implementation stores an integer instead of the string. Unknown verbs are still stored as strings. * header::method now returns a verb * header::method_string returns the method text
300 lines
9.6 KiB
Plaintext
300 lines
9.6 KiB
Plaintext
[/
|
|
Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
|
|
|
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
]
|
|
|
|
[section:http_message HTTP Message Container]
|
|
|
|
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:
|
|
|
|
* `m` is an instance of an HTTP message container
|
|
|
|
* `x` is a series of octets describing a valid HTTP message in
|
|
the serialized format decribed in __rfc7230__.
|
|
|
|
* `S(m)` is a serialization function which produces a series of octets
|
|
from a message container.
|
|
|
|
* `P(x)` is a parsing function which produces a message container from
|
|
a series of octets.
|
|
|
|
These relations are true:
|
|
|
|
* `S(m) == x`
|
|
|
|
* `P(S(m)) == m`
|
|
|
|
We would also like our message container to have customization points
|
|
permitting the following: full allocator support, 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.
|
|
|
|
Here is our first attempt at declaring some message containers:
|
|
|
|
[table
|
|
[[
|
|
```
|
|
/// An HTTP request
|
|
template<class Fields, class Body>
|
|
struct request
|
|
{
|
|
int version;
|
|
std::string method;
|
|
std::string target;
|
|
Fields fields;
|
|
typename Body::value_type body;
|
|
};
|
|
```
|
|
][
|
|
```
|
|
/// An HTTP response
|
|
template<class Fields, class Body>
|
|
struct response
|
|
{
|
|
int version;
|
|
int status;
|
|
std::string reason;
|
|
Fields fields;
|
|
typename Body::value_type body;
|
|
};
|
|
```
|
|
]]
|
|
]
|
|
|
|
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.
|
|
|
|
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. Instead, bypass those complexities
|
|
by making each container a partial specialization of one class:
|
|
```
|
|
/// An HTTP message
|
|
template<bool isRequest, class Fields, class Body>
|
|
struct message;
|
|
|
|
/// An HTTP request
|
|
template<class Fields, class Body>
|
|
struct message<true, Fields, Body>
|
|
{
|
|
int version;
|
|
std::string method;
|
|
std::string target;
|
|
Fields fields;
|
|
typename Body::value_type body;
|
|
};
|
|
|
|
/// An HTTP response
|
|
template<bool isRequest, class Fields, class Body>
|
|
struct message<false, Fields, Body>
|
|
{
|
|
int version;
|
|
int status;
|
|
std::string reason;
|
|
Fields fields;
|
|
typename Body::value_type body;
|
|
};
|
|
```
|
|
|
|
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);
|
|
```
|
|
|
|
This function can manipulate the fields common to requests and responses.
|
|
If it needs to access the other fields, it can do so using tag dispatch
|
|
with an object of type `std::integral_constant<bool, isRequest>`.
|
|
|
|
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:
|
|
```
|
|
/// An HTTP message header
|
|
template<bool isRequest, class Fields>
|
|
struct header;
|
|
|
|
/// An HTTP request header
|
|
template<class Fields>
|
|
struct header<true, Fields>
|
|
{
|
|
int version;
|
|
std::string method;
|
|
std::string target;
|
|
Fields fields;
|
|
};
|
|
|
|
/// An HTTP response header
|
|
template<class Fields>
|
|
struct header<false, Fields>
|
|
{
|
|
int version;
|
|
int status;
|
|
std::string reason;
|
|
Fields fields;
|
|
};
|
|
|
|
/// An HTTP message
|
|
template<bool isRequest, class Fields, class Body>
|
|
struct message : header<isRequest, Fields>
|
|
{
|
|
typename Body::value_type body;
|
|
|
|
/// Construct from a `header`
|
|
message(header<isRequest, Fields>&& h);
|
|
};
|
|
|
|
```
|
|
|
|
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]. This also lets us declare a function accepting any header:
|
|
```
|
|
template<bool isRequest, class Fields>
|
|
void f(header<isRequest, Fields>& msg);
|
|
```
|
|
|
|
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.
|
|
|
|
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 `last`
|
|
we have `fields` and `body`. 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.
|
|
|
|
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.
|
|
|
|
To get around this we make a simple change to the interface and then
|
|
engineer a clever concession. First, the interface change:
|
|
```
|
|
/// An HTTP request header
|
|
template<class Fields>
|
|
struct header<true, Fields>
|
|
{
|
|
int version;
|
|
verb method() const;
|
|
string_view method_string() const;
|
|
void method(verb);
|
|
void method(string_view);
|
|
string_view target(); const;
|
|
void target(string_view);
|
|
Fields fields;
|
|
|
|
private:
|
|
verb method_;
|
|
};
|
|
|
|
/// An HTTP response header
|
|
template<class Fields>
|
|
struct header<false, Fields>
|
|
{
|
|
int version;
|
|
int status;
|
|
string_view reason() const;
|
|
void reason(string_view);
|
|
Fields fields;
|
|
};
|
|
```
|
|
|
|
The start-line data members are replaced 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.
|
|
|
|
Now we make a concession: 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>
|
|
{
|
|
int version;
|
|
int status;
|
|
|
|
string_view
|
|
reason() const
|
|
{
|
|
return fields.reason();
|
|
}
|
|
|
|
void
|
|
reason(string_view s)
|
|
{
|
|
fields.reason(s);
|
|
}
|
|
|
|
Fields fields;
|
|
};
|
|
```
|
|
|
|
Now that we've accomplished our initial goals and more, there is one small
|
|
quality of life improvement 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:
|
|
```
|
|
/// An HTTP header
|
|
template<bool isRequest, class Body, class Fields = fields>
|
|
struct header;
|
|
|
|
/// An HTTP message
|
|
template<bool isRequest, class Body, class Fields = fields>
|
|
struct message;
|
|
```
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
[endsect]
|