mirror of
https://github.com/boostorg/beast.git
synced 2025-08-04 07:14:32 +02:00
@@ -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]]]
|
||||||
|
@@ -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]]
|
||||||
|
@@ -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.
|
||||||
]]
|
]]
|
||||||
]
|
]
|
||||||
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[
|
[
|
||||||
|
@@ -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]]
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -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.
|
||||||
|
@@ -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 |
@@ -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);
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -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");
|
||||||
|
Reference in New Issue
Block a user