mirror of
https://github.com/boostorg/beast.git
synced 2025-08-03 14:54:32 +02:00
Doc work
This commit is contained in:
@@ -3,6 +3,7 @@ Version 208:
|
||||
* Add get_lowest_layer free function
|
||||
* Add lowest_layer_type metafunction
|
||||
* Add close_socket, beast_close_socket customization
|
||||
* Doc work
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@@ -111,6 +111,12 @@
|
||||
[def __flat_static_buffer_base__ [link beast.ref.boost__beast__flat_static_buffer_base `flat_static_buffer_base`]]
|
||||
[def __websocket_stream__ [link beast.ref.boost__beast__websocket__stream `websocket::stream`]]
|
||||
|
||||
[/
|
||||
VFALCO unfortunately quickbook wants relative paths and we have no
|
||||
variables so all of the .cpp and .hpp files are placed here to keep
|
||||
the paths close to absolute.
|
||||
]
|
||||
|
||||
[import ../../example/common/detect_ssl.hpp]
|
||||
[import ../../example/doc/http_examples.hpp]
|
||||
[import ../../example/echo-op/echo_op.cpp]
|
||||
@@ -121,18 +127,20 @@
|
||||
|
||||
[import ../../test/doc/exemplars.cpp]
|
||||
[import ../../test/doc/core_snippets.cpp]
|
||||
[import ../../test/doc/core_3_layers.cpp]
|
||||
[import ../../test/doc/http_snippets.cpp]
|
||||
[import ../../test/doc/websocket_snippets.cpp]
|
||||
|
||||
[include 01_intro.qbk]
|
||||
[include 02_examples.qbk]
|
||||
[include 03_core.qbk]
|
||||
[include 04_http.qbk]
|
||||
[include 05_http_examples.qbk]
|
||||
[include 06_websocket.qbk]
|
||||
[include 07_concepts.qbk]
|
||||
[include 08_design.qbk]
|
||||
[import ../../test/doc/core_1_refresher.cpp]
|
||||
[import ../../test/doc/core_3_layers.cpp]
|
||||
|
||||
[include 01_intro/0_intro.qbk]
|
||||
[include 02_examples/0_examples.qbk]
|
||||
[include 03_core/0_core.qbk]
|
||||
[include 04_http/0_http.qbk]
|
||||
[include 05_http_examples/0_http_examples.qbk]
|
||||
[include 06_websocket/0_websocket.qbk]
|
||||
[include 07_concepts/0_concepts.qbk]
|
||||
[include 08_design/0_design.qbk]
|
||||
[include 09_releases.qbk]
|
||||
|
||||
[section:quickref Reference]
|
||||
|
@@ -137,4 +137,4 @@ for his generous participation and source code contributions.
|
||||
|
||||
|
||||
|
||||
[include 01_intro/1_reports.qbk]
|
||||
[include 1_reports.qbk]
|
@@ -9,17 +9,14 @@
|
||||
|
||||
[section:using_io Using Networking]
|
||||
|
||||
This library depends and builds on the networking facilities destined to become
|
||||
part of the official C++ standard library. The latest draft of the proposal to
|
||||
add these networking facilities is called the
|
||||
[@http://cplusplus.github.io/networking-ts/draft.pdf Networking Technical Specification].
|
||||
This specification is projected to become official no sooner than the year
|
||||
2023. There are three implementations of this specification, which differ
|
||||
cosmetically but otherwise use the same function signatures and type
|
||||
declarations: the reference networking-ts implementation, the Boost.Asio
|
||||
implementation, and the stand-alone Asio implementation. The following
|
||||
table illustrates shows how an I/O context variable is declared by including
|
||||
the appropriate header and using a suitable namespace alias:
|
||||
This library uses the
|
||||
[@http://cplusplus.github.io/networking-ts/draft.pdf Networking Technical Specification],
|
||||
scheduled to become an official part of C++ no sooner than the year
|
||||
2023. Three implementations exist, with cosmetic differences but
|
||||
otherwise using the same function signatures and type declarations:
|
||||
Boost.Asio, stand-alone Asio, and networking-ts-impl. This table shows
|
||||
how a variable of the each library's `io_context` type is declared by
|
||||
including the appropriate header and using a suitable namespace alias:
|
||||
|
||||
[table Networking Implementations
|
||||
[[Name][Namespace and Header Example ]]
|
||||
@@ -54,31 +51,31 @@ the appropriate header and using a suitable namespace alias:
|
||||
]
|
||||
|
||||
This document refers to the three implementations above interchangeably and
|
||||
collectively as "networking." The Boost.Asio and Asio flavors of networking
|
||||
provide additional functionality which is not currently proposed for C++ (but
|
||||
will likely appear in a future specification). Examples of functionality
|
||||
in Boost.Asio not present in the proposed networking draft include support for:
|
||||
collectively as [*Networking] (or just ['networking]). The Boost.Asio and
|
||||
Asio flavors of Networking provide additional features not currently proposed
|
||||
for C++, but likely to appear in a future specification, such as:
|
||||
|
||||
* [@boost:/doc/html/boost_asio/reference/serial_port.html Serial ports]
|
||||
* [@boost:/doc/html/boost_asio/reference/local__stream_protocol.html UNIX domain sockets]
|
||||
* [@boost:/doc/html/boost_asio/reference/signal_set.html POSIX signals] (e.g. SIGINT, SIGABORT)
|
||||
* [@boost:/doc/html/boost_asio/reference/ssl__stream.html TLS streams] (such as OpenSSL)
|
||||
|
||||
Boost.Beast depends specifically on the Boost.Asio flavor of Networking,
|
||||
although this may change in the future.
|
||||
While this library offers performant implementations of the HTTP and
|
||||
WebSocket network protocols, it depends on the networking interfaces
|
||||
to perform general tasks such as performing domain name resolution
|
||||
(DNS lookup), establishing outgoing connections, and accepting incoming
|
||||
connections. Callers are responsible for interacting with networking
|
||||
to initialize objects to the correct state where they are usable by
|
||||
this library.
|
||||
|
||||
In this documentation, the example code, and the implementation, the `net`
|
||||
namespace is used to qualify networking identifiers. For Boost.Beast,
|
||||
namespace is used to qualify Networking identifiers. For Boost.Beast,
|
||||
`net` will be an alias for the `boost::asio` namespace.
|
||||
|
||||
While this library offers performant implementations of the HTTP and
|
||||
WebSocket network protocols, it depends on the networking interface
|
||||
to perform tasks which are not specific to the protocol. Examples of
|
||||
these tasks include performing domain name resolution (DNS lookup),
|
||||
establishing outgoing connections, and accepting incoming connections.
|
||||
Callers are responsible for interacting with networking to initialize
|
||||
objects to the correct state where they are usable by this library.
|
||||
|
||||
To facilitiate interacting with networking, the library provides an
|
||||
extensive collection of types and algorithms. This section of the
|
||||
documentation explains these types and algorithms, provides examples
|
||||
To further ease of use, this library provides an extensive collection
|
||||
of types and algorithms. This section of the documentation explains these types and algorithms, provides examples
|
||||
of usage, and also provides refreshers and tutorials for working with
|
||||
networking.
|
||||
|
||||
@@ -92,12 +89,12 @@ effect:
|
||||
[snippet_core_1a]
|
||||
[snippet_core_1b]
|
||||
|
||||
[include 03_core/1_refresher.qbk]
|
||||
[include 03_core/2_streams.qbk]
|
||||
[include 03_core/3_layers.qbk]
|
||||
[include 03_core/4_buffers.qbk]
|
||||
[include 03_core/5_files.qbk]
|
||||
[include 03_core/6_composed.qbk]
|
||||
[include 03_core/7_detect_ssl.qbk]
|
||||
[include 1_refresher.qbk]
|
||||
[include 2_streams.qbk]
|
||||
[include 3_layers.qbk]
|
||||
[include 4_buffers.qbk]
|
||||
[include 5_files.qbk]
|
||||
[include 6_composed.qbk]
|
||||
[include 7_detect_ssl.qbk]
|
||||
|
||||
[endsect]
|
@@ -9,8 +9,8 @@
|
||||
|
||||
[section:asio_refresher Refresher]
|
||||
|
||||
To use Beast effectively, a prior understanding of networking is required.
|
||||
This section reviews networking concepts as a reminder and guide for further
|
||||
To use Beast effectively, a prior understanding of Networking is required.
|
||||
This section reviews these concepts as a reminder and guide for further
|
||||
learning.
|
||||
|
||||
A
|
||||
@@ -44,46 +44,37 @@ interact with networking using various flavors of interfaces such as
|
||||
[@https://en.wikipedia.org/wiki/Berkeley_sockets ['Berkeley sockets]] or
|
||||
[@https://en.wikipedia.org/wiki/Winsock ['Windows Sockets 2]] ("Winsock").
|
||||
|
||||
C++ Networking, represented by __NetTS__ and __Asio__, provides another layer
|
||||
of abstraction with features such as:
|
||||
Networking in C++, represented by __Asio__,
|
||||
[@https://think-async.com/Asio/ Asio], and
|
||||
__NetTS__, provides a layer of abstraction to interact portably with the
|
||||
operating system facilities for not just networking but general
|
||||
[@https://en.wikipedia.org/wiki/Input/output ['input/output]] ("I/O").
|
||||
|
||||
* Deadline timers
|
||||
* Buffer sequences
|
||||
* Stream concepts
|
||||
* Asynchronous I/O
|
||||
|
||||
These concepts enable generic programming so that higher levels of abstraction
|
||||
may be composed to arbitrary degree. In fact, the interfaces and concepts
|
||||
offered by networking are best described as providing support for general
|
||||
[@https://en.wikipedia.org/wiki/Input/output ['input/output]] ("I/O")
|
||||
algorithms, including networking.
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
[heading Buffers]
|
||||
|
||||
A
|
||||
[@https://en.wikipedia.org/wiki/Data_buffer ['buffer]]
|
||||
holds a contiguous sequence of bytes used when reading or writing data with
|
||||
objects that perform I/O.
|
||||
The networking types __const_buffer__ and __mutable_buffer__ represent
|
||||
these memory regions as type-safe pointer/size pairs, as shown below:
|
||||
```
|
||||
net::const_buffer cb(string_view("Hello, world!", 13));
|
||||
assert(string_view(reinterpret_cast<char const*>(cb.data()), cb.size()) == "Hello, world!");
|
||||
holds a contiguous sequence of bytes used when performing I/O.
|
||||
The types
|
||||
[@boost:/doc/html/boost_asio/reference/const_buffer.html `net::const_buffer`]
|
||||
and
|
||||
[@boost:/doc/html/boost_asio/reference/mutable_buffer.html `net::mutable_buffer`]
|
||||
represent these memory regions as type-safe pointer/size pairs:
|
||||
|
||||
char storage[13];
|
||||
net::mutable_buffer mb(bytes, sizeof(storage));
|
||||
std::memcpy(mb.data(), cb.data(), mb.size());
|
||||
assert(string_view(reinterpret_cast<char const*>(mb.data()), mb.size()) == "Hello, world!");
|
||||
```
|
||||
[code_core_1_refresher_1s]
|
||||
|
||||
[tip
|
||||
Networking uses custom buffer types because `span<byte>` does too much.
|
||||
It not only type-erases the original pointer but also recasts it to a
|
||||
pointer-to-byte. The operating system doesn't care about this, but if
|
||||
a user wants to send and receive an array of some other type, presenting
|
||||
it as an array of bytes which supports bitwise operations is unnecessary.
|
||||
Custom buffer types also enable networking implementations to provide
|
||||
targeted features such as
|
||||
`const_buffer` and `mutable_buffer` are preferred over `std::span<byte>`
|
||||
and `span<byte const>` because
|
||||
[@https://en.cppreference.com/w/cpp/container/span `std::span`]
|
||||
does too much. It not only
|
||||
type-erases the original pointer but also recasts it to a pointer-to-byte.
|
||||
The operating system doesn't care about this, but if a user wants to send
|
||||
and receive an array of some other type, presenting it as an array of bytes
|
||||
which supports bitwise operations is unnecessary. Custom buffer types also
|
||||
enable implementations to provide targeted features such as
|
||||
[@boost:/doc/html/boost_asio/overview/core/buffers.html#boost_asio.overview.core.buffers.buffer_debugging ['buffer debugging]]
|
||||
without changing the more general vocabulary types.
|
||||
]
|
||||
@@ -91,36 +82,71 @@ these memory regions as type-safe pointer/size pairs, as shown below:
|
||||
The concepts
|
||||
__ConstBufferSequence__ and __MutableBufferSequence__ describe bidirectional
|
||||
ranges whose value type is convertible to `const_buffer` and
|
||||
`mutable_buffer` respectively. Buffer sequences may be used to transact
|
||||
in multiple buffers in a single function call, a technique sometimes
|
||||
referred to as
|
||||
`mutable_buffer` respectively. These sequences allow transacting with
|
||||
multiple buffers in a single function call, a technique called
|
||||
[@https://en.wikipedia.org/wiki/Vectored_I/O ['scatter/gather I/O]].
|
||||
Buffers and sequences are non-owning; copies produce shallow references and
|
||||
not duplicates of the underlying memory. Each of these statements declares
|
||||
Buffers and buffer sequences are non-owning; copies produce shallow references
|
||||
and not duplicates of the underlying memory. Each of these statements declares
|
||||
a buffer sequence:
|
||||
```
|
||||
net::const_buffer b1;
|
||||
net::mutable_buffer b2;
|
||||
std::array<net::const_buffer, 3> b3;
|
||||
```
|
||||
|
||||
The __DynamicBuffer__ concept defines a buffer container with an interface
|
||||
that supports increasing and decreasing the size of the managed buffer
|
||||
sequence. Beast and networking use dynamic buffers when the amount of
|
||||
storage required to perform an operation is not known ahead of time,
|
||||
such as when reading a complete HTTP message.
|
||||
[code_core_1_refresher_2s]
|
||||
|
||||
The functions
|
||||
[@boost:/doc/html/boost_asio/reference/buffer_size.html `net::buffer_size`] and
|
||||
[@boost:/doc/html/boost_asio/reference/buffer_copy.html `net::buffer_copy`]
|
||||
determine the total number of bytes in a buffer sequence, and transfer some
|
||||
or all of bytes from one buffer sequence to another respectively. The
|
||||
function `buffer_size` is a customization point: user defined overloads
|
||||
in foreign namespaces are possible, and callers should invoke `buffer_size`
|
||||
without namespace qualification. The functions
|
||||
[@boost:/doc/html/boost_asio/reference/buffer_sequence_begin.html `net::buffer_sequence_begin`] and
|
||||
[@boost:/doc/html/boost_asio/reference/buffer_sequence_end.html `net::buffer_sequence_end`]
|
||||
are used to obtain a pair of iterators for traversing the sequence.
|
||||
Beast provides a set of buffer sequence types and algorithms such as
|
||||
[link beast.ref.boost__beast__buffers_cat `buffers_cat`],
|
||||
[link beast.ref.boost__beast__buffers_front `buffers_front`],
|
||||
[link beast.ref.boost__beast__buffers_prefix `buffers_prefix`],
|
||||
[link beast.ref.boost__beast__buffers_range `buffers_range`], and
|
||||
[link beast.ref.boost__beast__buffers_suffix `buffers_suffix`].
|
||||
This example returns the bytes in a buffer sequence as a string:
|
||||
|
||||
[code_core_1_refresher_1]
|
||||
|
||||
The __DynamicBuffer__ concept defines a resizable buffer sequence interface.
|
||||
Algorithms may be expressed in terms of dynamic buffers when the memory
|
||||
requirements are not known ahead of time, for example when reading an
|
||||
HTTP message from a stream.
|
||||
Beast provides a well-rounded collection of dynamic buffer types such as
|
||||
[link beast.ref.boost__beast__buffers_adaptor `buffers_adaptor`],
|
||||
[link beast.ref.boost__beast__flat_buffer `flat_buffer`],
|
||||
[link beast.ref.boost__beast__multi_buffer `multi_buffer`], and
|
||||
[link beast.ref.boost__beast__static_buffer `static_buffer`].
|
||||
In the following function, the contents of a stream are read into a dynamic
|
||||
buffer until it contains a newline character, using
|
||||
[@boost:/doc/html/boost_asio/reference/buffers_iterator.html `net::buffers_iterator`]
|
||||
to treat the contents of the buffer as a range of characters:
|
||||
|
||||
[code_core_1_refresher_2]
|
||||
|
||||
|
||||
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
[heading Synchronous I/O]
|
||||
|
||||
Synchronous input and output is accomplished through blocking function
|
||||
calls that provide the complete results of the operation upon returning.
|
||||
Such operations typically cannot be canceled and do not have a method for
|
||||
setting a timeout. The __SyncReadStream__ and __SyncWriteStream__ concepts
|
||||
define requirements for
|
||||
calls that return with the result of the operation. Such operations typically
|
||||
cannot be canceled and do not have a method for setting a timeout. The
|
||||
__SyncReadStream__ and __SyncWriteStream__ concepts define requirements for
|
||||
['synchronous streams]:
|
||||
a portable I/O abstraction that exchanges data using buffer sequences
|
||||
to represent bytes and either `error_code` or an exception to report
|
||||
any failures.
|
||||
a portable I/O abstraction that transfers data using buffer sequences to
|
||||
represent bytes and either `error_code` or an exception to report any
|
||||
failures.
|
||||
[@boost:/doc/html/boost_asio/reference/basic_stream_socket.html ['net::basic_stream_socket]]
|
||||
is a synchronous stream commonly used to form TCP/IP connections.
|
||||
User-defined types which meet the requirements are possible:
|
||||
|
||||
[code_core_1_refresher_3]
|
||||
|
||||
A
|
||||
['synchronous stream algorithm]
|
||||
@@ -128,35 +154,13 @@ is written as a function template accepting a stream object meeting the
|
||||
named requirements for synchronous reading, writing, or both. This example
|
||||
shows an algorithm which writes text and uses exceptions to indicate errors:
|
||||
|
||||
```
|
||||
template <class SyncWriteStream>
|
||||
void hello (SyncWriteStream& stream)
|
||||
{
|
||||
net::const_buffer cb(string_view("Hello, world!"));
|
||||
do
|
||||
{
|
||||
auto bytes_transferred = stream.write_some(cb); // may throw
|
||||
cb += bytes_transferred; // adjust the pointer and size
|
||||
}
|
||||
while (cb.size() > 0);
|
||||
}
|
||||
```
|
||||
[code_core_1_refresher_4]
|
||||
|
||||
The same algorithm may be expressed using error codes instead of exceptions:
|
||||
|
||||
```
|
||||
template <class SyncWriteStream>
|
||||
void hello (SyncWriteStream& stream, error_code& ec)
|
||||
{
|
||||
net::const_buffer cb(string_view("Hello, world!"));
|
||||
do
|
||||
{
|
||||
auto bytes_transferred = stream.write_some(cb, ec);
|
||||
cb += bytes_transferred; // adjust the pointer and size
|
||||
}
|
||||
while (cb.size() > 0 && ! ec);
|
||||
}
|
||||
```
|
||||
[code_core_1_refresher_5]
|
||||
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
[heading Asynchronous I/O]
|
||||
|
||||
@@ -164,9 +168,9 @@ An asynchronous operation begins with a call to an
|
||||
[@boost:/doc/html/boost_asio/reference/asynchronous_operations.html ['initiating function]],
|
||||
which starts the operation and returns to the caller immediately. This
|
||||
['outstanding]
|
||||
asynchronous operation continues to make progress concurrently without
|
||||
blocking. When the externally observable side effects are fully established,
|
||||
a movable function object known as a
|
||||
asynchronous operation proceeds concurrently without blocking the caller.
|
||||
When the externally observable side effects are fully established, a movable
|
||||
function object known as a
|
||||
[@boost:/doc/html/boost_asio/reference/CompletionHandler.html ['completion handler]]
|
||||
provided in the initiating function call is queued for execution with the
|
||||
results, which may include the error code and other specific information.
|
||||
@@ -174,32 +178,42 @@ An asynchronous operation is said to be
|
||||
['completed]
|
||||
after the completion handler is queued. The code that follows shows how some
|
||||
text may be written to a
|
||||
[@boost:/doc/html/boost_asio/reference/ip__tcp/socket.html `socket`]
|
||||
[@boost:/doc/html/boost_asio/reference/ip__tcp/socket.html `net::socket`]
|
||||
asynchronously, invoking a lambda when the
|
||||
operation is complete:
|
||||
```
|
||||
net::async_write(sock, net::const_buffer(string_view("Hello, world!")),
|
||||
[](error_code ec, std::size_t bytes_transferred)
|
||||
{
|
||||
if(! ec)
|
||||
assert(bytes_transferred == 13);
|
||||
else
|
||||
std::cerr << "Error: " << ec.message() << "\n";
|
||||
});
|
||||
```
|
||||
|
||||
[code_core_1_refresher_3s]
|
||||
|
||||
Every completion handler (also referred to as a
|
||||
[@https://en.wikipedia.org/wiki/Continuation ['continuation]])
|
||||
has both an
|
||||
[@boost:/doc/html/boost_asio/overview/core/allocation.html ['associated allocator]]
|
||||
returned by
|
||||
[@boost:/doc/html/boost_asio/reference/get_associated_allocator.html `net::get_associated_allocator`],
|
||||
and an
|
||||
[@boost:/doc/html/boost_asio/reference/associated_executor.html ['associated executor]].
|
||||
The allocator may be used to obtain temporary storage (which [*must] be
|
||||
deallocated before the completion handler is invoked), while the executor
|
||||
is a cheaply copyable object providing the algorithm used to invoke the
|
||||
completion handler. Unless customized by the caller, a completion handler
|
||||
defaults to using `std::allocator<void>` and the executor of the
|
||||
corresponding I/O object.
|
||||
[@boost:/doc/html/boost_asio/reference/associated_executor.html ['associated executor]]
|
||||
returned by
|
||||
[@boost:/doc/html/boost_asio/reference/get_associated_executor.html `net::get_associated_executor`].
|
||||
These associations may be specified intrusively:
|
||||
|
||||
[code_core_1_refresher_6]
|
||||
|
||||
Or these associations may be specified non-intrusively, by specializing
|
||||
the class templates
|
||||
[@boost:/doc/html/boost_asio/reference/associated_allocator.html `net::associated_allocator`]
|
||||
and
|
||||
[@boost:/doc/html/boost_asio/reference/associated_executor.html `net::associated_executor`].
|
||||
The function
|
||||
[@boost:/doc/html/boost_asio/reference/bind_executor.html `net::bind_executor`]
|
||||
may be used when the caller wants to change the executor of a completion
|
||||
handler.
|
||||
|
||||
The allocator is used by the implementation to obtain any temporary storage
|
||||
necessary to perform the operation. Temporary allocations are always freed
|
||||
before the completion handler is invoked. The executor is a cheaply copyable
|
||||
object providing the algorithm used to invoke the completion handler. Unless
|
||||
customized by the caller, a completion handler defaults to using
|
||||
`std::allocator<void>` and the executor of the corresponding I/O object.
|
||||
|
||||
Networking prescribes facilities to determine the context in which
|
||||
handlers run. Every I/O object refers to an __ExecutionContext__ for
|
||||
@@ -218,15 +232,10 @@ is written as a templated initiating function template accepting a stream
|
||||
object meeting the named requirements for asynchronous reading, writing, or
|
||||
both. This example shows an algorithm which writes some text to an
|
||||
asynchronous stream:
|
||||
```
|
||||
template <class AsyncWriteStream, class WriteHandler>
|
||||
void async_hello (AsyncWriteStream& stream, WriteHandler&& handler)
|
||||
{
|
||||
net::async_write (stream,
|
||||
net::buffer(string_view("Hello, world!")),
|
||||
std::forward<Handler>(handler));
|
||||
}
|
||||
```
|
||||
|
||||
[code_core_1_refresher_7]
|
||||
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
[heading Concurrency]
|
||||
|
||||
@@ -245,42 +254,57 @@ without explicit locking by requiring all access to I/O objects to be
|
||||
performed within a
|
||||
[@boost:/doc/html/boost_asio/overview/core/strands.html ['strand]].
|
||||
|
||||
[heading Asynchronous Model]
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
Completion handlers are native to networking but cause an inversion of the
|
||||
flow of control. Alternatives to using completion handlers include futures,
|
||||
fibers, coroutines, or user-defined types. Networking supports these
|
||||
alternatives with a feature that provides these hooks for customizing
|
||||
initiating functions:
|
||||
[heading Universal Model]
|
||||
|
||||
* Converting a custom ['CompletionToken] to a "real" handler type
|
||||
Because completion handlers cause an inversion of the flow of control,
|
||||
sometimes other methods of attaching a continuation are desired. Networking
|
||||
provides the
|
||||
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf ['Universal Model for Asynchronous Operations]],
|
||||
providing a customizable means for transforming the signature of the initiating
|
||||
function to use other types of objects and methods in place of a completion
|
||||
handler callback. For example to call to write a string to a socket
|
||||
asynchronously, using a `std::future` to receive the number of bytes transferred
|
||||
thusly looks like this:
|
||||
|
||||
* Creating the initiating function's result
|
||||
[code_core_1_refresher_4s]
|
||||
|
||||
This functionality is enabled by passing the variable
|
||||
[@boost:/doc/html/boost_asio/reference/use_future.html `net::use_future`]
|
||||
(of type
|
||||
[@boost:/doc/html/boost_asio/reference/use_future_t.html `net::use_future_t<>`])
|
||||
in place of the completion handler. The same `async_write` function overload
|
||||
can work with a
|
||||
[@https://en.wikipedia.org/wiki/Fiber_(computer_science) ['fiber]]
|
||||
launched with
|
||||
[@boost:/doc/html/boost_asio/reference/spawn/overload1.html `net::spawn`]:
|
||||
|
||||
[code_core_1_refresher_5s]
|
||||
|
||||
In both of these cases, an object with a specific type is used in place of
|
||||
the completion handler, and the return value of the initiating function
|
||||
is transformed from `void` to `std::future<std::size_t>` or `std::size_t`.
|
||||
The return type transformation is supported by customization points in the
|
||||
initiating function signature. Here is the signature for `net::async_write`:
|
||||
|
||||
[code_core_1_refresher_8]
|
||||
|
||||
[/
|
||||
The system
|
||||
for customizing the return type of initiating functions and obtaining the
|
||||
actual completion handler from a completion token is known as the
|
||||
[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf ['Universal Model for Asynchronous Operations]] (N3747).
|
||||
This generic example shows the signature for an initiating function to
|
||||
write some text to a stream:
|
||||
```
|
||||
template <class AsyncWriteStream, class WriteHandler>
|
||||
auto async_hello (AsyncWriteStream& stream, WriteHandler&& handler);
|
||||
```
|
||||
|
||||
The signature for the initiating function includes a
|
||||
['completion token],
|
||||
which is a generalization of completion handlers permitting user-defined
|
||||
types such as futures or coroutines to be substituted as the mechanism by
|
||||
which the results of the asynchronous operation are delivered. The following
|
||||
statements all call the same function to asynchronously read data from a
|
||||
stream, but use a different method for receiving the results:
|
||||
]
|
||||
The type of the function's return value is determined by the
|
||||
[@boost:/doc/html/boost_asio/reference/async_result.html `net::async_result`]
|
||||
customization point, which comes with specializations for common library
|
||||
types such as `std::future` and may also be specialized for user-defined
|
||||
types. The universal model also provides the
|
||||
[@boost:/doc/html/boost_asio/reference/async_completion.html `net::async_completion`]
|
||||
customization point for transforming
|
||||
the `handler` argument (called a
|
||||
[@boost:/doc/html/boost_asio/reference/asynchronous_operations/completion_token.html ['CompletionToken]]
|
||||
in this context) into an underlying completion handler to be invoked when the
|
||||
operation is complete. This transformed, internal handler is responsible for
|
||||
the finalizing step that delivers the result of the operation to the caller.
|
||||
For example, when using `net::use_future` the internal handler will deliver
|
||||
the result by calling `std::promise::set_value` on the promise object
|
||||
returned by the initiating function.
|
||||
|
||||
[/-----------------------------------------------------------------------------]
|
||||
|
||||
|
@@ -198,15 +198,15 @@ parts of the implementation. The layers are arranged thusly:
|
||||
[http_snippet_1]
|
||||
]
|
||||
|
||||
[include 04_http/01_primer.qbk]
|
||||
[include 04_http/02_message.qbk]
|
||||
[include 04_http/03_streams.qbk]
|
||||
[include 04_http/04_serializer_streams.qbk]
|
||||
[include 04_http/05_parser_streams.qbk]
|
||||
[include 04_http/06_serializer_buffers.qbk]
|
||||
[include 04_http/07_parser_buffers.qbk]
|
||||
[include 04_http/08_chunked_encoding.qbk]
|
||||
[include 04_http/09_custom_body.qbk]
|
||||
[include 04_http/10_custom_parsers.qbk]
|
||||
[include 01_primer.qbk]
|
||||
[include 02_message.qbk]
|
||||
[include 03_streams.qbk]
|
||||
[include 04_serializer_streams.qbk]
|
||||
[include 05_parser_streams.qbk]
|
||||
[include 06_serializer_buffers.qbk]
|
||||
[include 07_parser_buffers.qbk]
|
||||
[include 08_chunked_encoding.qbk]
|
||||
[include 09_custom_body.qbk]
|
||||
[include 10_custom_parsers.qbk]
|
||||
|
||||
[endsect]
|
@@ -29,13 +29,13 @@ Boost.Asio with a consistent asynchronous model using a modern C++ approach.
|
||||
[ws_snippet_1]
|
||||
]
|
||||
|
||||
[include 06_websocket/01_streams.qbk]
|
||||
[include 06_websocket/02_connect.qbk]
|
||||
[include 06_websocket/03_client.qbk]
|
||||
[include 06_websocket/04_server.qbk]
|
||||
[include 06_websocket/05_messages.qbk]
|
||||
[include 06_websocket/06_control.qbk]
|
||||
[include 06_websocket/07_teardown.qbk]
|
||||
[include 06_websocket/08_notes.qbk]
|
||||
[include 01_streams.qbk]
|
||||
[include 02_connect.qbk]
|
||||
[include 03_client.qbk]
|
||||
[include 04_server.qbk]
|
||||
[include 05_messages.qbk]
|
||||
[include 06_control.qbk]
|
||||
[include 07_teardown.qbk]
|
||||
[include 08_notes.qbk]
|
||||
|
||||
[endsect]
|
@@ -11,14 +11,14 @@
|
||||
|
||||
This section describes all of the concepts defined by the library.
|
||||
|
||||
[include 07_concepts/Body.qbk]
|
||||
[include 07_concepts/BodyReader.qbk]
|
||||
[include 07_concepts/BodyWriter.qbk]
|
||||
[include 07_concepts/BufferSequence.qbk]
|
||||
[include 07_concepts/DynamicBuffer.qbk]
|
||||
[include 07_concepts/Fields.qbk]
|
||||
[include 07_concepts/FieldsWriter.qbk]
|
||||
[include 07_concepts/File.qbk]
|
||||
[include 07_concepts/Streams.qbk]
|
||||
[include Body.qbk]
|
||||
[include BodyReader.qbk]
|
||||
[include BodyWriter.qbk]
|
||||
[include BufferSequence.qbk]
|
||||
[include DynamicBuffer.qbk]
|
||||
[include Fields.qbk]
|
||||
[include FieldsWriter.qbk]
|
||||
[include File.qbk]
|
||||
[include Streams.qbk]
|
||||
|
||||
[endsect]
|
@@ -66,9 +66,9 @@ interfaces of Beast (which have since changed).
|
||||
</mediaobject>
|
||||
''']
|
||||
|
||||
[include 08_design/1_http_message.qbk]
|
||||
[include 08_design/2_http_comparison.qbk]
|
||||
[include 08_design/3_websocket_zaphoyd.qbk]
|
||||
[include 08_design/4_faq.qbk]
|
||||
[include 1_http_message.qbk]
|
||||
[include 2_http_comparison.qbk]
|
||||
[include 3_websocket_zaphoyd.qbk]
|
||||
[include 4_faq.qbk]
|
||||
|
||||
[endsect]
|
@@ -17,9 +17,11 @@ add_executable (tests-doc
|
||||
${EXTRAS_FILES}
|
||||
${TEST_MAIN}
|
||||
Jamfile
|
||||
snippets.hpp
|
||||
snippets.ipp
|
||||
core_examples.cpp
|
||||
core_snippets.cpp
|
||||
core_1_refresher.cpp
|
||||
core_3_layers.cpp
|
||||
http_examples.cpp
|
||||
http_snippets.cpp
|
||||
|
@@ -20,6 +20,7 @@ alias run-tests :
|
||||
[ compile http_snippets.cpp ]
|
||||
[ compile websocket_snippets.cpp ]
|
||||
[ run core_examples.cpp $(TEST_MAIN) ]
|
||||
[ run core_1_refresher.cpp $(TEST_MAIN) ]
|
||||
[ run core_3_layers.cpp $(TEST_MAIN) ]
|
||||
[ run http_examples.cpp $(TEST_MAIN) ]
|
||||
;
|
||||
@@ -27,6 +28,7 @@ alias run-tests :
|
||||
exe fat-tests :
|
||||
$(TEST_MAIN)
|
||||
core_examples.cpp
|
||||
core_1_refresher.cpp
|
||||
core_3_layers.cpp
|
||||
http_examples.cpp
|
||||
;
|
||||
|
347
test/doc/core_1_refresher.cpp
Normal file
347
test/doc/core_1_refresher.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
|
||||
#include "snippets.hpp"
|
||||
|
||||
#include <boost/beast/_experimental/unit_test/suite.hpp>
|
||||
#include <boost/beast/_experimental/test/stream.hpp>
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/buffers_iterator.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/use_future.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
namespace boost {
|
||||
namespace beast {
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
snippets()
|
||||
{
|
||||
#include "snippets.ipp"
|
||||
{
|
||||
//[code_core_1_refresher_1s
|
||||
net::const_buffer cb("Hello, world!", 13);
|
||||
assert(string_view(reinterpret_cast<char const*>(
|
||||
cb.data()), cb.size()) == "Hello, world!");
|
||||
|
||||
char storage[13];
|
||||
net::mutable_buffer mb(storage, sizeof(storage));
|
||||
std::memcpy(mb.data(), cb.data(), mb.size());
|
||||
assert(string_view(reinterpret_cast<char const*>(
|
||||
mb.data()), mb.size()) == "Hello, world!");
|
||||
//]
|
||||
}
|
||||
{
|
||||
//[code_core_1_refresher_2s
|
||||
net::const_buffer b1; // a ConstBufferSequence by definition
|
||||
net::mutable_buffer b2; // a MutableBufferSequence by definition
|
||||
std::array<net::const_buffer, 3> b3; // A ConstBufferSequence by named requirements
|
||||
//]
|
||||
}
|
||||
{
|
||||
//[code_core_1_refresher_3s
|
||||
// initiate an asynchronous write operation
|
||||
net::async_write(sock, net::const_buffer("Hello, world!", 13),
|
||||
[](error_code ec, std::size_t bytes_transferred)
|
||||
{
|
||||
// this lambda is invoked when the write operation completes
|
||||
if(! ec)
|
||||
assert(bytes_transferred == 13);
|
||||
else
|
||||
std::cerr << "Error: " << ec.message() << "\n";
|
||||
});
|
||||
// meanwhile, the operation is outstanding and execution continues from here
|
||||
//]
|
||||
}
|
||||
{
|
||||
//[code_core_1_refresher_4s
|
||||
std::future<std::size_t> f = net::async_write(sock,
|
||||
net::const_buffer("Hello, world!", 13), net::use_future);
|
||||
//]
|
||||
}
|
||||
{
|
||||
//[code_core_1_refresher_5s
|
||||
net::spawn(
|
||||
[&sock](net::yield_context yield)
|
||||
{
|
||||
std::size_t bytes_transferred = net::async_write(sock,
|
||||
net::const_buffer("Hello, world!", 13), yield);
|
||||
(void)bytes_transferred;
|
||||
});
|
||||
//]
|
||||
}
|
||||
}
|
||||
|
||||
//[code_core_1_refresher_1
|
||||
template <class ConstBufferSequence>
|
||||
std::string string_from_buffers (ConstBufferSequence const& buffers)
|
||||
{
|
||||
// check that the type meets the requirements using the provided type traits
|
||||
static_assert(
|
||||
net::is_const_buffer_sequence<ConstBufferSequence>::value,
|
||||
"ConstBufferSequence requirements not met");
|
||||
|
||||
// optimization: reserve all the space for the string first
|
||||
std::string result;
|
||||
using net::buffer_size; // buffer_size is a customization point,
|
||||
result.reserve(buffer_size(buffers)); // called without namespace qualification
|
||||
|
||||
// iterate over each buffer in the sequence and append it to the string
|
||||
for(auto it = net::buffer_sequence_begin(buffers); // returns an iterator to beginning of the sequence
|
||||
it != net::buffer_sequence_end(buffers);) // returns a past-the-end iterator to the sequence
|
||||
{
|
||||
// A buffer sequence iterator's value_type is always convertible to net::const_buffer
|
||||
net::const_buffer buffer = *it++;
|
||||
|
||||
// A cast is always required to out-out of type-safety
|
||||
result.append(static_cast<char const*>(buffer.data()), buffer.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//]
|
||||
|
||||
//[code_core_1_refresher_2
|
||||
// Read a line ending in '\n' from a socket, returning
|
||||
// the number of characters up to but not including the newline
|
||||
template <class DynamicBuffer>
|
||||
std::size_t read_line(net::ip::tcp::socket& sock, DynamicBuffer& buffer)
|
||||
{
|
||||
// this alias keeps things readable
|
||||
using range = net::buffers_iterator<
|
||||
typename DynamicBuffer::const_buffers_type>;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
// get iterators representing the range of characters in the buffer
|
||||
auto begin = range::begin(buffer.data());
|
||||
auto end = range::end(buffer.data());
|
||||
|
||||
// search for "\n" and return if found
|
||||
auto pos = std::find(begin, end, '\n');
|
||||
if(pos != range::end(buffer.data()))
|
||||
return std::distance(begin, end);
|
||||
|
||||
// Determine the number of bytes to read,
|
||||
// using available capacity in the buffer first.
|
||||
std::size_t bytes_to_read = std::min<std::size_t>(
|
||||
std::max<std::size_t>(512, // under 512 is too little,
|
||||
buffer.capacity() - buffer.size()),
|
||||
std::min<std::size_t>(65536, // and over 65536 is too much.
|
||||
buffer.max_size() - buffer.size()));
|
||||
|
||||
// Read up to bytes_to_read bytes into the dynamic buffer
|
||||
buffer.commit(sock.read_some(buffer.prepare(bytes_to_read)));
|
||||
}
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//[code_core_1_refresher_3
|
||||
// Meets the requirements of SyncReadStream
|
||||
struct sync_read_stream
|
||||
{
|
||||
// Returns the number of bytes read upon success, otherwise throws an exception
|
||||
template <class MutableBufferSequence>
|
||||
std::size_t read_some(MutableBufferSequence const& buffers);
|
||||
|
||||
// Returns the number of bytes read successfully, sets the error code if a failure occurs
|
||||
template <class MutableBufferSequence>
|
||||
std::size_t read_some(MutableBufferSequence const& buffers, error_code& ec);
|
||||
};
|
||||
|
||||
// Meets the requirements of SyncWriteStream
|
||||
struct sync_write_stream
|
||||
{
|
||||
// Returns the number of bytes written upon success, otherwise throws an exception
|
||||
template <class ConstBufferSequence>
|
||||
std::size_t write_some(ConstBufferSequence const& buffers);
|
||||
|
||||
// Returns the number of bytes written successfully, sets the error code if a failure occurs
|
||||
template <class ConstBufferSequence>
|
||||
std::size_t write_some(ConstBufferSequence const& buffers, error_code& ec);
|
||||
};
|
||||
//]
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t sync_read_stream::read_some(MutableBufferSequence const&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t sync_read_stream::read_some(MutableBufferSequence const&, error_code&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t sync_write_stream::write_some(ConstBufferSequence const&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t sync_write_stream::write_some(ConstBufferSequence const&, error_code&)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
BOOST_STATIC_ASSERT(is_sync_read_stream<sync_read_stream>::value);
|
||||
BOOST_STATIC_ASSERT(is_sync_write_stream<sync_write_stream>::value);
|
||||
|
||||
//[code_core_1_refresher_4
|
||||
template <class SyncWriteStream>
|
||||
void hello (SyncWriteStream& stream)
|
||||
{
|
||||
net::const_buffer cb("Hello, world!", 13);
|
||||
do
|
||||
{
|
||||
auto bytes_transferred = stream.write_some(cb); // may throw
|
||||
cb += bytes_transferred; // adjust the pointer and size
|
||||
}
|
||||
while (cb.size() > 0);
|
||||
}
|
||||
//]
|
||||
|
||||
//[code_core_1_refresher_5
|
||||
template <class SyncWriteStream>
|
||||
void hello (SyncWriteStream& stream, error_code& ec)
|
||||
{
|
||||
net::const_buffer cb("Hello, world!", 13);
|
||||
do
|
||||
{
|
||||
auto bytes_transferred = stream.write_some(cb, ec);
|
||||
cb += bytes_transferred; // adjust the pointer and size
|
||||
}
|
||||
while (cb.size() > 0 && ! ec);
|
||||
}
|
||||
//]
|
||||
|
||||
//[code_core_1_refresher_6
|
||||
// Intrusively specify an associated allocator and executor
|
||||
struct handler
|
||||
{
|
||||
using allocator_type = std::allocator<char>;
|
||||
allocator_type get_allocator() const noexcept;
|
||||
|
||||
using executor_type = net::io_context::executor_type;
|
||||
executor_type get_executor() const noexcept;
|
||||
|
||||
void operator()(error_code, std::size_t);
|
||||
};
|
||||
//]
|
||||
inline auto handler::get_allocator() const noexcept ->
|
||||
allocator_type
|
||||
{
|
||||
return {};
|
||||
}
|
||||
inline auto handler::get_executor() const noexcept ->
|
||||
executor_type
|
||||
{
|
||||
static net::io_context ioc;
|
||||
return ioc.get_executor();
|
||||
}
|
||||
inline void handler::operator()(error_code, std::size_t)
|
||||
{
|
||||
}
|
||||
|
||||
//[code_core_1_refresher_7
|
||||
template <class AsyncWriteStream, class WriteHandler>
|
||||
void async_hello (AsyncWriteStream& stream, WriteHandler&& handler)
|
||||
{
|
||||
net::async_write (stream,
|
||||
net::buffer("Hello, world!", 13),
|
||||
std::forward<WriteHandler>(handler));
|
||||
}
|
||||
//]
|
||||
|
||||
//[code_core_1_refresher_8
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
class ConstBufferSequence,
|
||||
class WriteHandler>
|
||||
auto
|
||||
async_write(
|
||||
AsyncWriteStream& stream,
|
||||
ConstBufferSequence const& buffers,
|
||||
WriteHandler&& handler) ->
|
||||
typename net::async_result< // return-type customization point
|
||||
typename std::decay<WriteHandler>::type, // type used to specialize async_result
|
||||
void(error_code, std::size_t) // signature of the corresponding completion handler
|
||||
>::return_type
|
||||
{
|
||||
net::async_completion<
|
||||
WriteHandler, // completion handler customization point
|
||||
void(error_code, std::size_t) // signature of the corresponding completion handler
|
||||
> init(handler); // variable which holds the corresponding completion handler
|
||||
|
||||
(void)init.completion_handler; // the underlying completion handler used for the operation
|
||||
|
||||
// ...launch the operation (omitted for clarity)
|
||||
|
||||
return init.result.get();
|
||||
}
|
||||
//]
|
||||
|
||||
} // (anon)
|
||||
|
||||
struct core_1_refresher_test
|
||||
: public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
BEAST_EXPECT(&snippets);
|
||||
|
||||
BEAST_EXPECT((static_cast<
|
||||
std::string(*)(net::const_buffer const&)>(
|
||||
&string_from_buffers<net::const_buffer>)));
|
||||
|
||||
BEAST_EXPECT(static_cast<
|
||||
std::size_t(*)(net::ip::tcp::socket&, flat_buffer&)>(
|
||||
&read_line<flat_buffer>));
|
||||
|
||||
BEAST_EXPECT(static_cast<
|
||||
std::size_t(sync_read_stream::*)(
|
||||
net::mutable_buffer const&)>(
|
||||
&sync_read_stream::read_some));
|
||||
BEAST_EXPECT(static_cast<
|
||||
std::size_t(sync_read_stream::*)(
|
||||
net::mutable_buffer const&, error_code&)>(
|
||||
&sync_read_stream::read_some));
|
||||
BEAST_EXPECT(static_cast<
|
||||
std::size_t(sync_write_stream::*)(
|
||||
net::const_buffer const&)>(
|
||||
&sync_write_stream::write_some));
|
||||
BEAST_EXPECT(static_cast<
|
||||
std::size_t(sync_write_stream::*)(
|
||||
net::const_buffer const&, error_code&)>(
|
||||
&sync_write_stream::write_some));
|
||||
|
||||
BEAST_EXPECT(static_cast<
|
||||
void(*)(test::stream&)>(
|
||||
&hello<test::stream>));
|
||||
|
||||
BEAST_EXPECT(static_cast<
|
||||
void(*)(test::stream&, error_code&)>(
|
||||
&hello<test::stream>));
|
||||
|
||||
handler h;
|
||||
h.get_allocator();
|
||||
h.get_executor();
|
||||
|
||||
BEAST_EXPECT((&async_hello<test::stream, handler>));
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(beast,doc,core_1_refresher);
|
||||
|
||||
} // beast
|
||||
} // boost
|
@@ -22,10 +22,8 @@
|
||||
namespace boost {
|
||||
namespace beast {
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
snippets()
|
||||
core_3_layers_snippets()
|
||||
{
|
||||
#include "snippets.ipp"
|
||||
{
|
||||
@@ -231,8 +229,6 @@ BOOST_STATIC_ASSERT(is_sync_write_stream<counted_stream<test::stream>>::value);
|
||||
BOOST_STATIC_ASSERT(is_async_read_stream<counted_stream<test::stream>>::value);
|
||||
BOOST_STATIC_ASSERT(is_async_write_stream<counted_stream<test::stream>>::value);
|
||||
|
||||
} // (anon)
|
||||
|
||||
struct core_3_layers_test
|
||||
: public beast::unit_test::suite
|
||||
{
|
||||
@@ -246,7 +242,7 @@ struct core_3_layers_test
|
||||
void
|
||||
run() override
|
||||
{
|
||||
BEAST_EXPECT(&snippets);
|
||||
BEAST_EXPECT(&core_3_layers_snippets);
|
||||
BEAST_EXPECT(&set_non_blocking<net::ip::tcp::socket>);
|
||||
|
||||
BEAST_EXPECT(&counted_stream<test::stream>::get_executor);
|
||||
|
Reference in New Issue
Block a user