Doc tidying

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

View File

@@ -51,6 +51,8 @@
[def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]]
[def __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]]]

View File

@@ -11,9 +11,9 @@ A __Stream__ is a communication channel where data is transferred as
an ordered sequence of octet buffers. Streams are either synchronous
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]]

View File

@@ -35,7 +35,8 @@ These metafunctions check if types match the buffer concepts:
]]
]
To suit various needs, several implementation of dynamic buffer are available:
Beast provides several dynamic buffer implementations for a variety
of scenarios:
[table Dynamic Buffer Implementations
[[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.
]]
]

View File

@@ -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.

View File

@@ -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

View File

@@ -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.
]
][
[

View File

@@ -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]]

View File

@@ -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

View File

@@ -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.

View File

@@ -29,7 +29,7 @@
[```
template<class DynamicBuffer>
void
read(opcode& op, DynamicBuffer& dynabuf)
read(DynamicBuffer& dynabuf)
```]
[
/<not available>/
@@ -171,12 +171,22 @@
[@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);
```]
]]]]
@@ -196,10 +206,14 @@
[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>/
@@ -239,7 +253,7 @@
][
[```
template<class DynamicBuffer>
read(opcode& op, DynamicBuffer& dynabuf);
read(DynamicBuffer& dynabuf);
```]
[```
template <template<class> class con_msg_manager>
@@ -414,9 +428,9 @@
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

View File

@@ -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);

View File

@@ -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.

View File

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

View File

@@ -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

View File

@@ -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;

View File

@@ -546,26 +546,119 @@ 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
{
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))
{
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();
}
};
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>
@@ -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

View File

@@ -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.

View File

@@ -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");