Doc tidying

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ These relations are true:
* `P(S(m)) == m` * `P(S(m)) == m`
We would also like our message container to have customization points We would also like our message container to have customization points
permitting the following: full allocator support, user-defined containers permitting the following: allocator awareness, user-defined containers
to represent header fields, and user-defined types and algorithms to to represent header fields, and user-defined types and algorithms to
represent the body. And finally, because requests and responses have represent the body. And finally, because requests and responses have
different fields in the ['start-line], we would like the containers for different fields in the ['start-line], we would like the containers for
@@ -52,6 +52,7 @@ struct request
std::string method; std::string method;
std::string target; std::string target;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -65,6 +66,7 @@ struct response
int status; int status;
std::string reason; std::string reason;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -83,8 +85,8 @@ However, a problem arises. How do we write a function which can accept
an object that is either a request or a response? As written, the only an object that is either a request or a response? As written, the only
obvious solution is to make the message a template type. Additional traits obvious solution is to make the message a template type. Additional traits
classes would then be needed to make sure that the passed object has a classes would then be needed to make sure that the passed object has a
valid type which meets the requirements. Instead, bypass those complexities valid type which meets the requirements. These unnecessary complexities
by making each container a partial specialization of one class: are bypassed by making each container a partial specialization:
``` ```
/// An HTTP message /// An HTTP message
template<bool isRequest, class Fields, class Body> template<bool isRequest, class Fields, class Body>
@@ -98,6 +100,7 @@ struct message<true, Fields, Body>
std::string method; std::string method;
std::string target; std::string target;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
@@ -109,6 +112,7 @@ struct message<false, Fields, Body>
int status; int status;
std::string reason; std::string reason;
Fields fields; Fields fields;
typename Body::value_type body; typename Body::value_type body;
}; };
``` ```
@@ -129,30 +133,36 @@ this, there needs to be a way to model the header portion of a message.
And we'd like to do this in a way that allows functions which take the And we'd like to do this in a way that allows functions which take the
header as a parameter, to also accept a type representing the whole header as a parameter, to also accept a type representing the whole
message (the function will see just the header part). This suggests message (the function will see just the header part). This suggests
inheritance: inheritance, by splitting a new base class off of the message:
``` ```
/// An HTTP message header /// An HTTP message header
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
struct header; struct header;
```
Code which accesses the fields has to laboriously mention the `fields`
member, so we'll not only make `header` a base class but we'll make
a quality of life improvement and derive the header from the fields
for notational convenience. In order to properly support all forms
of construction of [*Fields] there will need to be a set of suitable
constructor overloads (not shown):
```
/// An HTTP request header /// An HTTP request header
template<class Fields> template<class Fields>
struct header<true, Fields> struct header<true, Fields> : Fields
{ {
int version; int version;
std::string method; std::string method;
std::string target; std::string target;
Fields fields;
}; };
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int status;
std::string reason; std::string reason;
Fields fields;
}; };
/// An HTTP message /// An HTTP message
@@ -170,7 +180,7 @@ struct message : header<isRequest, Fields>
Note that the `message` class now has a constructor allowing messages Note that the `message` class now has a constructor allowing messages
to be constructed from a similarly typed `header`. This handles the case to be constructed from a similarly typed `header`. This handles the case
where the user already has the header and wants to make a commitment to the where the user already has the header and wants to make a commitment to the
type for [*Body]. This also lets us declare a function accepting any header: type for [*Body]. A function can be declared which accepts any header:
``` ```
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
void f(header<isRequest, Fields>& msg); void f(header<isRequest, Fields>& msg);
@@ -202,21 +212,22 @@ support the scheme. It also means that request messages could have [*four]
different allocators: two for the fields and body, and two for the method different allocators: two for the fields and body, and two for the method
and target strings. A better solution is needed. and target strings. A better solution is needed.
To get around this we make a simple change to the interface and then To get around this we make an interface modification and then add
engineer a clever concession. First, the interface change: a requirement to the [*Fields] type. First, the interface change:
``` ```
/// An HTTP request header /// An HTTP request header
template<class Fields> template<class Fields>
struct header<true, Fields> struct header<true, Fields> : Fields
{ {
int version; int version;
verb method() const; verb method() const;
string_view method_string() const; string_view method_string() const;
void method(verb); void method(verb);
void method(string_view); void method(string_view);
string_view target(); const; string_view target(); const;
void target(string_view); void target(string_view);
Fields fields;
private: private:
verb method_; verb method_;
@@ -224,13 +235,12 @@ private:
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int result;
string_view reason() const; string_view reason() const;
void reason(string_view); void reason(string_view);
Fields fields;
}; };
``` ```
@@ -239,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 a simple integer instead of the entire string, for the case where
the method is recognized from the set of known verb strings. the method is recognized from the set of known verb strings.
Now we make a concession: management of the corresponding string is Now we add a requirement to the fields type: management of the
delegated to the [*Fields] container, which can already be allocator corresponding string is delegated to the [*Fields] container, which can
aware and constructed with the necessary allocator parameter via the already be allocator aware and constructed with the necessary allocator
provided constructor overloads for `message`. The delegation parameter via the provided constructor overloads for `message`. The
implementation looks like this (only the response header specialization delegation implementation looks like this (only the response header
is shown): specialization is shown):
``` ```
/// An HTTP response header /// An HTTP response header
template<class Fields> template<class Fields>
struct header<false, Fields> struct header<false, Fields> : Fields
{ {
int version; int version;
int status; int status;
@@ -256,23 +266,24 @@ struct header<false, Fields>
string_view string_view
reason() const reason() const
{ {
return fields.reason_impl(); return this->reason_impl(); // protected member of Fields
} }
void void
reason(string_view s) reason(string_view s)
{ {
fields.reason_impl(s); this->reason_impl(s); // protected member of Fields
} }
Fields fields;
}; };
``` ```
Now that we've accomplished our initial goals and more, there is one small Now that we've accomplished our initial goals and more, there are a few
quality of life improvement to make. Users will choose different types for more quality of life improvements to make. Users will choose different
`Body` far more often than they will for `Fields`. Thus, we swap the order types for `Body` far more often than they will for `Fields`. Thus, we
of these types and provide a default: swap the order of these types and provide a default. Then, we provide
type aliases for requests and responses to soften the impact of using
`bool` to choose the specialization:
``` ```
/// An HTTP header /// An HTTP header
template<bool isRequest, class Body, class Fields = fields> template<bool isRequest, class Body, class Fields = fields>
@@ -281,6 +292,22 @@ struct header;
/// An HTTP message /// An HTTP message
template<bool isRequest, class Body, class Fields = fields> template<bool isRequest, class Body, class Fields = fields>
struct message; struct message;
/// An HTTP request
template<class Body, class Fields = fields>
using request = message<true, Body, Fields>;
/// An HTTP response
template<class Body, class Fields = fields>
using response = message<false, Body, Fields>;
```
This allows concise specification for the common cases, while
allowing for maximum customization for edge cases:
```
request<string_body> req;
response<file_body> res;
``` ```
This container is also capable of representing complete HTTP/2 messages. This container is also capable of representing complete HTTP/2 messages.

View File

@@ -29,7 +29,7 @@
[``` [```
template<class DynamicBuffer> template<class DynamicBuffer>
void void
read(opcode& op, DynamicBuffer& dynabuf) read(DynamicBuffer& dynabuf)
```] ```]
[ [
/<not available>/ /<not available>/
@@ -171,12 +171,22 @@
[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]]
][ ][
[``` [```
template<class DynamicBuffer, class ReadHandler> template<
typename async_completion<ReadHandler, void(error_code)>::result_type class DynamicBuffer, // Supports user defined types
async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); 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); void set_message_handler(message_handler h);
```] ```]
]]]] ]]]]
@@ -196,10 +206,14 @@
[websocketpp] [websocketpp]
][ ][
[``` [```
beast::async_completion<ReadHandler, void(error_code)> completion(handler); beast::async_completion<
read_op<DynamicBuffer, decltype(completion.handler)>{ ReadHandler,
void(error_code)> completion{handler};
read_op<
DynamicBuffer, decltype(completion.handler)>{
completion.handler, *this, op, buffer}; completion.handler, *this, op, buffer};
return completion.result.get();
return completion.result.get(); // Customization point
```] ```]
[ [
/<not available>/ /<not available>/
@@ -239,7 +253,7 @@
][ ][
[``` [```
template<class DynamicBuffer> template<class DynamicBuffer>
read(opcode& op, DynamicBuffer& dynabuf); read(DynamicBuffer& dynabuf);
```] ```]
[``` [```
template <template<class> class con_msg_manager> template <template<class> class con_msg_manager>
@@ -414,9 +428,9 @@
void void
accept(ConstBufferSequence const& buffers); accept(ConstBufferSequence const& buffers);
template<class Body, class Headers> template<class Allocator>
void void
accept(http::request_v1<Body, Headers> const& request); accept(http::header<true, http::basic_fields<Allocator>> const& req);
```] ```]
[ [
/<not available>/ /<not available>/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -546,26 +546,119 @@ upcall:
d_.invoke(ec); d_.invoke(ec);
} }
//------------------------------------------------------------------------------
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence, class WriteHandler> template<class Buffers, class Handler>
async_return_type< class stream<NextLayer>::write_op
WriteHandler, void(error_code)> {
stream<NextLayer>:: struct data : op
async_write_frame(bool fin, {
ConstBufferSequence const& bs, WriteHandler&& handler) 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 NextLayer>
template<class ConstBufferSequence> 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 NextLayer>
template<class ConstBufferSequence, class WriteHandler> template<class ConstBufferSequence, class WriteHandler>
async_return_type< async_return_type<
WriteHandler, void(error_code)> WriteHandler, void(error_code)>
stream<NextLayer>:: stream<NextLayer>::
async_write(ConstBufferSequence const& bs, WriteHandler&& handler) async_write_frame(bool fin,
ConstBufferSequence const& bs, WriteHandler&& handler)
{ {
static_assert(is_async_stream<next_layer_type>::value, static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream requirements not met"); "AsyncStream requirements not met");
@@ -916,13 +898,14 @@ async_write(ConstBufferSequence const& bs, WriteHandler&& handler)
"ConstBufferSequence requirements not met"); "ConstBufferSequence requirements not met");
async_completion<WriteHandler, async_completion<WriteHandler,
void(error_code)> init{handler}; void(error_code)> init{handler};
write_op<ConstBufferSequence, handler_type< write_frame_op<ConstBufferSequence, handler_type<
WriteHandler, void(error_code)>>{ WriteHandler, void(error_code)>>{init.completion_handler,
init.completion_handler, *this, bs}( *this, fin, bs};
error_code{});
return init.result.get(); return init.result.get();
} }
//------------------------------------------------------------------------------
template<class NextLayer> template<class NextLayer>
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
@@ -954,6 +937,28 @@ write(ConstBufferSequence const& buffers, error_code& ec)
write_frame(true, buffers, ec); write_frame(true, buffers, ec);
} }
template<class NextLayer>
template<class ConstBufferSequence, class WriteHandler>
async_return_type<
WriteHandler, void(error_code)>
stream<NextLayer>::
async_write(
ConstBufferSequence const& bs, WriteHandler&& handler)
{
static_assert(is_async_stream<next_layer_type>::value,
"AsyncStream requirements not met");
static_assert(beast::is_const_buffer_sequence<
ConstBufferSequence>::value,
"ConstBufferSequence requirements not met");
async_completion<WriteHandler,
void(error_code)> init{handler};
write_op<ConstBufferSequence, handler_type<
WriteHandler, void(error_code)>>{
init.completion_handler, *this, bs}(
error_code{});
return init.result.get();
}
} // websocket } // websocket
} // beast } // beast

View File

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

View File

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