mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 23:04:35 +02:00
@@ -51,6 +51,8 @@
|
||||
[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 __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 __Body__ [link beast.concept.Body [*Body]]]
|
||||
[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
|
||||
or asynchronous, and may allow reading, writing, or both. Note that
|
||||
a particular type may model more than one concept. For example, the
|
||||
Asio types __socket__ and __ssl_stream__ and support everything.
|
||||
All stream algorithms in Beast are declared as template functions
|
||||
with specific concept requirements chosen from this list:
|
||||
Asio types __socket__ and __ssl_stream__ support both __SyncStream__
|
||||
and __AsyncStream__. All stream algorithms in Beast are declared as
|
||||
template functions using these concepts:
|
||||
|
||||
[table Stream Concepts
|
||||
[[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
|
||||
[[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
|
||||
length one. Upon construction an optional upper limit to the total
|
||||
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`]
|
||||
@@ -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.
|
||||
Additional character array objects are appended to the sequence to
|
||||
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`]
|
||||
@@ -150,7 +153,7 @@ output streams.
|
||||
[link beast.ref.beast__ostream `ostream`]
|
||||
][
|
||||
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.
|
||||
]]
|
||||
]
|
||||
|
@@ -5,40 +5,34 @@
|
||||
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?>''']
|
||||
|
||||
Asynchronous operations are started by calling a free function or member
|
||||
function known as an ['asynchronous initiation function]. The initiation
|
||||
function accepts parameters specific to the operation as well as a "completion
|
||||
token." This token is either a completion handler, or a type defining how
|
||||
the caller is informed of the asynchronous operation result. __Asio__ comes
|
||||
with the special completion tokens __use_future__ and __yield_context__ for
|
||||
using futures and coroutines respectively. This system of customizing the
|
||||
return value and method of completion notification is known as the
|
||||
function known as an asynchronous ['__async_initfn__]. This function accepts
|
||||
parameters specific to the operation as well as a "completion token." The
|
||||
token is either a completion handler, or a type defining how the caller is
|
||||
informed of the asynchronous operation result. __Asio__ comes with the
|
||||
special tokens __use_future__ and __yield_context__ for using futures
|
||||
and coroutines respectively. This system of customizing the return value
|
||||
and method of completion notification is known as the
|
||||
['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
|
||||
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.
|
||||
]
|
||||
[example_core_echo_op_1]
|
||||
|
||||
Since the interfaces provided here are low level, authors of libraries
|
||||
may wish to create higher level interfaces using the primitives found
|
||||
in this library. Non-trivial applications will want to provide their own
|
||||
asynchronous initiation functions which perform a series of other,
|
||||
intermediate asynchronous operations before invoking the final completion
|
||||
handler. The set of intermediate actions produced by calling an initiation
|
||||
function is known as a
|
||||
Authors using Beast can reuse the library's primitives to create their
|
||||
own initiating functions for performing a series of other, intermediate
|
||||
asynchronous operations before invoking a final completion handler.
|
||||
The set of intermediate actions produced by an initiating function is
|
||||
known as a
|
||||
[@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
|
||||
requirements on the implementation of composed operations. A number of useful
|
||||
classes and macros to facilitate the development of composed operations and
|
||||
the associated asynchronous initiation functions used to launch them are
|
||||
available:
|
||||
requirements on the implementation of composed operations. These classes
|
||||
and functions make it easier to develop initiating functions and their
|
||||
composed operations:
|
||||
|
||||
[table Asynchronous Helpers
|
||||
[[Name][Description]]
|
||||
@@ -105,8 +99,8 @@ available:
|
||||
|
||||
[section Echo]
|
||||
|
||||
Here we develop an asynchronous composed operation called [*echo].
|
||||
This operation will read up to the first newline on a stream, and
|
||||
This example develops an initiating function called [*echo].
|
||||
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.
|
||||
The implementation performs both reading and writing, and has a
|
||||
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
|
||||
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.
|
||||
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 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 return value type using the helper, and creating and launching the
|
||||
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
|
||||
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
|
||||
to implement the function call operator. Our strategy is to make our composed
|
||||
object meet the requirements of a completion handler by being copyable (also
|
||||
movable), and by providing the function call operator with the correct
|
||||
Next is to implement the function call operator. Our strategy is to make our
|
||||
composed object meet the requirements of a completion handler by being copyable
|
||||
(also movable), and by providing the function call operator with the correct
|
||||
signature. Rather than using `std::bind` or `boost::bind`, which destroys
|
||||
the type information and therefore breaks the allocation and invocation
|
||||
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
|
||||
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
|
||||
directory.
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
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
|
||||
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.
|
||||
This SSL detector may be used to allow a server to accept both SSL/TLS and
|
||||
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
|
||||
operation which has more boilerplate. First, we declare the asynchronous
|
||||
initiation function:
|
||||
initiating function:
|
||||
|
||||
[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
|
||||
type customization helper to obtain the actual handler, and
|
||||
then create the composed operation and launch it. The actual
|
||||
@@ -51,7 +51,7 @@ is worth the effort.
|
||||
|
||||
[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
|
||||
with the signature `void(error_code, std::size_t)` which is exactly
|
||||
the signature needed when performing asynchronous reads. This function
|
||||
|
@@ -9,15 +9,19 @@
|
||||
|
||||
The HTTP protocol defines the
|
||||
[@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]:
|
||||
clients send requests and servers send back responses. A request
|
||||
or response is an
|
||||
clients send requests and servers send back responses. When a client and
|
||||
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]
|
||||
(referred to hereafter as "message"), with two parts:
|
||||
a header containing structured metadata and an optional variable-length
|
||||
body containing arbitrary data. A serialized header is one or more text
|
||||
lines where each line ends in a carriage return followed by linefeed
|
||||
(`"\r\n"`). An empty line marks the end of the header. The first line
|
||||
in the header is called the ['start-line]. The start line contents are
|
||||
(referred to hereafter as "message") having two parts:
|
||||
a header with structured metadata and an optional variable-length body
|
||||
holding arbitrary data. A serialized header is one or more text lines
|
||||
where each line ends in a carriage return followed by linefeed (`"\r\n"`).
|
||||
An empty line marks the end of the header. The first line in the header
|
||||
is called the ['start-line]. The contents of the start line contents are
|
||||
different for requests and responses.
|
||||
|
||||
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
|
||||
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]
|
||||
|
||||
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]
|
||||
|
||||
Certain fields appearing in messages are special. The library understands
|
||||
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
|
||||
[[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
|
||||
(outermost) applied coding. The library will automatically apply
|
||||
chunked encoding when the content length is not known ahead of time
|
||||
during serialization, and the library will automatically removed chunked
|
||||
encoding from parsed messages.
|
||||
during serialization, and the library will automatically remove chunked
|
||||
encoding from parsed messages when present.
|
||||
]
|
||||
][
|
||||
[
|
||||
|
@@ -44,22 +44,21 @@ class header;
|
||||
|
||||
Requests and responses share the version, fields, and body but have
|
||||
a few members unique to the type. This is implemented by declaring the
|
||||
header classes as partial specializations of `isRequest`. Furthermore,
|
||||
__message__ is derived from __header__; a message may be passed as an
|
||||
argument to a function taking a suitably typed header as a parameter.
|
||||
Furthermore, `header` is publicly derived from `Fields`; a message
|
||||
inherits all of the fields member functions. Since `fields` is a
|
||||
container, iterating a message iterates its header fields. This
|
||||
diagram shows the inheritance relationship between header and message,
|
||||
along with the fields from the different partial specializations for
|
||||
each possible value of `isRequest`:
|
||||
header classes as partial specializations of `isRequest`. __message__
|
||||
is derived from __header__; a message may be passed as an argument to
|
||||
a function taking a suitably typed header as a parameter. Additionally,
|
||||
`header` is publicly derived from `Fields`; a message inherits all the
|
||||
member functions of `Fields`. This diagram shows the inheritance
|
||||
relationship between header and message, along with some of the
|
||||
notable differences in members in each partial specialization:
|
||||
|
||||
[$images/message.png [width 730px] [height 410px]]
|
||||
|
||||
The template type aliases
|
||||
[link beast.ref.beast__http__request `request`] and
|
||||
[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
|
||||
@@ -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
|
||||
(as seen in the diagram above) and may also include algorithms for
|
||||
transferring buffers in and out. These algorithms are used during
|
||||
parsing and serialization. These body types are available within the
|
||||
library, and users may define their own body types which meet the
|
||||
__Body__ requirements:
|
||||
parsing and serialization. Users may define their own body types which
|
||||
meet the requirements, or use the ones that come with the library:
|
||||
|
||||
[table
|
||||
[[Name][Description]]
|
||||
@@ -130,10 +128,10 @@ __Body__ requirements:
|
||||
|
||||
[heading Usage]
|
||||
|
||||
The code examples below show how to create and fill in request and response
|
||||
objects: Here is simple example of building an
|
||||
These examples show how to create and fill in request and response
|
||||
objects: Here we build an
|
||||
[@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET]
|
||||
request.
|
||||
request with an empty message body:
|
||||
|
||||
[table Create Request
|
||||
[[Statements] [Serialized Result]]
|
||||
@@ -149,12 +147,12 @@ request.
|
||||
]]
|
||||
]
|
||||
|
||||
Here we create an HTTP response indicating success. Note that this
|
||||
message has a body. The function
|
||||
In this code we create an HTTP response with a status code indicating success.
|
||||
This message has a body with a non-zero length. The function
|
||||
[link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`]
|
||||
automatically sets the Content-Length or Transfer-Encoding field
|
||||
depending on the content and type of the `body` member. The use
|
||||
of prepare is optional; these fields may also be set explicitly.
|
||||
depending on the content and type of the `body` member. Use of this function
|
||||
is optional; these fields may also be set explicitly.
|
||||
|
||||
[table Create Response
|
||||
[[Statements] [Serialized Result]]
|
||||
|
@@ -67,11 +67,11 @@ into a single buffer for parsing.
|
||||
]
|
||||
|
||||
Messages may also be read asynchronously. When performing asynchronous
|
||||
stream read operations, the buffer and message variables must remain
|
||||
valid until the operation has completed. Beast asynchronous initiation
|
||||
functions use Asio's completion handler model. Here we read a message
|
||||
asynchronously. When the operation completes the message in the error
|
||||
code indicating the result is printed:
|
||||
stream read operations the stream, buffer, and message variables must
|
||||
remain valid until the operation has completed. Beast asynchronous
|
||||
initiation functions use Asio's completion handler model. This call
|
||||
reads a message asynchronously and report the error code upon
|
||||
completion:
|
||||
|
||||
[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
|
||||
[link beast.ref.beast__http__error `buffer_overflow`]
|
||||
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
|
||||
code will generate an error:
|
||||
HTTP message header for protection from buffer overflow attacks. The
|
||||
following code will print the error message:
|
||||
|
||||
[http_snippet_6]
|
||||
|
||||
@@ -89,9 +89,9 @@ code will generate an error:
|
||||
[heading Writing]
|
||||
|
||||
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
|
||||
transfer encoding, the end of the message is indicated by the server closing
|
||||
the connection. When sending such a response, Beast will return the
|
||||
a stream. If a response has no declared content length and no chunked
|
||||
transfer encoding, then the end of the message is indicated by the server
|
||||
closing the connection. When sending such a response, Beast will return the
|
||||
[link beast.ref.beast__http__error `error::end_of_stream`]
|
||||
from the write algorithm to indicate
|
||||
to the caller that the connection should be closed. This example
|
||||
|
@@ -32,7 +32,7 @@ These relations are true:
|
||||
* `P(S(m)) == m`
|
||||
|
||||
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
|
||||
represent the body. And finally, because requests and responses have
|
||||
different fields in the ['start-line], we would like the containers for
|
||||
@@ -52,6 +52,7 @@ struct request
|
||||
std::string method;
|
||||
std::string target;
|
||||
Fields fields;
|
||||
|
||||
typename Body::value_type body;
|
||||
};
|
||||
```
|
||||
@@ -65,6 +66,7 @@ struct response
|
||||
int status;
|
||||
std::string reason;
|
||||
Fields fields;
|
||||
|
||||
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
|
||||
obvious solution is to make the message a template type. Additional traits
|
||||
classes would then be needed to make sure that the passed object has a
|
||||
valid type which meets the requirements. Instead, bypass those complexities
|
||||
by making each container a partial specialization of one class:
|
||||
valid type which meets the requirements. These unnecessary complexities
|
||||
are bypassed by making each container a partial specialization:
|
||||
```
|
||||
/// An HTTP message
|
||||
template<bool isRequest, class Fields, class Body>
|
||||
@@ -98,6 +100,7 @@ struct message<true, Fields, Body>
|
||||
std::string method;
|
||||
std::string target;
|
||||
Fields fields;
|
||||
|
||||
typename Body::value_type body;
|
||||
};
|
||||
|
||||
@@ -109,6 +112,7 @@ struct message<false, Fields, Body>
|
||||
int status;
|
||||
std::string reason;
|
||||
Fields fields;
|
||||
|
||||
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
|
||||
header as a parameter, to also accept a type representing the whole
|
||||
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
|
||||
template<bool isRequest, class Fields>
|
||||
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
|
||||
template<class Fields>
|
||||
struct header<true, Fields>
|
||||
struct header<true, Fields> : Fields
|
||||
{
|
||||
int version;
|
||||
std::string method;
|
||||
std::string target;
|
||||
Fields fields;
|
||||
};
|
||||
|
||||
/// An HTTP response header
|
||||
template<class Fields>
|
||||
struct header<false, Fields>
|
||||
struct header<false, Fields> : Fields
|
||||
{
|
||||
int version;
|
||||
int status;
|
||||
std::string reason;
|
||||
Fields fields;
|
||||
};
|
||||
|
||||
/// An HTTP message
|
||||
@@ -170,7 +180,7 @@ struct message : header<isRequest, Fields>
|
||||
Note that the `message` class now has a constructor allowing messages
|
||||
to be constructed from a similarly typed `header`. This handles the case
|
||||
where the user already has the header and wants to make a commitment to the
|
||||
type for [*Body]. This also lets us declare a function accepting any header:
|
||||
type for [*Body]. A function can be declared which accepts any header:
|
||||
```
|
||||
template<bool isRequest, class Fields>
|
||||
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
|
||||
and target strings. A better solution is needed.
|
||||
|
||||
To get around this we make a simple change to the interface and then
|
||||
engineer a clever concession. First, the interface change:
|
||||
To get around this we make an interface modification and then add
|
||||
a requirement to the [*Fields] type. First, the interface change:
|
||||
```
|
||||
/// An HTTP request header
|
||||
template<class Fields>
|
||||
struct header<true, Fields>
|
||||
struct header<true, Fields> : Fields
|
||||
{
|
||||
int version;
|
||||
|
||||
verb method() const;
|
||||
string_view method_string() const;
|
||||
void method(verb);
|
||||
void method(string_view);
|
||||
|
||||
string_view target(); const;
|
||||
void target(string_view);
|
||||
Fields fields;
|
||||
|
||||
private:
|
||||
verb method_;
|
||||
@@ -224,13 +235,12 @@ private:
|
||||
|
||||
/// An HTTP response header
|
||||
template<class Fields>
|
||||
struct header<false, Fields>
|
||||
struct header<false, Fields> : Fields
|
||||
{
|
||||
int version;
|
||||
int status;
|
||||
int result;
|
||||
string_view reason() const;
|
||||
void reason(string_view);
|
||||
Fields fields;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -239,16 +249,16 @@ non-owning references to string buffers. The method is stored using
|
||||
a simple integer instead of the entire string, for the case where
|
||||
the method is recognized from the set of known verb strings.
|
||||
|
||||
Now we make a concession: management of the corresponding string is
|
||||
delegated to the [*Fields] container, which can already be allocator
|
||||
aware and constructed with the necessary allocator parameter via the
|
||||
provided constructor overloads for `message`. The delegation
|
||||
implementation looks like this (only the response header specialization
|
||||
is shown):
|
||||
Now we add a requirement to the fields type: management of the
|
||||
corresponding string is delegated to the [*Fields] container, which can
|
||||
already be allocator aware and constructed with the necessary allocator
|
||||
parameter via the provided constructor overloads for `message`. The
|
||||
delegation implementation looks like this (only the response header
|
||||
specialization is shown):
|
||||
```
|
||||
/// An HTTP response header
|
||||
template<class Fields>
|
||||
struct header<false, Fields>
|
||||
struct header<false, Fields> : Fields
|
||||
{
|
||||
int version;
|
||||
int status;
|
||||
@@ -256,23 +266,24 @@ struct header<false, Fields>
|
||||
string_view
|
||||
reason() const
|
||||
{
|
||||
return fields.reason_impl();
|
||||
return this->reason_impl(); // protected member of Fields
|
||||
}
|
||||
|
||||
void
|
||||
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
|
||||
quality of life improvement to make. Users will choose different types for
|
||||
`Body` far more often than they will for `Fields`. Thus, we swap the order
|
||||
of these types and provide a default:
|
||||
Now that we've accomplished our initial goals and more, there are a few
|
||||
more quality of life improvements to make. Users will choose different
|
||||
types for `Body` far more often than they will for `Fields`. Thus, we
|
||||
swap the order of these types and provide a default. Then, we provide
|
||||
type aliases for requests and responses to soften the impact of using
|
||||
`bool` to choose the specialization:
|
||||
|
||||
```
|
||||
/// An HTTP header
|
||||
template<bool isRequest, class Body, class Fields = fields>
|
||||
@@ -281,6 +292,22 @@ struct header;
|
||||
/// An HTTP message
|
||||
template<bool isRequest, class Body, class Fields = fields>
|
||||
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.
|
||||
|
@@ -13,15 +13,15 @@
|
||||
How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp],
|
||||
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
|
||||
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:
|
||||
Beast offers full support for WebSockets using a synchronous interface. It
|
||||
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:
|
||||
|
||||
[table
|
||||
[table
|
||||
[
|
||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]]
|
||||
[websocketpp]
|
||||
@@ -29,36 +29,36 @@
|
||||
[```
|
||||
template<class DynamicBuffer>
|
||||
void
|
||||
read(opcode& op, DynamicBuffer& dynabuf)
|
||||
read(DynamicBuffer& dynabuf)
|
||||
```]
|
||||
[
|
||||
/<not available>/
|
||||
]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[2. Connection Model][
|
||||
[[2. Connection Model][
|
||||
|
||||
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])
|
||||
To get an idea of the complexity involved with implementing a transport,
|
||||
compare the asio transport to the
|
||||
[@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`).
|
||||
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])
|
||||
To get an idea of the complexity involved with implementing a transport,
|
||||
compare the asio transport to the
|
||||
[@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`).
|
||||
|
||||
In contrast, Beast abstracts the transport by defining just one [*`NextLayer`]
|
||||
template argument The type requirements for [*`NextLayer`] are
|
||||
already familiar to users as they are documented in Asio:
|
||||
__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__.
|
||||
In contrast, Beast abstracts the transport by defining just one [*`NextLayer`]
|
||||
template argument The type requirements for [*`NextLayer`] are
|
||||
already familiar to users as they are documented in Asio:
|
||||
__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__.
|
||||
|
||||
The type requirements for instantiating `beast::websocket::stream` versus
|
||||
`websocketpp::connection` with user defined types are vastly reduced
|
||||
(18 functions versus 2). Note that websocketpp connections are passed by
|
||||
`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
|
||||
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.
|
||||
The type requirements for instantiating `beast::websocket::stream` versus
|
||||
`websocketpp::connection` with user defined types are vastly reduced
|
||||
(18 functions versus 2). Note that websocketpp connections are passed by
|
||||
`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
|
||||
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.
|
||||
|
||||
[table
|
||||
[table
|
||||
[
|
||||
[[@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]]
|
||||
@@ -82,17 +82,17 @@
|
||||
...
|
||||
}
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[3. Client and Server Role][
|
||||
[[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.
|
||||
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
|
||||
[table
|
||||
[
|
||||
[Beast]
|
||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp],
|
||||
@@ -107,24 +107,24 @@
|
||||
template <typename config>
|
||||
class server : public endpoint<connection<config>,config>;
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[4. Thread Safety][
|
||||
[[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.
|
||||
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.
|
||||
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
|
||||
[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]]
|
||||
@@ -140,106 +140,120 @@
|
||||
[```
|
||||
mutex_type m_read_mutex;
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[5. Callback Model][
|
||||
[[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.
|
||||
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`.
|
||||
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.
|
||||
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
|
||||
[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);
|
||||
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;
|
||||
typedef lib::function<
|
||||
void(connection_hdl,message_ptr)
|
||||
> message_handler;
|
||||
void set_message_handler(message_handler h);
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[6. Extensible Asynchronous Model][
|
||||
[[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 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.
|
||||
Beast websocket asynchronous interfaces may be used seamlessly with
|
||||
`std::future` stackful/stackless coroutines, or user defined customizations.
|
||||
|
||||
[table
|
||||
[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)>{
|
||||
beast::async_completion<
|
||||
ReadHandler,
|
||||
void(error_code)> completion{handler};
|
||||
read_op<
|
||||
DynamicBuffer, decltype(completion.handler)>{
|
||||
completion.handler, *this, op, buffer};
|
||||
return completion.result.get();
|
||||
|
||||
return completion.result.get(); // Customization point
|
||||
```]
|
||||
[
|
||||
/<not available>/
|
||||
]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[7. Message Buffering][
|
||||
[[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.
|
||||
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 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.
|
||||
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
|
||||
[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);
|
||||
read(DynamicBuffer& dynabuf);
|
||||
```]
|
||||
[```
|
||||
template <template<class> class con_msg_manager>
|
||||
@@ -251,32 +265,32 @@
|
||||
...
|
||||
};
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[8. Sending Messages][
|
||||
[[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.
|
||||
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.
|
||||
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`.
|
||||
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
|
||||
[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]]
|
||||
@@ -292,21 +306,21 @@
|
||||
...
|
||||
lib::error_code send(message_ptr msg);
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[9. Streaming Messages][
|
||||
[[9. Streaming Messages][
|
||||
|
||||
websocketpp requires that the entire message fit into memory, and that
|
||||
the size is known ahead of time.
|
||||
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.
|
||||
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
|
||||
[table
|
||||
[
|
||||
[[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]]
|
||||
[websocketpp]
|
||||
@@ -320,25 +334,25 @@
|
||||
[
|
||||
/<not available>/
|
||||
]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[10. Flow Control][
|
||||
[[10. Flow Control][
|
||||
|
||||
The websocketpp read implementation continuously reads asynchronously
|
||||
from the network and buffers message data. To prevent unbounded growth
|
||||
and leverage TCP/IP's flow control mechanism, callers can periodically
|
||||
turn this 'read pump' off and back on.
|
||||
The websocketpp read implementation continuously reads asynchronously
|
||||
from the network and buffers message data. To prevent unbounded growth
|
||||
and leverage TCP/IP's flow control mechanism, callers can periodically
|
||||
turn this 'read pump' off and back on.
|
||||
|
||||
In contrast a `beast::websocket::stream` does not independently begin
|
||||
background activity, nor does it buffer messages. It receives data only
|
||||
when there is a call to an asynchronous initiation function (for
|
||||
example `beast::websocket::stream::async_read`) with an associated handler.
|
||||
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.
|
||||
In contrast a `beast::websocket::stream` does not independently begin
|
||||
background activity, nor does it buffer messages. It receives data only
|
||||
when there is a call to an asynchronous initiation function (for
|
||||
example `beast::websocket::stream::async_read`) with an associated handler.
|
||||
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.
|
||||
|
||||
[table
|
||||
[table
|
||||
[
|
||||
[Beast]
|
||||
[[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]]
|
||||
@@ -350,23 +364,23 @@
|
||||
lib::error_code pause_reading();
|
||||
lib::error_code resume_reading();
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[11. Connection Establishment][
|
||||
[[11. Connection Establishment][
|
||||
|
||||
websocketpp offers the `endpoint` class which can handle binding and
|
||||
listening to a port, and spawning connection objects.
|
||||
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 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.
|
||||
Beast users are free to implement their own "connection manager", but
|
||||
there is no requirement to do so.
|
||||
|
||||
[table
|
||||
[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]]
|
||||
@@ -379,31 +393,31 @@
|
||||
template <typename config>
|
||||
class endpoint : public config::socket_type;
|
||||
```]
|
||||
]]]]
|
||||
]]]]
|
||||
|
||||
[[12. WebSocket Handshaking][
|
||||
[[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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
[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]]
|
||||
@@ -414,16 +428,16 @@
|
||||
void
|
||||
accept(ConstBufferSequence const& buffers);
|
||||
|
||||
template<class Body, class Headers>
|
||||
template<class Allocator>
|
||||
void
|
||||
accept(http::request_v1<Body, Headers> const& request);
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
This function is used to asynchronously read a line ending
|
||||
in a carriage-return linefeed ("CRLF") from the stream,
|
||||
and then write it back. The function call always returns
|
||||
immediately. The asynchronous operation will continue until
|
||||
one of the following conditions is true:
|
||||
in a carriage-return ("CR") from the stream, and then write
|
||||
it back. The function call always returns immediately. The
|
||||
asynchronous operation will continue until one of the
|
||||
following conditions is true:
|
||||
|
||||
@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
|
||||
completion handler, copies will be made as required.
|
||||
The signature of the handler must be:
|
||||
The equivalent signature of the handler must be:
|
||||
@code
|
||||
void handler(
|
||||
error_code& ec // result of operation
|
||||
error_code ec // result of operation
|
||||
);
|
||||
@endcode
|
||||
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.
|
||||
//
|
||||
@@ -79,10 +90,11 @@ class echo_op
|
||||
int step = 0;
|
||||
|
||||
// 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
|
||||
// make efficient re-use of memory allocated by composed
|
||||
// operations during continuations.
|
||||
//
|
||||
// We use a custom allocator for performance, this allows
|
||||
// the implementation of the io_service to make efficient
|
||||
// re-use of memory allocated by composed operations during
|
||||
// a continuation.
|
||||
//
|
||||
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
|
||||
// memory allocation hooks associated with the final completion
|
||||
// handler, manages the lifetime of that handler for us, and
|
||||
// enforces the destroy-before-invocation requirement on memory
|
||||
// allocated using the hooks.
|
||||
// The operation's data is kept in a cheap-to-copy smart
|
||||
// pointer container called `handler_ptr`. This efficiently
|
||||
// satisfies the CopyConstructible requirements of completion
|
||||
// handlers.
|
||||
//
|
||||
// `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_;
|
||||
|
||||
@@ -123,65 +141,35 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
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.
|
||||
// The entry point for this handler. This will get called
|
||||
// as our intermediate operations complete. Definition below.
|
||||
//
|
||||
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),
|
||||
// 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:
|
||||
// read up to the first newline
|
||||
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:
|
||||
// write everything back
|
||||
@@ -217,10 +205,15 @@ operator()(beast::error_code ec, std::size_t bytes_transferred)
|
||||
break;
|
||||
}
|
||||
|
||||
// Invoke the final handler. If we wanted to pass any arguments
|
||||
// which come from our state, they would have to be moved to the
|
||||
// stack first, since the `handler_ptr` guarantees that the state
|
||||
// is destroyed before the handler is invoked.
|
||||
// Invoke the final handler. The implementation of `handler_ptr`
|
||||
// will deallocate the storage for the state before the handler
|
||||
// is invoked. This is necessary to provide the
|
||||
// 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);
|
||||
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>
|
||||
class echo_op;
|
||||
@@ -254,8 +312,8 @@ async_echo(AsyncStream& stream, CompletionToken&& token)
|
||||
// Create the composed operation and launch it. This is a constructor
|
||||
// call followed by invocation of operator(). We use handler_type
|
||||
// to convert the completion token into the correct handler type,
|
||||
// allowing user defined specializations of the async result template
|
||||
// to take effect.
|
||||
// allowing user-defined specializations of the async_result template
|
||||
// to be used.
|
||||
//
|
||||
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
|
||||
stream, init.completion_handler}(beast::error_code{}, 0);
|
||||
|
@@ -32,7 +32,7 @@ namespace beast {
|
||||
where the specific completion handler signature is known.
|
||||
|
||||
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
|
||||
@b CompletionToken is the completion handler.
|
||||
|
||||
|
@@ -42,6 +42,7 @@ public:
|
||||
/// Copy constructor
|
||||
drain_buffer(drain_buffer const&)
|
||||
{
|
||||
// Previously returned ranges are invalidated
|
||||
}
|
||||
|
||||
/// Copy assignment
|
||||
|
@@ -35,10 +35,10 @@ namespace http {
|
||||
value.
|
||||
|
||||
Field names are stored as-is, but comparisons are case-insensitive.
|
||||
When the container is iterated, the fields are presented in the order
|
||||
of insertion. For fields with the same name, the container behaves
|
||||
as a `std::multiset`; there will be a separate value for each occurrence
|
||||
of the field name.
|
||||
The container behaves as a `std::multiset`; there will be a separate
|
||||
value for each occurrence of the same field name. When the container
|
||||
is iterated the fields are presented in the order of insertion, with
|
||||
fields having the same name following each other consecutively.
|
||||
|
||||
Meets the requirements of @b Fields
|
||||
|
||||
|
@@ -53,7 +53,7 @@ struct header<true, Fields> : Fields
|
||||
|
||||
/// Indicates if the header is a request or response.
|
||||
#if BEAST_DOXYGEN
|
||||
using is_request = isRequest;
|
||||
using is_request = std::integral_constant<bool, isRequest>;
|
||||
#else
|
||||
using is_request = std::true_type;
|
||||
#endif
|
||||
@@ -203,7 +203,11 @@ struct header<false, Fields> : Fields
|
||||
"Fields requirements not met");
|
||||
|
||||
/// 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;
|
||||
#endif
|
||||
|
||||
/// The type representing the fields.
|
||||
using fields_type = Fields;
|
||||
|
@@ -546,27 +546,120 @@ upcall:
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class NextLayer>
|
||||
template<class ConstBufferSequence, class WriteHandler>
|
||||
async_return_type<
|
||||
WriteHandler, void(error_code)>
|
||||
stream<NextLayer>::
|
||||
async_write_frame(bool fin,
|
||||
ConstBufferSequence const& bs, WriteHandler&& handler)
|
||||
template<class Buffers, class Handler>
|
||||
class stream<NextLayer>::write_op
|
||||
{
|
||||
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_frame_op<ConstBufferSequence, handler_type<
|
||||
WriteHandler, void(error_code)>>{init.completion_handler,
|
||||
*this, fin, bs};
|
||||
return init.result.get();
|
||||
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 ConstBufferSequence>
|
||||
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 ConstBufferSequence, class WriteHandler>
|
||||
async_return_type<
|
||||
WriteHandler, void(error_code)>
|
||||
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,
|
||||
"AsyncStream requirements not met");
|
||||
@@ -916,13 +898,14 @@ async_write(ConstBufferSequence const& bs, WriteHandler&& handler)
|
||||
"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{});
|
||||
write_frame_op<ConstBufferSequence, handler_type<
|
||||
WriteHandler, void(error_code)>>{init.completion_handler,
|
||||
*this, fin, bs};
|
||||
return init.result.get();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class NextLayer>
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
@@ -954,6 +937,28 @@ write(ConstBufferSequence const& buffers, error_code& 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
|
||||
} // beast
|
||||
|
||||
|
@@ -47,17 +47,15 @@ using response_type = http::response<http::string_body>;
|
||||
that they are are all performed within the same implicit
|
||||
or explicit strand.
|
||||
|
||||
@par Thread Safety
|
||||
@e Distinct @e objects: Safe.@n
|
||||
@e Shared @e objects: Unsafe.
|
||||
|
||||
@par Example
|
||||
|
||||
To use the @ref stream template with an `ip::tcp::socket`,
|
||||
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
|
||||
websocket::stream<ip::tcp::socket> ws{io_service};
|
||||
@endcode
|
||||
@@ -67,9 +65,11 @@ using response_type = http::response<http::string_body>;
|
||||
websocket::stream<ip::tcp::socket&> ws{sock};
|
||||
@endcode
|
||||
|
||||
@par Thread Safety
|
||||
@e Distinct @e objects: Safe.@n
|
||||
@e Shared @e objects: Unsafe.
|
||||
@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.
|
||||
|
||||
@note A stream object must not be moved or destroyed while there
|
||||
are pending asynchronous operations associated with it.
|
||||
|
@@ -31,7 +31,7 @@ void fxx() {
|
||||
{
|
||||
//[http_snippet_2
|
||||
|
||||
request<string_body> req;
|
||||
request<empty_body> req;
|
||||
req.version = 11; // HTTP/1.1
|
||||
req.method(verb::get);
|
||||
req.target("/index.htm");
|
||||
|
Reference in New Issue
Block a user