Doc tidying

fix #521, fix #524
This commit is contained in:
Vinnie Falco
2017-06-20 21:28:17 -07:00
parent bd4d1cbe91
commit b2ab40f09c
19 changed files with 949 additions and 754 deletions

View File

@@ -51,6 +51,8 @@
[def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] [def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]]
[def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] [def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]]
[def __async_initfn__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html initiating function]]
[def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]] [def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]]
[def __Body__ [link beast.concept.Body [*Body]]] [def __Body__ [link beast.concept.Body [*Body]]]
[def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]] [def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]]

View File

@@ -11,9 +11,9 @@ A __Stream__ is a communication channel where data is transferred as
an ordered sequence of octet buffers. Streams are either synchronous an ordered sequence of octet buffers. Streams are either synchronous
or asynchronous, and may allow reading, writing, or both. Note that or asynchronous, and may allow reading, writing, or both. Note that
a particular type may model more than one concept. For example, the a particular type may model more than one concept. For example, the
Asio types __socket__ and __ssl_stream__ and support everything. Asio types __socket__ and __ssl_stream__ support both __SyncStream__
All stream algorithms in Beast are declared as template functions and __AsyncStream__. All stream algorithms in Beast are declared as
with specific concept requirements chosen from this list: template functions using these concepts:
[table Stream Concepts [table Stream Concepts
[[Concept][Description]] [[Concept][Description]]

View File

@@ -35,7 +35,8 @@ These metafunctions check if types match the buffer concepts:
]] ]]
] ]
To suit various needs, several implementation of dynamic buffer are available: Beast provides several dynamic buffer implementations for a variety
of scenarios:
[table Dynamic Buffer Implementations [table Dynamic Buffer Implementations
[[Name][Description]] [[Name][Description]]
@@ -63,7 +64,8 @@ To suit various needs, several implementation of dynamic buffer are available:
Guarantees that input and output areas are buffer sequences with Guarantees that input and output areas are buffer sequences with
length one. Upon construction an optional upper limit to the total length one. Upon construction an optional upper limit to the total
size of the input and output areas may be set. The basic container size of the input and output areas may be set. The basic container
supports the standard allocator model. is an
[@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]].
]] ]]
[[ [[
[link beast.ref.beast__multi_buffer `multi_buffer`] [link beast.ref.beast__multi_buffer `multi_buffer`]
@@ -72,7 +74,8 @@ To suit various needs, several implementation of dynamic buffer are available:
Uses a sequence of one or more character arrays of varying sizes. Uses a sequence of one or more character arrays of varying sizes.
Additional character array objects are appended to the sequence to Additional character array objects are appended to the sequence to
accommodate changes in the size of the character sequence. The basic accommodate changes in the size of the character sequence. The basic
container supports the standard allocator model. container is an
[@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]].
]] ]]
[[ [[
[link beast.ref.beast__static_buffer `static_buffer`] [link beast.ref.beast__static_buffer `static_buffer`]
@@ -150,7 +153,7 @@ output streams.
[link beast.ref.beast__ostream `ostream`] [link beast.ref.beast__ostream `ostream`]
][ ][
This function returns a `std::ostream` which wraps a dynamic buffer. This function returns a `std::ostream` which wraps a dynamic buffer.
Characters sent to the stream using `operator<<` is stored in the Characters sent to the stream using `operator<<` are stored in the
dynamic buffer. dynamic buffer.
]] ]]
] ]

View File

@@ -5,40 +5,34 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
] ]
[section Composed Operations] [section Writing Composed Operations]
[block'''<?dbhtml stop-chunking?>'''] [block'''<?dbhtml stop-chunking?>''']
Asynchronous operations are started by calling a free function or member Asynchronous operations are started by calling a free function or member
function known as an ['asynchronous initiation function]. The initiation function known as an asynchronous ['__async_initfn__]. This function accepts
function accepts parameters specific to the operation as well as a "completion parameters specific to the operation as well as a "completion token." The
token." This token is either a completion handler, or a type defining how token is either a completion handler, or a type defining how the caller is
the caller is informed of the asynchronous operation result. __Asio__ comes informed of the asynchronous operation result. __Asio__ comes with the
with the special completion tokens __use_future__ and __yield_context__ for special tokens __use_future__ and __yield_context__ for using futures
using futures and coroutines respectively. This system of customizing the and coroutines respectively. This system of customizing the return value
return value and method of completion notification is known as the and method of completion notification is known as the
['Extensible Asynchronous Model] described in __N3747__, and a built in ['Extensible Asynchronous Model] described in __N3747__, and a built in
to __N4588__. to __N4588__. Here is an example of an initiating function which reads a
line from the stream and echoes it back. This function is developed
further in the next section:
[note [example_core_echo_op_1]
A full explanation of completion handlers, the Extensible Asynchronous
Model and how these asynchronous interfaces are used is beyond the
scope of this document. Interested readers should consult the
__Asio__ documentation.
]
Since the interfaces provided here are low level, authors of libraries Authors using Beast can reuse the library's primitives to create their
may wish to create higher level interfaces using the primitives found own initiating functions for performing a series of other, intermediate
in this library. Non-trivial applications will want to provide their own asynchronous operations before invoking a final completion handler.
asynchronous initiation functions which perform a series of other, The set of intermediate actions produced by an initiating function is
intermediate asynchronous operations before invoking the final completion known as a
handler. The set of intermediate actions produced by calling an initiation
function is known as a
[@http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html ['composed operation]]. [@http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html ['composed operation]].
To ensure full interoperability and well-defined behavior, __Asio__ imposes To ensure full interoperability and well-defined behavior, __Asio__ imposes
requirements on the implementation of composed operations. A number of useful requirements on the implementation of composed operations. These classes
classes and macros to facilitate the development of composed operations and and functions make it easier to develop initiating functions and their
the associated asynchronous initiation functions used to launch them are composed operations:
available:
[table Asynchronous Helpers [table Asynchronous Helpers
[[Name][Description]] [[Name][Description]]
@@ -105,8 +99,8 @@ available:
[section Echo] [section Echo]
Here we develop an asynchronous composed operation called [*echo]. This example develops an initiating function called [*echo].
This operation will read up to the first newline on a stream, and The operation will read up to the first newline on a stream, and
then write the same line including the newline back on the stream. then write the same line including the newline back on the stream.
The implementation performs both reading and writing, and has a The implementation performs both reading and writing, and has a
non-trivially-copyable state. non-trivially-copyable state.
@@ -115,16 +109,16 @@ initiation function. For our echo operation the only inputs are the
stream and the completion token. The output is the error code which stream and the completion token. The output is the error code which
is usually included in all completion handler signatures. is usually included in all completion handler signatures.
[example_core_echo_op_1] [example_core_echo_op_2]
Now that we have a declaration, we will define the body of the function. Now that we have a declaration, we will define the body of the function.
We want to achieve the following goals: perform static type checking on We want to achieve the following goals: perform static type checking on
the input parameters, set up the return value as per __N3747__, and launch the input parameters, set up the return value as per __N3747__, and launch
the composed operation by constructing the object and invoking it. the composed operation by constructing the object and invoking it.
[example_core_echo_op_2] [example_core_echo_op_3]
The initiation function contains a few relatively simple parts. There is The initiating function contains a few relatively simple parts. There is
the customization of the return value type, static type checking, building the customization of the return value type, static type checking, building
the return value type using the helper, and creating and launching the the return value type using the helper, and creating and launching the
composed operation object. The [*`echo_op`] object does most of the work composed operation object. The [*`echo_op`] object does most of the work
@@ -136,12 +130,11 @@ without explaining them in depth.
Here is the boilerplate present in all composed operations written Here is the boilerplate present in all composed operations written
in this style: in this style:
[example_core_echo_op_3] [example_core_echo_op_4]
We have the common boilerplate for a composed operation and now we just need Next is to implement the function call operator. Our strategy is to make our
to implement the function call operator. Our strategy is to make our composed composed object meet the requirements of a completion handler by being copyable
object meet the requirements of a completion handler by being copyable (also (also movable), and by providing the function call operator with the correct
movable), and by providing the function call operator with the correct
signature. Rather than using `std::bind` or `boost::bind`, which destroys signature. Rather than using `std::bind` or `boost::bind`, which destroys
the type information and therefore breaks the allocation and invocation the type information and therefore breaks the allocation and invocation
hooks, we will simply pass `std::move(*this)` as the completion handler hooks, we will simply pass `std::move(*this)` as the completion handler
@@ -150,7 +143,88 @@ care must be taken to ensure that no access to data members are made after the
move takes place. Here is the implementation of the function call operator for move takes place. Here is the implementation of the function call operator for
this echo operation: this echo operation:
[example_core_echo_op_4] [example_core_echo_op_5]
This is the most important element of writing a composed operation, and
the part which is often neglected or implemented incorrectly. It is the
declaration and definition of the "handler hooks". There are four hooks:
[table Handler Hooks
[[Name][Description]]
[[
[@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]
][
Default invoke function for handlers. This hooking function ensures
that the invoked method used for the final handler is accessible at
each intermediate step.
]]
[[
[@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`]
][
Default allocation function for handlers. Implement `asio_handler_allocate`
and `asio_handler_deallocate` for your own handlers to provide custom
allocation for temporary objects.
]]
[[
[@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_deallocate.html `asio_handler_deallocate`]
][
Default deallocation function for handlers. Implement `asio_handler_allocate`
and `asio_handler_deallocate` for your own handlers to provide custom
allocation for temporary objects.
]]
[[
[@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_is_continuation.html `asio_handler_is_continuation`]
][
Default continuation function for handlers. Implement
`asio_handler_is_continuation` for your own handlers to indicate when
a handler represents a continuation.
]]
]
Our composed operation stores the final handler and performs its own
intermediate asynchronous operations. To ensure that I/O objects, in this
case the stream, are accessed safely it is important to use the same method
to invoke intermediate handlers as that used to invoke the final handler.
Similarly, for the memory allocation hooks our composed operation should use
the same hooks as those used by the final handler. And finally for the
`asio_is_continuation` hook, we want to return `true` for any intermediate
asynchronous operations we perform after the first one, since those represent
continuations. For the first asynchronous operation we perform, the hook should
return `true` only if the final handler also represents a continuation. Our
implementation of the hooks will forward the call to the corresponding
overloads of the final handler:
[example_core_echo_op_6]
There are some common mistakes that should be avoided when writing
composed operations:
* Type erasing the final handler. This will cause undefined behavior.
* Not using `std::addressof` to get the address of the handler.
* Forgetting to include a return statement after calling an
initiating function.
* Calling a synchronous function by accident. In general composed
operations should not block for long periods of time, since this
ties up a thread running on the __io_service__.
* Forgetting to overload `asio_handler_invoke` for the composed
operation. This will cause undefined behavior if someone calls
the initiating function with a strand-wrapped function object,
and there is more than thread running on the `io_service`.
* For operations which complete immediately (i.e. without calling an
intermediate initiating function), forgetting to use `io_service::post`
to invoke the final handler. This breaks the following initiating
function guarantee: ['Regardless of whether the asynchronous operation
completes immediately or not, the handler will not be invoked from
within this function. Invocation of the handler will be performed
in a manner equivalent to using `boost::asio::io_service::post`].
The function
[link beast.ref.beast__bind_handler `bind_handler`]
is provided for this purpose.
A complete, runnable version of this example may be found in the examples A complete, runnable version of this example may be found in the examples
directory. directory.

View File

@@ -9,7 +9,7 @@
In this example we will build a simple function to detect the presence In this example we will build a simple function to detect the presence
of the SSL handshake given an input buffer sequence. Then we build on of the SSL handshake given an input buffer sequence. Then we build on
the example by adding synchronous stream algorithms. Finally, we the example by adding a synchronous stream algorithm. Finally, we
implemement an asynchronous detection function using a composed operation. implemement an asynchronous detection function using a composed operation.
This SSL detector may be used to allow a server to accept both SSL/TLS and This SSL detector may be used to allow a server to accept both SSL/TLS and
unencrypted connections at the same port. unencrypted connections at the same port.
@@ -32,11 +32,11 @@ synchronous version which takes the stream and buffer as input:
The synchronous algorithm is the model for building the asynchronous The synchronous algorithm is the model for building the asynchronous
operation which has more boilerplate. First, we declare the asynchronous operation which has more boilerplate. First, we declare the asynchronous
initiation function: initiating function:
[example_core_detect_ssl_4] [example_core_detect_ssl_4]
The implementation of the initiation function is straightforward The implementation of the initiating function is straightforward
and contains mostly boilerplate. It is to construct the return and contains mostly boilerplate. It is to construct the return
type customization helper to obtain the actual handler, and type customization helper to obtain the actual handler, and
then create the composed operation and launch it. The actual then create the composed operation and launch it. The actual
@@ -51,7 +51,7 @@ is worth the effort.
[example_core_detect_ssl_6] [example_core_detect_ssl_6]
The boilerplate is all done, and now we need to implemnt the function The boilerplate is all done, and now we need to implement the function
call operator that turns this composed operation a completion handler call operator that turns this composed operation a completion handler
with the signature `void(error_code, std::size_t)` which is exactly with the signature `void(error_code, std::size_t)` which is exactly
the signature needed when performing asynchronous reads. This function the signature needed when performing asynchronous reads. This function

View File

@@ -9,15 +9,19 @@
The HTTP protocol defines the The HTTP protocol defines the
[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]: [@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]:
clients send requests and servers send back responses. A request clients send requests and servers send back responses. When a client and
or response is an server have established a connection, the client sends a series of requests
while the server sends back at least one response for each received request
in the order those requests were received.
A request or response is an
[@https://tools.ietf.org/html/rfc7230#section-3 HTTP message] [@https://tools.ietf.org/html/rfc7230#section-3 HTTP message]
(referred to hereafter as "message"), with two parts: (referred to hereafter as "message") having two parts:
a header containing structured metadata and an optional variable-length a header with structured metadata and an optional variable-length body
body containing arbitrary data. A serialized header is one or more text holding arbitrary data. A serialized header is one or more text lines
lines where each line ends in a carriage return followed by linefeed where each line ends in a carriage return followed by linefeed (`"\r\n"`).
(`"\r\n"`). An empty line marks the end of the header. The first line An empty line marks the end of the header. The first line in the header
in the header is called the ['start-line]. The start line contents are is called the ['start-line]. The contents of the start line contents are
different for requests and responses. different for requests and responses.
Every message contains a set of zero or more field name/value pairs, Every message contains a set of zero or more field name/value pairs,
@@ -26,11 +30,6 @@ text strings with various requirements. A serialized field contains the
field name, then a colon followed by a space (`": "`), and finally the field field name, then a colon followed by a space (`": "`), and finally the field
value with a trailing CRLF. value with a trailing CRLF.
When a client and server have established a connection and intend to
use HTTP, the client sends a series of requests while the server reads
sends back at least one response for each request in the order those
requests were received.
[heading Requests] [heading Requests]
Clients send requests, which contain a Clients send requests, which contain a
@@ -90,11 +89,21 @@ field informs the remote host of the size of the body which follows.
]] ]]
] ]
[heading Body]
Messages may optionally carry a body. The size of the message body
is determined by the semantics of the message and the special fields
Content-Length and Transfer-Encoding.
[@https://tools.ietf.org/html/rfc7230#section-3.3 rfc7230 section 3.3]
provides a comprehensive description for how the body length is
determined.
[heading Special Fields] [heading Special Fields]
Certain fields appearing in messages are special. The library understands Certain fields appearing in messages are special. The library understands
these fields when performing serialization and parsing, taking automatic these fields when performing serialization and parsing, taking automatic
action as needed when the fields are present: action as needed when the fields are parsed in a message and also setting
the fields if the caller requests it.
[table Special Fields [table Special Fields
[[Field][Description]] [[Field][Description]]
@@ -126,8 +135,8 @@ action as needed when the fields are present:
Beast understands the "chunked" coding scheme when it is the last Beast understands the "chunked" coding scheme when it is the last
(outermost) applied coding. The library will automatically apply (outermost) applied coding. The library will automatically apply
chunked encoding when the content length is not known ahead of time chunked encoding when the content length is not known ahead of time
during serialization, and the library will automatically removed chunked during serialization, and the library will automatically remove chunked
encoding from parsed messages. encoding from parsed messages when present.
] ]
][ ][
[ [

View File

@@ -44,22 +44,21 @@ class header;
Requests and responses share the version, fields, and body but have Requests and responses share the version, fields, and body but have
a few members unique to the type. This is implemented by declaring the a few members unique to the type. This is implemented by declaring the
header classes as partial specializations of `isRequest`. Furthermore, header classes as partial specializations of `isRequest`. __message__
__message__ is derived from __header__; a message may be passed as an is derived from __header__; a message may be passed as an argument to
argument to a function taking a suitably typed header as a parameter. a function taking a suitably typed header as a parameter. Additionally,
Furthermore, `header` is publicly derived from `Fields`; a message `header` is publicly derived from `Fields`; a message inherits all the
inherits all of the fields member functions. Since `fields` is a member functions of `Fields`. This diagram shows the inheritance
container, iterating a message iterates its header fields. This relationship between header and message, along with some of the
diagram shows the inheritance relationship between header and message, notable differences in members in each partial specialization:
along with the fields from the different partial specializations for
each possible value of `isRequest`:
[$images/message.png [width 730px] [height 410px]] [$images/message.png [width 730px] [height 410px]]
The template type aliases The template type aliases
[link beast.ref.beast__http__request `request`] and [link beast.ref.beast__http__request `request`] and
[link beast.ref.beast__http__response `response`] [link beast.ref.beast__http__response `response`]
are provided for brevity. They specify the common default of `fields`. are provided for brevity. They specify __fields__ as the default for [*Fields].
``` ```
/// A typical HTTP request /// A typical HTTP request
@@ -77,9 +76,8 @@ Beast defines the __Body__ concept, which determines both the type of
the [link beast.ref.beast__http__message.body `message::body`] member the [link beast.ref.beast__http__message.body `message::body`] member
(as seen in the diagram above) and may also include algorithms for (as seen in the diagram above) and may also include algorithms for
transferring buffers in and out. These algorithms are used during transferring buffers in and out. These algorithms are used during
parsing and serialization. These body types are available within the parsing and serialization. Users may define their own body types which
library, and users may define their own body types which meet the meet the requirements, or use the ones that come with the library:
__Body__ requirements:
[table [table
[[Name][Description]] [[Name][Description]]
@@ -130,10 +128,10 @@ __Body__ requirements:
[heading Usage] [heading Usage]
The code examples below show how to create and fill in request and response These examples show how to create and fill in request and response
objects: Here is simple example of building an objects: Here we build an
[@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET] [@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET]
request. request with an empty message body:
[table Create Request [table Create Request
[[Statements] [Serialized Result]] [[Statements] [Serialized Result]]
@@ -149,12 +147,12 @@ request.
]] ]]
] ]
Here we create an HTTP response indicating success. Note that this In this code we create an HTTP response with a status code indicating success.
message has a body. The function This message has a body with a non-zero length. The function
[link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`]
automatically sets the Content-Length or Transfer-Encoding field automatically sets the Content-Length or Transfer-Encoding field
depending on the content and type of the `body` member. The use depending on the content and type of the `body` member. Use of this function
of prepare is optional; these fields may also be set explicitly. is optional; these fields may also be set explicitly.
[table Create Response [table Create Response
[[Statements] [Serialized Result]] [[Statements] [Serialized Result]]

View File

@@ -67,11 +67,11 @@ into a single buffer for parsing.
] ]
Messages may also be read asynchronously. When performing asynchronous Messages may also be read asynchronously. When performing asynchronous
stream read operations, the buffer and message variables must remain stream read operations the stream, buffer, and message variables must
valid until the operation has completed. Beast asynchronous initiation remain valid until the operation has completed. Beast asynchronous
functions use Asio's completion handler model. Here we read a message initiation functions use Asio's completion handler model. This call
asynchronously. When the operation completes the message in the error reads a message asynchronously and report the error code upon
code indicating the result is printed: completion:
[http_snippet_5] [http_snippet_5]
@@ -79,8 +79,8 @@ If a read stream algorithm cannot complete its operation without exceeding
the maximum specified size of the dynamic buffer provided, the error the maximum specified size of the dynamic buffer provided, the error
[link beast.ref.beast__http__error `buffer_overflow`] [link beast.ref.beast__http__error `buffer_overflow`]
is returned. This may be used to impose a limit on the maximum size of an is returned. This may be used to impose a limit on the maximum size of an
HTTP message header for protection from buffer overflow attacks. The following HTTP message header for protection from buffer overflow attacks. The
code will generate an error: following code will print the error message:
[http_snippet_6] [http_snippet_6]
@@ -89,9 +89,9 @@ code will generate an error:
[heading Writing] [heading Writing]
A set of free functions allow serialization of an entire HTTP message to A set of free functions allow serialization of an entire HTTP message to
a stream. If a response has no declared content length, and no chunked a stream. If a response has no declared content length and no chunked
transfer encoding, the end of the message is indicated by the server closing transfer encoding, then the end of the message is indicated by the server
the connection. When sending such a response, Beast will return the closing the connection. When sending such a response, Beast will return the
[link beast.ref.beast__http__error `error::end_of_stream`] [link beast.ref.beast__http__error `error::end_of_stream`]
from the write algorithm to indicate from the write algorithm to indicate
to the caller that the connection should be closed. This example to the caller that the connection should be closed. This example

View File

@@ -32,7 +32,7 @@ These relations are true:
* `P(S(m)) == m` * `P(S(m)) == m`
We would also like our message container to have customization points We would also like our message container to have customization points
permitting the following: full allocator support, user-defined containers permitting the following: allocator awareness, user-defined containers
to represent header fields, and user-defined types and algorithms to to represent header fields, and user-defined types and algorithms to
represent the body. And finally, because requests and responses have represent the body. And finally, because requests and responses have
different fields in the ['start-line], we would like the containers for different fields in the ['start-line], we would like the containers for
@@ -48,10 +48,11 @@ Here is our first attempt at declaring some message containers:
template<class Fields, class Body> template<class Fields, class Body>
struct request struct request
{ {
int version; int version;
std::string method; std::string method;
std::string target; std::string target;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -61,10 +62,11 @@ struct request
template<class Fields, class Body> template<class Fields, class Body>
struct response struct response
{ {
int version; int version;
int status; int status;
std::string reason; std::string reason;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -83,8 +85,8 @@ 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 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 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 classes would then be needed to make sure that the passed object has a
valid type which meets the requirements. Instead, bypass those complexities valid type which meets the requirements. These unnecessary complexities
by making each container a partial specialization of one class: are bypassed by making each container a partial specialization:
``` ```
/// An HTTP message /// An HTTP message
template<bool isRequest, class Fields, class Body> template<bool isRequest, class Fields, class Body>
@@ -94,10 +96,11 @@ struct message;
template<class Fields, class Body> template<class Fields, class Body>
struct message<true, Fields, Body> struct message<true, Fields, Body>
{ {
int version; int version;
std::string method; std::string method;
std::string target; std::string target;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
@@ -105,10 +108,11 @@ struct message<true, Fields, Body>
template<bool isRequest, class Fields, class Body> template<bool isRequest, class Fields, class Body>
struct message<false, Fields, Body> struct message<false, Fields, Body>
{ {
int version; int version;
int status; int status;
std::string reason; std::string reason;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -121,7 +125,7 @@ void f(message<isRequest, Fields, Body>& msg);
This function can manipulate the fields common to requests and responses. 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 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>`. with an object of type `std::integral_constant<bool, isRequest>`.
Often, in non-trivial HTTP applications, we want to read the HTTP header 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 and examine its contents before choosing a type for [*Body]. To accomplish
@@ -129,30 +133,36 @@ 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 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 header as a parameter, to also accept a type representing the whole
message (the function will see just the header part). This suggests message (the function will see just the header part). This suggests
inheritance: inheritance, by splitting a new base class off of the message:
``` ```
/// An HTTP message header /// An HTTP message header
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
struct header; struct header;
```
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 /// An HTTP request header
template<class Fields> template<class Fields>
struct header<true, Fields> struct header<true, Fields> : Fields
{ {
int version; int version;
std::string method; std::string method;
std::string target; std::string target;
Fields fields;
}; };
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int status;
std::string reason; std::string reason;
Fields fields;
}; };
/// An HTTP message /// An HTTP message
@@ -170,7 +180,7 @@ struct message : header<isRequest, Fields>
Note that the `message` class now has a constructor allowing messages Note that the `message` class now has a constructor allowing messages
to be constructed from a similarly typed `header`. This handles the case 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 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: type for [*Body]. A function can be declared which accepts any header:
``` ```
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
void f(header<isRequest, Fields>& msg); void f(header<isRequest, Fields>& msg);
@@ -202,21 +212,22 @@ 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 different allocators: two for the fields and body, and two for the method
and target strings. A better solution is needed. and target strings. A better solution is needed.
To get around this we make a simple change to the interface and then To get around this we make an interface modification and then add
engineer a clever concession. First, the interface change: a requirement to the [*Fields] type. First, the interface change:
``` ```
/// An HTTP request header /// An HTTP request header
template<class Fields> template<class Fields>
struct header<true, Fields> struct header<true, Fields> : Fields
{ {
int version; int version;
verb method() const;
verb method() const;
string_view method_string() const; string_view method_string() const;
void method(verb); void method(verb);
void method(string_view); void method(string_view);
string_view target(); const; string_view target(); const;
void target(string_view); void target(string_view);
Fields fields;
private: private:
verb method_; verb method_;
@@ -224,13 +235,12 @@ private:
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int result;
string_view reason() const; string_view reason() const;
void reason(string_view); void reason(string_view);
Fields fields;
}; };
``` ```
@@ -239,40 +249,41 @@ non-owning references to string buffers. The method is stored using
a simple integer instead of the entire string, for the case where a simple integer instead of the entire string, for the case where
the method is recognized from the set of known verb strings. the method is recognized from the set of known verb strings.
Now we make a concession: management of the corresponding string is Now we add a requirement to the fields type: management of the
delegated to the [*Fields] container, which can already be allocator corresponding string is delegated to the [*Fields] container, which can
aware and constructed with the necessary allocator parameter via the already be allocator aware and constructed with the necessary allocator
provided constructor overloads for `message`. The delegation parameter via the provided constructor overloads for `message`. The
implementation looks like this (only the response header specialization delegation implementation looks like this (only the response header
is shown): specialization is shown):
``` ```
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int status;
string_view string_view
reason() const reason() const
{ {
return fields.reason_impl(); return this->reason_impl(); // protected member of Fields
} }
void void
reason(string_view s) reason(string_view s)
{ {
fields.reason_impl(s); this->reason_impl(s); // protected member of Fields
} }
Fields fields;
}; };
``` ```
Now that we've accomplished our initial goals and more, there is one small Now that we've accomplished our initial goals and more, there are a few
quality of life improvement to make. Users will choose different types for more quality of life improvements to make. Users will choose different
`Body` far more often than they will for `Fields`. Thus, we swap the order types for `Body` far more often than they will for `Fields`. Thus, we
of these types and provide a default: 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:
``` ```
/// An HTTP header /// An HTTP header
template<bool isRequest, class Body, class Fields = fields> template<bool isRequest, class Body, class Fields = fields>
@@ -281,6 +292,22 @@ struct header;
/// An HTTP message /// An HTTP message
template<bool isRequest, class Body, class Fields = fields> template<bool isRequest, class Body, class Fields = fields>
struct message; struct message;
/// An HTTP request
template<class Body, class Fields = fields>
using request = message<true, Body, Fields>;
/// An HTTP response
template<class Body, class Fields = fields>
using response = message<false, Body, Fields>;
```
This allows concise specification for the common cases, while
allowing for maximum customization for edge cases:
```
request<string_body> req;
response<file_body> res;
``` ```
This container is also capable of representing complete HTTP/2 messages. This container is also capable of representing complete HTTP/2 messages.

View File

@@ -13,417 +13,431 @@
How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp],
an alternate header-only WebSocket implementation? an alternate header-only WebSocket implementation?
][ ][
[variablelist [variablelist
[[1. Synchronous Interface][ [[1. Synchronous Interface][
Beast offers full support for WebSockets using a synchronous interface. It Beast offers full support for WebSockets using a synchronous interface. It
uses the same style of interfaces found in Boost.Asio: versions that throw uses the same style of interfaces found in Boost.Asio: versions that throw
exceptions, or versions that return the error code in a reference parameter: exceptions, or versions that return the error code in a reference parameter:
[table [table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
[websocketpp]
][
[```
template<class DynamicBuffer>
void
read(DynamicBuffer& dynabuf)
```]
[ [
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] /<not available>/
[websocketpp] ]
][ ]]]]
[```
template<class DynamicBuffer>
void
read(opcode& op, DynamicBuffer& dynabuf)
```]
[
/<not available>/
]
]]]]
[[2. Connection Model][ [[2. Connection Model][
websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` websocketpp supports multiple transports by utilizing a trait, the `config::transport_type`
([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) ([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example])
To get an idea of the complexity involved with implementing a transport, To get an idea of the complexity involved with implementing a transport,
compare the asio transport to the compare the asio transport to the
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport]
(a layer that allows websocket communication over a `std::iostream`). (a layer that allows websocket communication over a `std::iostream`).
In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] In contrast, Beast abstracts the transport by defining just one [*`NextLayer`]
template argument The type requirements for [*`NextLayer`] are template argument The type requirements for [*`NextLayer`] are
already familiar to users as they are documented in Asio: already familiar to users as they are documented in Asio:
__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. __AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__.
The type requirements for instantiating `beast::websocket::stream` versus The type requirements for instantiating `beast::websocket::stream` versus
`websocketpp::connection` with user defined types are vastly reduced `websocketpp::connection` with user defined types are vastly reduced
(18 functions versus 2). Note that websocketpp connections are passed by (18 functions versus 2). Note that websocketpp connections are passed by
`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. `shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface.
A `beast::websocket::stream` is constructible and movable in a manner identical A `beast::websocket::stream` is constructible and movable in a manner identical
to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a
`shared_ptr` if they want to, but there is no requirement to do so. `shared_ptr` if they want to, but there is no requirement to do so.
[table [table
[ [
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]]
][ ][
[``` [```
template<class NextLayer> template<class NextLayer>
class stream class stream
{ {
NextLayer next_layer_; NextLayer next_layer_;
...
}
```]
[```
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
public:
typedef lib::shared_ptr<type> ptr;
...
}
```]
]]]]
[[3. Client and Server Role][
websocketpp provides multi-role support through a hierarchy of
different classes. A `beast::websocket::stream` is role-agnostic, it
offers member functions to perform both client and server handshakes
in the same class. The same types are used for client and server
streams.
[table
[
[Beast]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp],
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]]
][
[
/<not needed>/
]
[```
template <typename config>
class client : public endpoint<connection<config>,config>;
template <typename config>
class server : public endpoint<connection<config>,config>;
```]
]]]]
[[4. Thread Safety][
websocketpp uses mutexes to protect shared data from concurrent
access. In contrast, Beast does not use mutexes anywhere in its
implementation. Instead, it follows the Asio pattern. Calls to
asynchronous initiation functions use the same method to invoke
intermediate handlers as the method used to invoke the final handler,
through the __asio_handler_invoke__ mechanism.
The only requirement in Beast is that calls to asynchronous initiation
functions are made from the same implicit or explicit strand. For
example, if the `io_service` associated with a `beast::websocket::stream`
is single threaded, this counts as an implicit strand and no performance
costs associated with mutexes are incurred.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]]
][
[```
template <class Function>
friend
void asio_handler_invoke(Function&& f, read_frame_op* op)
{
return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h);
}
```]
[```
mutex_type m_read_mutex;
```]
]]]]
[[5. Callback Model][
websocketpp requires a one-time call to set the handler for each event
in its interface (for example, upon message receipt). The handler is
represented by a `std::function` equivalent. Its important to recognize
that the websocketpp interface performs type-erasure on this handler.
In comparison, Beast handlers are specified in a manner identical to
Boost.Asio. They are function objects which can be copied or moved but
most importantly they are not type erased. The compiler can see
through the type directly to the implementation, permitting
optimization. Furthermore, Beast follows the Asio rules for treatment
of handlers. It respects any allocation, continuation, or invocation
customizations associated with the handler through the use of argument
dependent lookup overloads of functions such as `asio_handler_allocate`.
The Beast completion handler is provided at the call site. For each
call to an asynchronous initiation function, it is guaranteed that
there will be exactly one final call to the handler. This functions
exactly the same way as the asynchronous initiation functions found in
Boost.Asio, allowing the composition of higher level abstractions.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp],
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]]
][
[```
template<class DynamicBuffer, class ReadHandler>
typename async_completion<ReadHandler, void(error_code)>::result_type
async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler);
```]
[```
typedef lib::function<void(connection_hdl,message_ptr)> message_handler;
void set_message_handler(message_handler h);
```]
]]]]
[[6. Extensible Asynchronous Model][
Beast fully supports the
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model]
developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8).
Beast websocket asynchronous interfaces may be used seamlessly with
`std::future` stackful/stackless coroutines, or user defined customizations.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]]
[websocketpp]
][
[```
beast::async_completion<ReadHandler, void(error_code)> completion(handler);
read_op<DynamicBuffer, decltype(completion.handler)>{
completion.handler, *this, op, buffer};
return completion.result.get();
```]
[
/<not available>/
]
]]]]
[[7. Message Buffering][
websocketpp defines a message buffer, passed in arguments by
`shared_ptr`, and an associated message manager which permits
aggregation and reuse of memory. The implementation of
`websocketpp::message` uses a `std::string` to hold the payload. If an
incoming message is broken up into multiple frames, the string may be
reallocated for each continuation frame. The `std::string` always uses
the standard allocator, it is not possible to customize the choice of
allocator.
Beast allows callers to specify the object for receiving the message
or frame data, which is of any type meeting the requirements of
__DynamicBuffer__ (modeled after `boost::asio::streambuf`).
Beast comes with the class __basic_multi_buffer__, an efficient
implementation of the __DynamicBuffer__ concept which makes use of multiple
allocated octet arrays. If an incoming message is broken up into
multiple pieces, no reallocation occurs. Instead, new allocations are
appended to the sequence when existing allocations are filled. Beast
does not impose any particular memory management model on callers. The
__basic_multi_buffer__ provided by beast supports standard allocators through
a template argument. Use the __DynamicBuffer__ that comes with beast,
customize the allocator if you desire, or provide your own type that
meets the requirements.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]]
][
[```
template<class DynamicBuffer>
read(opcode& op, DynamicBuffer& dynabuf);
```]
[```
template <template<class> class con_msg_manager>
class message {
public:
typedef lib::shared_ptr<message> ptr;
...
std::string m_payload;
...
};
```]
]]]]
[[8. Sending Messages][
When sending a message, websocketpp requires that the payload is
packaged in a `websocketpp::message` object using `std::string` as the
storage, or it requires a copy of the caller provided buffer by
constructing a new message object. Messages are placed onto an
outgoing queue. An asynchronous write operation runs in the background
to clear the queue. No user facing handler can be registered to be
notified when messages or frames have completed sending.
Beast doesn't allocate or make copies of buffers when sending data. The
caller's buffers are sent in-place. You can use any object meeting the
requirements of
[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence],
permitting efficient scatter-gather I/O.
The [*ConstBufferSequence] interface allows callers to send data from
memory-mapped regions (not possible in websocketpp). Callers can also
use the same buffers to send data to multiple streams, for example
broadcasting common subscription data to many clients at once. For
each call to `async_write` the completion handler is called once when
the data finishes sending, in a manner identical to `boost::asio::async_write`.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]]
][
[```
template<class ConstBufferSequence>
void
write(ConstBufferSequence const& buffers);
```]
[```
lib::error_code send(std::string const & payload,
frame::opcode::value op = frame::opcode::text);
... ...
lib::error_code send(message_ptr msg); }
```] ```]
]]]] [```
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
public:
typedef lib::shared_ptr<type> ptr;
...
}
```]
]]]]
[[9. Streaming Messages][ [[3. Client and Server Role][
websocketpp requires that the entire message fit into memory, and that websocketpp provides multi-role support through a hierarchy of
the size is known ahead of time. different classes. A `beast::websocket::stream` is role-agnostic, it
offers member functions to perform both client and server handshakes
in the same class. The same types are used for client and server
streams.
Beast allows callers to compose messages in individual frames. This is [table
useful when the size of the data is not known ahead of time or if it [
is not desired to buffer the entire message in memory at once before [Beast]
sending it. For example, sending periodic output of a database query [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp],
running on a coroutine. Or sending the contents of a file in pieces, [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]]
without bringing it all into memory. ][
[table
[ [
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] /<not needed>/
[websocketpp] ]
][ [```
[``` template <typename config>
template<class ConstBufferSequence> class client : public endpoint<connection<config>,config>;
void template <typename config>
write_frame(bool fin, class server : public endpoint<connection<config>,config>;
ConstBufferSequence const& buffers); ```]
```] ]]]]
[
/<not available>/
]
]]]]
[[10. Flow Control][ [[4. Thread Safety][
The websocketpp read implementation continuously reads asynchronously websocketpp uses mutexes to protect shared data from concurrent
from the network and buffers message data. To prevent unbounded growth access. In contrast, Beast does not use mutexes anywhere in its
and leverage TCP/IP's flow control mechanism, callers can periodically implementation. Instead, it follows the Asio pattern. Calls to
turn this 'read pump' off and back on. asynchronous initiation functions use the same method to invoke
intermediate handlers as the method used to invoke the final handler,
through the __asio_handler_invoke__ mechanism.
In contrast a `beast::websocket::stream` does not independently begin The only requirement in Beast is that calls to asynchronous initiation
background activity, nor does it buffer messages. It receives data only functions are made from the same implicit or explicit strand. For
when there is a call to an asynchronous initiation function (for example, if the `io_service` associated with a `beast::websocket::stream`
example `beast::websocket::stream::async_read`) with an associated handler. is single threaded, this counts as an implicit strand and no performance
Applications do not need to implement explicit logic to regulate the costs associated with mutexes are incurred.
flow of data. Instead, they follow the traditional model of issuing a
read, receiving a read completion, processing the message, then
issuing a new read and repeating the process.
[table [table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]]
][
[```
template <class Function>
friend
void asio_handler_invoke(Function&& f, read_frame_op* op)
{
return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h);
}
```]
[```
mutex_type m_read_mutex;
```]
]]]]
[[5. Callback Model][
websocketpp requires a one-time call to set the handler for each event
in its interface (for example, upon message receipt). The handler is
represented by a `std::function` equivalent. Its important to recognize
that the websocketpp interface performs type-erasure on this handler.
In comparison, Beast handlers are specified in a manner identical to
Boost.Asio. They are function objects which can be copied or moved but
most importantly they are not type erased. The compiler can see
through the type directly to the implementation, permitting
optimization. Furthermore, Beast follows the Asio rules for treatment
of handlers. It respects any allocation, continuation, or invocation
customizations associated with the handler through the use of argument
dependent lookup overloads of functions such as `asio_handler_allocate`.
The Beast completion handler is provided at the call site. For each
call to an asynchronous initiation function, it is guaranteed that
there will be exactly one final call to the handler. This functions
exactly the same way as the asynchronous initiation functions found in
Boost.Asio, allowing the composition of higher level abstractions.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp],
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]]
][
[```
template<
class DynamicBuffer, // Supports user defined types
class ReadHandler // Handler is NOT type-erased
>
typename async_completion< // Return value customization
ReadHandler, // supports futures and coroutines
void(error_code)
>::result_type
async_read(
DynamicBuffer& dynabuf,
ReadHandler&& handler);
```]
[```
typedef lib::function<
void(connection_hdl,message_ptr)
> message_handler;
void set_message_handler(message_handler h);
```]
]]]]
[[6. Extensible Asynchronous Model][
Beast fully supports the
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model]
developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8).
Beast websocket asynchronous interfaces may be used seamlessly with
`std::future` stackful/stackless coroutines, or user defined customizations.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]]
[websocketpp]
][
[```
beast::async_completion<
ReadHandler,
void(error_code)> completion{handler};
read_op<
DynamicBuffer, decltype(completion.handler)>{
completion.handler, *this, op, buffer};
return completion.result.get(); // Customization point
```]
[ [
[Beast] /<not available>/
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] ]
][ ]]]]
[
/<implicit>/
]
[```
lib::error_code pause_reading();
lib::error_code resume_reading();
```]
]]]]
[[11. Connection Establishment][ [[7. Message Buffering][
websocketpp offers the `endpoint` class which can handle binding and websocketpp defines a message buffer, passed in arguments by
listening to a port, and spawning connection objects. `shared_ptr`, and an associated message manager which permits
aggregation and reuse of memory. The implementation of
`websocketpp::message` uses a `std::string` to hold the payload. If an
incoming message is broken up into multiple frames, the string may be
reallocated for each continuation frame. The `std::string` always uses
the standard allocator, it is not possible to customize the choice of
allocator.
Beast does not reinvent the wheel here, callers use the interfaces Beast allows callers to specify the object for receiving the message
already in `boost::asio` for receiving incoming connections resolving or frame data, which is of any type meeting the requirements of
host names, or establishing outgoing connections. After the socket (or __DynamicBuffer__ (modeled after `boost::asio::streambuf`).
`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream`
is constructed around it and the WebSocket handshake can be performed.
Beast users are free to implement their own "connection manager", but Beast comes with the class __basic_multi_buffer__, an efficient
there is no requirement to do so. implementation of the __DynamicBuffer__ concept which makes use of multiple
allocated octet arrays. If an incoming message is broken up into
multiple pieces, no reallocation occurs. Instead, new allocations are
appended to the sequence when existing allocations are filled. Beast
does not impose any particular memory management model on callers. The
__basic_multi_buffer__ provided by beast supports standard allocators through
a template argument. Use the __DynamicBuffer__ that comes with beast,
customize the allocator if you desire, or provide your own type that
meets the requirements.
[table [table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]]
][
[```
template<class DynamicBuffer>
read(DynamicBuffer& dynabuf);
```]
[```
template <template<class> class con_msg_manager>
class message {
public:
typedef lib::shared_ptr<message> ptr;
...
std::string m_payload;
...
};
```]
]]]]
[[8. Sending Messages][
When sending a message, websocketpp requires that the payload is
packaged in a `websocketpp::message` object using `std::string` as the
storage, or it requires a copy of the caller provided buffer by
constructing a new message object. Messages are placed onto an
outgoing queue. An asynchronous write operation runs in the background
to clear the queue. No user facing handler can be registered to be
notified when messages or frames have completed sending.
Beast doesn't allocate or make copies of buffers when sending data. The
caller's buffers are sent in-place. You can use any object meeting the
requirements of
[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence],
permitting efficient scatter-gather I/O.
The [*ConstBufferSequence] interface allows callers to send data from
memory-mapped regions (not possible in websocketpp). Callers can also
use the same buffers to send data to multiple streams, for example
broadcasting common subscription data to many clients at once. For
each call to `async_write` the completion handler is called once when
the data finishes sending, in a manner identical to `boost::asio::async_write`.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]]
][
[```
template<class ConstBufferSequence>
void
write(ConstBufferSequence const& buffers);
```]
[```
lib::error_code send(std::string const & payload,
frame::opcode::value op = frame::opcode::text);
...
lib::error_code send(message_ptr msg);
```]
]]]]
[[9. Streaming Messages][
websocketpp requires that the entire message fit into memory, and that
the size is known ahead of time.
Beast allows callers to compose messages in individual frames. This is
useful when the size of the data is not known ahead of time or if it
is not desired to buffer the entire message in memory at once before
sending it. For example, sending periodic output of a database query
running on a coroutine. Or sending the contents of a file in pieces,
without bringing it all into memory.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]]
[websocketpp]
][
[```
template<class ConstBufferSequence>
void
write_frame(bool fin,
ConstBufferSequence const& buffers);
```]
[ [
[[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast], /<not available>/
[@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] ]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] ]]]]
][
[```
#include <boost/asio.hpp>
```]
[```
template <typename config>
class endpoint : public config::socket_type;
```]
]]]]
[[12. WebSocket Handshaking][ [[10. Flow Control][
Callers invoke `beast::websocket::accept` to perform the WebSocket The websocketpp read implementation continuously reads asynchronously
handshake, but there is no requirement to use this function. Advanced from the network and buffers message data. To prevent unbounded growth
users can perform the WebSocket handshake themselves. Beast WebSocket and leverage TCP/IP's flow control mechanism, callers can periodically
provides the tools for composing the request or response, and the turn this 'read pump' off and back on.
Beast HTTP interface provides the container and algorithms for sending
and receiving HTTP/1 messages including the necessary HTTP Upgrade
request for establishing the WebSocket session.
Beast allows the caller to pass the incoming HTTP Upgrade request for In contrast a `beast::websocket::stream` does not independently begin
the cases where the caller has already received an HTTP message. background activity, nor does it buffer messages. It receives data only
This flexibility permits novel and robust implementations. For example, when there is a call to an asynchronous initiation function (for
a listening socket that can handshake in multiple protocols on the example `beast::websocket::stream::async_read`) with an associated handler.
same port. Applications do not need to implement explicit logic to regulate the
flow of data. Instead, they follow the traditional model of issuing a
read, receiving a read completion, processing the message, then
issuing a new read and repeating the process.
Sometimes callers want to read some bytes on the socket before reading [table
the WebSocket HTTP Upgrade request. Beast allows these already-received [
bytes to be supplied to an overload of the accepting function to permit [Beast]
sophisticated features. For example, a listening socket that can [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]]
accept both regular WebSocket and Secure WebSocket (SSL) connections. ][
[table
[ [
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], /<implicit>/
[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] ]
[websocketpp] [```
][ lib::error_code pause_reading();
[``` lib::error_code resume_reading();
template<class ConstBufferSequence> ```]
void ]]]]
accept(ConstBufferSequence const& buffers);
template<class Body, class Headers> [[11. Connection Establishment][
void
accept(http::request_v1<Body, Headers> const& request);
```]
[
/<not available>/
]
]]]]
] websocketpp offers the `endpoint` class which can handle binding and
listening to a port, and spawning connection objects.
Beast does not reinvent the wheel here, callers use the interfaces
already in `boost::asio` for receiving incoming connections resolving
host names, or establishing outgoing connections. After the socket (or
`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream`
is constructed around it and the WebSocket handshake can be performed.
Beast users are free to implement their own "connection manager", but
there is no requirement to do so.
[table
[
[[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast],
[@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]]
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]]
][
[```
#include <boost/asio.hpp>
```]
[```
template <typename config>
class endpoint : public config::socket_type;
```]
]]]]
[[12. WebSocket Handshaking][
Callers invoke `beast::websocket::accept` to perform the WebSocket
handshake, but there is no requirement to use this function. Advanced
users can perform the WebSocket handshake themselves. Beast WebSocket
provides the tools for composing the request or response, and the
Beast HTTP interface provides the container and algorithms for sending
and receiving HTTP/1 messages including the necessary HTTP Upgrade
request for establishing the WebSocket session.
Beast allows the caller to pass the incoming HTTP Upgrade request for
the cases where the caller has already received an HTTP message.
This flexibility permits novel and robust implementations. For example,
a listening socket that can handshake in multiple protocols on the
same port.
Sometimes callers want to read some bytes on the socket before reading
the WebSocket HTTP Upgrade request. Beast allows these already-received
bytes to be supplied to an overload of the accepting function to permit
sophisticated features. For example, a listening socket that can
accept both regular WebSocket and Secure WebSocket (SSL) connections.
[table
[
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast],
[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]]
[websocketpp]
][
[```
template<class ConstBufferSequence>
void
accept(ConstBufferSequence const& buffers);
template<class Allocator>
void
accept(http::header<true, http::basic_fields<Allocator>> const& req);
```]
[
/<not available>/
]
]]]]
]
]] ]]
] ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -13,13 +13,24 @@
//[example_core_echo_op_1 //[example_core_echo_op_1
template<
class AsyncStream,
class CompletionToken>
auto
async_echo(AsyncStream& stream, CompletionToken&& token)
//]
-> beast::async_return_type<CompletionToken, void(beast::error_code)>;
//[example_core_echo_op_2
/** Asynchronously read a line and echo it back. /** Asynchronously read a line and echo it back.
This function is used to asynchronously read a line ending This function is used to asynchronously read a line ending
in a carriage-return linefeed ("CRLF") from the stream, in a carriage-return ("CR") from the stream, and then write
and then write it back. The function call always returns it back. The function call always returns immediately. The
immediately. The asynchronous operation will continue until asynchronous operation will continue until one of the
one of the following conditions is true: following conditions is true:
@li A line was read in and sent back on the stream @li A line was read in and sent back on the stream
@@ -38,10 +49,10 @@
@param token The completion token to use. If this is a @param token The completion token to use. If this is a
completion handler, copies will be made as required. completion handler, copies will be made as required.
The signature of the handler must be: The equivalent signature of the handler must be:
@code @code
void handler( void handler(
error_code& ec // result of operation error_code ec // result of operation
); );
@endcode @endcode
Regardless of whether the asynchronous operation completes Regardless of whether the asynchronous operation completes
@@ -61,7 +72,7 @@ async_echo(
//] //]
//[example_core_echo_op_3 //[example_core_echo_op_4
// This composed operation reads a line of input and echoes it back. // This composed operation reads a line of input and echoes it back.
// //
@@ -79,10 +90,11 @@ class echo_op
int step = 0; int step = 0;
// The buffer used to hold the input and output data. // The buffer used to hold the input and output data.
// Note that we use a custom allocator for performance, //
// this allows the implementation of the io_service to // We use a custom allocator for performance, this allows
// make efficient re-use of memory allocated by composed // the implementation of the io_service to make efficient
// operations during continuations. // re-use of memory allocated by composed operations during
// a continuation.
// //
boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer; boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer;
@@ -98,11 +110,17 @@ class echo_op
} }
}; };
// This smart pointer container allocates our state using the // The operation's data is kept in a cheap-to-copy smart
// memory allocation hooks associated with the final completion // pointer container called `handler_ptr`. This efficiently
// handler, manages the lifetime of that handler for us, and // satisfies the CopyConstructible requirements of completion
// enforces the destroy-before-invocation requirement on memory // handlers.
// allocated using the hooks. //
// `handler_ptr` uses these memory allocation hooks associated
// with the final completion handler, in order to allocate the
// storage for `state`:
//
// asio_handler_allocate
// asio_handler_deallocate
// //
beast::handler_ptr<state, Handler> p_; beast::handler_ptr<state, Handler> p_;
@@ -123,65 +141,35 @@ public:
{ {
} }
// Determines if the next asynchronous operation represents a // The entry point for this handler. This will get called
// continuation of the asynchronous flow of control associated // as our intermediate operations complete. Definition below.
// with the final handler. If we are past step one, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
friend bool asio_handler_is_continuation(echo_op* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->p_->step > 1 ||
asio_handler_is_continuation(std::addressof(op->p_.handler()));
}
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes through the call to the hook
// associated with the final handler.
friend void* asio_handler_allocate(std::size_t size, echo_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
}
friend void asio_handler_deallocate(void* p, std::size_t size, echo_op* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size, std::addressof(op->p_.handler()));
}
template<class Function>
friend void asio_handler_invoke(Function&& f, echo_op* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
}
// Our main entry point. This will get called as our
// intermediate operations complete. Definition below.
// //
void operator()(beast::error_code ec, std::size_t bytes_transferred); void operator()(beast::error_code ec, std::size_t bytes_transferred);
// The next four functions are required for our class
// to meet the requirements for composed operations.
// Definitions and exposition will follow.
template<class AsyncStream_, class Handler_, class Function>
friend void asio_handler_invoke(
Function&& f, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend void* asio_handler_allocate(
std::size_t size, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend void asio_handler_deallocate(
void* p, std::size_t size, echo_op<AsyncStream_, Handler_>* op);
template<class AsyncStream_, class Handler_>
friend bool asio_handler_is_continuation(
echo_op<AsyncStream_, Handler_>* op);
}; };
//] //]
//[example_core_echo_op_4 //[example_core_echo_op_5
// echo_op is callable with the signature void(error_code, bytes_transferred), // echo_op is callable with the signature void(error_code, bytes_transferred),
// allowing `*this` to be used as both a ReadHandler and a WriteHandler. // allowing `*this` to be used as both a ReadHandler and a WriteHandler.
@@ -202,7 +190,7 @@ operator()(beast::error_code ec, std::size_t bytes_transferred)
case 0: case 0:
// read up to the first newline // read up to the first newline
p.step = 1; p.step = 1;
return boost::asio::async_read_until(p.stream, p.buffer, "\n", std::move(*this)); return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this));
case 1: case 1:
// write everything back // write everything back
@@ -217,10 +205,15 @@ operator()(beast::error_code ec, std::size_t bytes_transferred)
break; break;
} }
// Invoke the final handler. If we wanted to pass any arguments // Invoke the final handler. The implementation of `handler_ptr`
// which come from our state, they would have to be moved to the // will deallocate the storage for the state before the handler
// stack first, since the `handler_ptr` guarantees that the state // is invoked. This is necessary to provide the
// is destroyed before the handler is invoked. // destroy-before-invocation guarantee on handler memory
// customizations.
//
// If we wanted to pass any arguments to the handler which come
// from the `state`, they would have to be moved to the stack
// first or else undefined behavior results.
// //
p_.invoke(ec); p_.invoke(ec);
return; return;
@@ -228,7 +221,72 @@ operator()(beast::error_code ec, std::size_t bytes_transferred)
//] //]
//[example_core_echo_op_2 //[example_core_echo_op_6
// Handler hook forwarding. These free functions invoke the hooks
// associated with the final completion handler. In effect, they
// make the Asio implementation treat our composed operation the
// same way it would treat the final completion handler for the
// purpose of memory allocation and invocation.
//
// Our implementation just passes the call through to the hook
// associated with the final handler. The "using" statements are
// structured to permit argument dependent lookup. Always use
// `std::addressof` or its equivalent to pass the pointer to the
// handler, otherwise an unwanted overload of `operator&` may be
// called instead.
template<class AsyncStream, class Handler, class Function>
void asio_handler_invoke(
Function&& f, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_invoke;
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
}
template<class AsyncStream, class Handler>
void* asio_handler_allocate(
std::size_t size, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
}
template<class AsyncStream, class Handler>
void asio_handler_deallocate(
void* p, std::size_t size, echo_op<AsyncStream, Handler>* op)
{
using boost::asio::asio_handler_deallocate;
return asio_handler_deallocate(p, size,
std::addressof(op->p_.handler()));
}
// Determines if the next asynchronous operation represents a
// continuation of the asynchronous flow of control associated
// with the final handler. If we are past step one, it means
// we have performed an asynchronous operation therefore any
// subsequent operation would represent a continuation.
// Otherwise, we propagate the handler's associated value of
// is_continuation. Getting this right means the implementation
// may schedule the invokation of the invoked functions more
// efficiently.
//
template<class AsyncStream, class Handler>
bool asio_handler_is_continuation(echo_op<AsyncStream, Handler>* op)
{
// This next call is structured to permit argument
// dependent lookup to take effect.
using boost::asio::asio_handler_is_continuation;
// Always use std::addressof to pass the pointer to the handler,
// otherwise an unwanted overload of operator& may be called instead.
return op->p_->step > 1 ||
asio_handler_is_continuation(std::addressof(op->p_.handler()));
}
//]
//[example_core_echo_op_3
template<class AsyncStream, class Handler> template<class AsyncStream, class Handler>
class echo_op; class echo_op;
@@ -254,8 +312,8 @@ async_echo(AsyncStream& stream, CompletionToken&& token)
// Create the composed operation and launch it. This is a constructor // Create the composed operation and launch it. This is a constructor
// call followed by invocation of operator(). We use handler_type // call followed by invocation of operator(). We use handler_type
// to convert the completion token into the correct handler type, // to convert the completion token into the correct handler type,
// allowing user defined specializations of the async result template // allowing user-defined specializations of the async_result template
// to take effect. // to be used.
// //
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{ echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
stream, init.completion_handler}(beast::error_code{}, 0); stream, init.completion_handler}(beast::error_code{}, 0);

View File

@@ -32,7 +32,7 @@ namespace beast {
where the specific completion handler signature is known. where the specific completion handler signature is known.
This template takes advantage of specializations of both This template takes advantage of specializations of both
`boost::asio_async_result` and `boost::asio::handler_type` for user-defined `boost::asio::async_result` and `boost::asio::handler_type` for user-defined
completion token types. The primary template assumes that the completion token types. The primary template assumes that the
@b CompletionToken is the completion handler. @b CompletionToken is the completion handler.

View File

@@ -42,6 +42,7 @@ public:
/// Copy constructor /// Copy constructor
drain_buffer(drain_buffer const&) drain_buffer(drain_buffer const&)
{ {
// Previously returned ranges are invalidated
} }
/// Copy assignment /// Copy assignment

View File

@@ -35,10 +35,10 @@ namespace http {
value. value.
Field names are stored as-is, but comparisons are case-insensitive. Field names are stored as-is, but comparisons are case-insensitive.
When the container is iterated, the fields are presented in the order The container behaves as a `std::multiset`; there will be a separate
of insertion. For fields with the same name, the container behaves value for each occurrence of the same field name. When the container
as a `std::multiset`; there will be a separate value for each occurrence is iterated the fields are presented in the order of insertion, with
of the field name. fields having the same name following each other consecutively.
Meets the requirements of @b Fields Meets the requirements of @b Fields

View File

@@ -53,7 +53,7 @@ struct header<true, Fields> : Fields
/// Indicates if the header is a request or response. /// Indicates if the header is a request or response.
#if BEAST_DOXYGEN #if BEAST_DOXYGEN
using is_request = isRequest; using is_request = std::integral_constant<bool, isRequest>;
#else #else
using is_request = std::true_type; using is_request = std::true_type;
#endif #endif
@@ -203,7 +203,11 @@ struct header<false, Fields> : Fields
"Fields requirements not met"); "Fields requirements not met");
/// Indicates if the header is a request or response. /// Indicates if the header is a request or response.
#if BEAST_DOXYGEN
using is_request = std::integral_constant<bool, isRequest>;
#else
using is_request = std::false_type; using is_request = std::false_type;
#endif
/// The type representing the fields. /// The type representing the fields.
using fields_type = Fields; using fields_type = Fields;

View File

@@ -546,27 +546,120 @@ upcall:
d_.invoke(ec); d_.invoke(ec);
} }
//------------------------------------------------------------------------------
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence, class WriteHandler> template<class Buffers, class Handler>
async_return_type< class stream<NextLayer>::write_op
WriteHandler, void(error_code)>
stream<NextLayer>::
async_write_frame(bool fin,
ConstBufferSequence const& bs, WriteHandler&& handler)
{ {
static_assert(is_async_stream<next_layer_type>::value, struct data : op
"AsyncStream requirements not met"); {
static_assert(beast::is_const_buffer_sequence< int step = 0;
ConstBufferSequence>::value, stream<NextLayer>& ws;
"ConstBufferSequence requirements not met"); consuming_buffers<Buffers> cb;
async_completion<WriteHandler, std::size_t remain;
void(error_code)> init{handler};
write_frame_op<ConstBufferSequence, handler_type< data(Handler& handler, stream<NextLayer>& ws_,
WriteHandler, void(error_code)>>{init.completion_handler, Buffers const& bs)
*this, fin, bs}; : ws(ws_)
return init.result.get(); , cb(bs)
, remain(boost::asio::buffer_size(cb))
{
}
};
handler_ptr<data, Handler> d_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = default;
template<class DeducedHandler, class... Args>
explicit
write_op(DeducedHandler&& h,
stream<NextLayer>& ws, Args&&... args)
: d_(std::forward<DeducedHandler>(h),
ws, std::forward<Args>(args)...)
{
}
void operator()(error_code ec);
friend
void* asio_handler_allocate(
std::size_t size, write_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->d_.handler()));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
using boost::asio::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->d_.handler()));
}
friend
bool asio_handler_is_continuation(write_op* op)
{
using boost::asio::asio_handler_is_continuation;
return op->d_->step > 2 ||
asio_handler_is_continuation(
std::addressof(op->d_.handler()));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(op->d_.handler()));
}
};
template<class NextLayer>
template<class Buffers, class Handler>
void
stream<NextLayer>::
write_op<Buffers, Handler>::
operator()(error_code ec)
{
auto& d = *d_;
switch(d.step)
{
case 2:
d.step = 3;
BOOST_FALLTHROUGH;
case 3:
case 0:
{
auto const n = d.remain;
d.remain -= n;
auto const fin = d.remain <= 0;
if(fin)
d.step = d.step ? 4 : 1;
else
d.step = d.step ? 3 : 2;
auto const pb = buffer_prefix(n, d.cb);
d.cb.consume(n);
return d.ws.async_write_frame(
fin, pb, std::move(*this));
}
case 1:
case 4:
break;
}
d_.invoke(ec);
} }
//------------------------------------------------------------------------------
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
@@ -790,124 +883,13 @@ write_frame(bool fin,
} }
} }
//------------------------------------------------------------------------------
template<class NextLayer>
template<class Buffers, class Handler>
class stream<NextLayer>::write_op
{
struct data : op
{
int step = 0;
stream<NextLayer>& ws;
consuming_buffers<Buffers> cb;
std::size_t remain;
data(Handler& handler, stream<NextLayer>& ws_,
Buffers const& bs)
: ws(ws_)
, cb(bs)
, remain(boost::asio::buffer_size(cb))
{
}
};
handler_ptr<data, Handler> d_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = default;
template<class DeducedHandler, class... Args>
explicit
write_op(DeducedHandler&& h,
stream<NextLayer>& ws, Args&&... args)
: d_(std::forward<DeducedHandler>(h),
ws, std::forward<Args>(args)...)
{
}
void operator()(error_code ec);
friend
void* asio_handler_allocate(
std::size_t size, write_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->d_.handler()));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
using boost::asio::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->d_.handler()));
}
friend
bool asio_handler_is_continuation(write_op* op)
{
using boost::asio::asio_handler_is_continuation;
return op->d_->step > 2 ||
asio_handler_is_continuation(
std::addressof(op->d_.handler()));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(op->d_.handler()));
}
};
template<class NextLayer>
template<class Buffers, class Handler>
void
stream<NextLayer>::
write_op<Buffers, Handler>::
operator()(error_code ec)
{
auto& d = *d_;
switch(d.step)
{
case 2:
d.step = 3;
BOOST_FALLTHROUGH;
case 3:
case 0:
{
auto const n = d.remain;
d.remain -= n;
auto const fin = d.remain <= 0;
if(fin)
d.step = d.step ? 4 : 1;
else
d.step = d.step ? 3 : 2;
auto const pb = buffer_prefix(n, d.cb);
d.cb.consume(n);
return d.ws.async_write_frame(
fin, pb, std::move(*this));
}
case 1:
case 4:
break;
}
d_.invoke(ec);
}
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence, class WriteHandler> template<class ConstBufferSequence, class WriteHandler>
async_return_type< async_return_type<
WriteHandler, void(error_code)> WriteHandler, void(error_code)>
stream<NextLayer>:: stream<NextLayer>::
async_write(ConstBufferSequence const& bs, WriteHandler&& handler) async_write_frame(bool fin,
ConstBufferSequence const& bs, WriteHandler&& handler)
{ {
static_assert(is_async_stream<next_layer_type>::value, static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream requirements not met"); "AsyncStream requirements not met");
@@ -916,13 +898,14 @@ async_write(ConstBufferSequence const& bs, WriteHandler&& handler)
"ConstBufferSequence requirements not met"); "ConstBufferSequence requirements not met");
async_completion<WriteHandler, async_completion<WriteHandler,
void(error_code)> init{handler}; void(error_code)> init{handler};
write_op<ConstBufferSequence, handler_type< write_frame_op<ConstBufferSequence, handler_type<
WriteHandler, void(error_code)>>{ WriteHandler, void(error_code)>>{init.completion_handler,
init.completion_handler, *this, bs}( *this, fin, bs};
error_code{});
return init.result.get(); return init.result.get();
} }
//------------------------------------------------------------------------------
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
@@ -954,6 +937,28 @@ write(ConstBufferSequence const& buffers, error_code& ec)
write_frame(true, buffers, ec); write_frame(true, buffers, ec);
} }
template<class NextLayer>
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code)>
stream<NextLayer>::
async_write(
ConstBufferSequence const& bs, WriteHandler&& handler)
{
static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream requirements not met");
static_assert(beast::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
async_completion<WriteHandler,
void(error_code)> init{handler};
write_op<ConstBufferSequence, handler_type<
WriteHandler, void(error_code)>>{
init.completion_handler, *this, bs}(
error_code{});
return init.result.get();
}
} // websocket } // websocket
} // beast } // beast

View File

@@ -47,17 +47,15 @@ using response_type = http::response<http::string_body>;
that they are are all performed within the same implicit that they are are all performed within the same implicit
or explicit strand. or explicit strand.
@par Thread Safety
@e Distinct @e objects: Safe.@n
@e Shared @e objects: Unsafe.
@par Example @par Example
To use the @ref stream template with an `ip::tcp::socket`, To use the @ref stream template with an `ip::tcp::socket`,
you would write: you would write:
@tparam NextLayer The type representing the next layer, to which
data will be read and written during operations. For synchronous
operations, the type must support the @b SyncStream concept.
For asynchronous operations, the type must support the
@b AsyncStream concept.
@code @code
websocket::stream<ip::tcp::socket> ws{io_service}; websocket::stream<ip::tcp::socket> ws{io_service};
@endcode @endcode
@@ -67,9 +65,11 @@ using response_type = http::response<http::string_body>;
websocket::stream<ip::tcp::socket&> ws{sock}; websocket::stream<ip::tcp::socket&> ws{sock};
@endcode @endcode
@par Thread Safety @tparam NextLayer The type representing the next layer, to which
@e Distinct @e objects: Safe.@n data will be read and written during operations. For synchronous
@e Shared @e objects: Unsafe. operations, the type must support the @b SyncStream concept.
For asynchronous operations, the type must support the
@b AsyncStream concept.
@note A stream object must not be moved or destroyed while there @note A stream object must not be moved or destroyed while there
are pending asynchronous operations associated with it. are pending asynchronous operations associated with it.

View File

@@ -31,7 +31,7 @@ void fxx() {
{ {
//[http_snippet_2 //[http_snippet_2
request<string_body> req; request<empty_body> req;
req.version = 11; // HTTP/1.1 req.version = 11; // HTTP/1.1
req.method(verb::get); req.method(verb::get);
req.target("/index.htm"); req.target("/index.htm");