forked from boostorg/beast
Documentation work
This commit is contained in:
@@ -49,6 +49,8 @@
|
|||||||
|
|
||||||
[def __AsyncStream__ [link beast.ref.streams.AsyncStream [*AsyncStream]]]
|
[def __AsyncStream__ [link beast.ref.streams.AsyncStream [*AsyncStream]]]
|
||||||
[def __Body__ [link beast.ref.Body [*Body]]]
|
[def __Body__ [link beast.ref.Body [*Body]]]
|
||||||
|
[def __BodyReader__ [link beast.ref.BodyReader [*BodyReader]]]
|
||||||
|
[def __BodyWriter__ [link beast.ref.BodyWriter [*BodyWriter]]]
|
||||||
[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]]
|
[def __DynamicBuffer__ [link beast.ref.DynamicBuffer [*DynamicBuffer]]]
|
||||||
[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]]
|
[def __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]]
|
||||||
[def __Stream__ [link beast.ref.streams [*Stream]]]
|
[def __Stream__ [link beast.ref.streams [*Stream]]]
|
||||||
@@ -100,8 +102,7 @@ asynchronous model of __Asio__.
|
|||||||
[[
|
[[
|
||||||
[link beast.design Design]
|
[link beast.design Design]
|
||||||
][
|
][
|
||||||
Design rationale, answers to questions,
|
Rationale, comparison to other libraries, and FAQ.
|
||||||
library comparisons, and Boost Formal Review FAQ.
|
|
||||||
]]
|
]]
|
||||||
[[
|
[[
|
||||||
[link beast.ref Reference]
|
[link beast.ref Reference]
|
||||||
@@ -116,9 +117,9 @@ asynchronous model of __Asio__.
|
|||||||
]
|
]
|
||||||
|
|
||||||
[include 1_overview.qbk]
|
[include 1_overview.qbk]
|
||||||
[include 2_core.qbk]
|
[include 2_0_core.qbk]
|
||||||
[include 3_0_http.qbk]
|
[include 3_0_http.qbk]
|
||||||
[include 4_websocket.qbk]
|
[include 4_0_websocket.qbk]
|
||||||
[include 5_examples.qbk]
|
[include 5_examples.qbk]
|
||||||
[include 6_0_design.qbk]
|
[include 6_0_design.qbk]
|
||||||
|
|
||||||
|
@@ -122,7 +122,7 @@ and
|
|||||||
[@https://www.ripple.com Ripple Labs]
|
[@https://www.ripple.com Ripple Labs]
|
||||||
for supporting its early development. Also thanks to
|
for supporting its early development. Also thanks to
|
||||||
Agustín Bergé,
|
Agustín Bergé,
|
||||||
Glen Fernandes,
|
[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes],
|
||||||
and
|
and
|
||||||
Peter Dimov
|
Peter Dimov
|
||||||
for helping me considerably on Slack.
|
for helping me considerably on Slack.
|
||||||
|
53
doc/2_0_core.qbk
Normal file
53
doc/2_0_core.qbk
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:core Core Concepts]
|
||||||
|
|
||||||
|
[block '''
|
||||||
|
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
||||||
|
<member><link linkend="beast.core.asio">Working With Asio</link></member>
|
||||||
|
<member><link linkend="beast.core.streams">Stream Concepts</link></member>
|
||||||
|
<member><link linkend="beast.core.buffers">Buffer Concepts</link></member>
|
||||||
|
<member><link linkend="beast.core.async">Asynchronous Utilities</link></member>
|
||||||
|
<member><link linkend="beast.core.tutorial">Writing Composed Operations</link></member>
|
||||||
|
</simplelist></entry></row></tbody></tgroup></informaltable>
|
||||||
|
''']
|
||||||
|
|
||||||
|
A goal of the library is expose implementation primitives in order that
|
||||||
|
users may build their own library-like components. These primitives include
|
||||||
|
traits, buffers, buffer algorithms, and helpers for implementing asynchronous
|
||||||
|
operations compatible with __Asio__ and described in __N3747__. This section
|
||||||
|
lists these facilities by group, with descriptions.
|
||||||
|
|
||||||
|
[important
|
||||||
|
This documentation assumes familiarity with __Asio__. Sample
|
||||||
|
code and identifiers used throughout are written as if the
|
||||||
|
following declarations are in effect:
|
||||||
|
```
|
||||||
|
#include <beast/core.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace beast;
|
||||||
|
|
||||||
|
boost::asio::io_service ios;
|
||||||
|
boost::asio::io_service work{ios};
|
||||||
|
std::thread t{[&](){ ios.run(); }};
|
||||||
|
|
||||||
|
error_code ec;
|
||||||
|
|
||||||
|
```
|
||||||
|
]
|
||||||
|
|
||||||
|
[include 2_1_asio.qbk]
|
||||||
|
[include 2_2_streams.qbk]
|
||||||
|
[include 2_3_buffers.qbk]
|
||||||
|
[include 2_4_async.qbk]
|
||||||
|
[include 2_5_tutorial.qbk]
|
||||||
|
|
||||||
|
[endsect]
|
61
doc/2_1_asio.qbk
Normal file
61
doc/2_1_asio.qbk
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading:asio Working With Asio]
|
||||||
|
|
||||||
|
Beast does not manage sockets, make outgoing connections, accept incoming
|
||||||
|
connections, or handle any aspect of connection management. In order to
|
||||||
|
invoke library algorithms it is necessary to first have a connected socket,
|
||||||
|
SSL stream, or other object which meets the required stream concepts. This
|
||||||
|
example is provided as a reminder of how to work with sockets:
|
||||||
|
```
|
||||||
|
auto host = "www.example.com";
|
||||||
|
boost::asio::ip::tcp::resolver r{ios};
|
||||||
|
boost::asio::ip::tcp::socket sock{ios};
|
||||||
|
boost::asio::connect(sock, r.resolve(
|
||||||
|
boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||||
|
|
||||||
|
// At this point `sock` is a connected to a remote
|
||||||
|
// host and may be used to perform stream operations.
|
||||||
|
```
|
||||||
|
|
||||||
|
Throughout this documentation identifiers with the following names have
|
||||||
|
special meaning:
|
||||||
|
|
||||||
|
[table Global Variables
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/io_service.html [*`ios`]]
|
||||||
|
][
|
||||||
|
A variable of type
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`]
|
||||||
|
which is running on one separate thread, and upon which a
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/io_service__work.html `boost::asio::io_service::work`]
|
||||||
|
object has been constructed.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html [*`sock`]]
|
||||||
|
][
|
||||||
|
A variable of type
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]
|
||||||
|
which has already been connected to a remote host.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html [*`ssl_sock`]]
|
||||||
|
][
|
||||||
|
A variable of type
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream<boost::asio::ip::tcp::socket>`]
|
||||||
|
which is already connected and has handshaked with a remote host.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.websocket__stream [*`ws`]]
|
||||||
|
][
|
||||||
|
A variable of type
|
||||||
|
[link beast.ref.websocket__stream `websocket::stream<boost::asio::ip::tcp::socket>`]
|
||||||
|
which is already connected with a remote host.
|
||||||
|
]]
|
||||||
|
]
|
129
doc/2_2_streams.qbk
Normal file
129
doc/2_2_streams.qbk
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading:streams Stream Concepts]
|
||||||
|
|
||||||
|
A __Stream__ is communication channel where data expressed as octet
|
||||||
|
buffers is transferred sequentially. Streams are either synchronous
|
||||||
|
or asynchronous, and may allow reading, writing, or both. Note that
|
||||||
|
a particular type may model more than one concept. For example, the
|
||||||
|
Asio types
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ip__tcp/socket.html `boost::asio::ip::tcp::socket`]
|
||||||
|
and
|
||||||
|
[@http://www.boost.org/doc/html/boost_asio/reference/ssl__stream.html `boost::asio::ssl::stream`]
|
||||||
|
support everything. All stream algorithms in Beast are declared as
|
||||||
|
template functions with specific concept requirements chosen from
|
||||||
|
this list:
|
||||||
|
|
||||||
|
[table Stream Concepts
|
||||||
|
[[Concept][Description]]
|
||||||
|
[
|
||||||
|
[__SyncReadStream__]
|
||||||
|
[
|
||||||
|
Supports buffer-oriented blocking reads.
|
||||||
|
]
|
||||||
|
][
|
||||||
|
[__SyncWriteStream__]
|
||||||
|
[
|
||||||
|
Supports buffer-oriented blocking writes.
|
||||||
|
]
|
||||||
|
][
|
||||||
|
[__SyncStream__]
|
||||||
|
[
|
||||||
|
A stream supporting buffer-oriented blocking reads and writes.
|
||||||
|
]
|
||||||
|
][
|
||||||
|
[__AsyncReadStream__]
|
||||||
|
[
|
||||||
|
Supports buffer-oriented asynchronous reads.
|
||||||
|
]
|
||||||
|
][
|
||||||
|
[__AsyncWriteStream__]
|
||||||
|
[
|
||||||
|
Supports buffer-oriented asynchronous writes.
|
||||||
|
]
|
||||||
|
][
|
||||||
|
[__AsyncStream__]
|
||||||
|
[
|
||||||
|
A stream supporting buffer-oriented asynchronous reads and writes.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
These template metafunctions check whether a given type meets the
|
||||||
|
requirements for the various stream concepts, and some additional
|
||||||
|
useful utilities. The library uses these type checks internally
|
||||||
|
and also provides them as public interfaces so users may use the
|
||||||
|
same techniques to augment their own code. The use of these type
|
||||||
|
checks helps provide more concise errors during compilation:
|
||||||
|
|
||||||
|
[table Stream Type Checks
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.get_lowest_layer `get_lowest_layer`]
|
||||||
|
][
|
||||||
|
Returns `T::lowest_layer_type` if it exists, else returns `T`.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.has_get_io_service `has_get_io_service`]
|
||||||
|
][
|
||||||
|
Determine if the `get_io_service` member function is present,
|
||||||
|
and returns an __io_service__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_async_read_stream `is_async_read_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __AsyncReadStream__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_async_stream `is_async_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of both __AsyncReadStream__
|
||||||
|
and __AsyncWriteStream__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_async_write_stream `is_async_write_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __AsyncWriteStream__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_completion_handler `is_completion_handler`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __CompletionHandler__,
|
||||||
|
and is callable with a specified signature.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_sync_read_stream `is_sync_read_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __SyncReadStream__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_sync_stream `is_sync_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of both __SyncReadStream__
|
||||||
|
and __SyncWriteStream__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_sync_write_stream `is_sync_write_stream`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __SyncWriteStream__.
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
|
Using the type checks with `static_assert` on function or class template
|
||||||
|
types will provide users with helpful error messages and prevent undefined
|
||||||
|
behaviors. This example shows how a template function which writes to a
|
||||||
|
synchronous stream may check its argument:
|
||||||
|
```
|
||||||
|
template<class SyncWriteStream>
|
||||||
|
void write_string(SyncWriteStream& stream, string_view s)
|
||||||
|
{
|
||||||
|
static_assert(is_sync_write_stream<SyncWriteStream>::value,
|
||||||
|
"SyncWriteStream requirements not met");
|
||||||
|
boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size()));
|
||||||
|
}
|
||||||
|
```
|
147
doc/2_3_buffers.qbk
Normal file
147
doc/2_3_buffers.qbk
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading:buffers Buffer Concepts]
|
||||||
|
|
||||||
|
__Asio__ provides the __ConstBufferSequence__ and __MutableBufferSequence__
|
||||||
|
concepts, whose models provide ranges of buffers, as well as the __streambuf__
|
||||||
|
class which encapsulates memory storage that may be automatically resized as
|
||||||
|
required, where the memory is divided into an input sequence followed by an
|
||||||
|
output sequence. The Networking TS (__N4588__) generalizes the `streambuf`
|
||||||
|
interface into the __DynamicBuffer__ concept. Beast algorithms which require
|
||||||
|
resizable buffers accept as parameters dynamic buffer objects. These
|
||||||
|
metafunctions check types against these buffer concepts:
|
||||||
|
|
||||||
|
[table Buffer Type Checks
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_const_buffer_sequence `is_const_buffer_sequence`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __ConstBufferSequence__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_mutable_buffer_sequence `is_mutable_buffer_sequence`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __MutableBufferSequence__.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.is_dynamic_buffer `is_dynamic_buffer`]
|
||||||
|
][
|
||||||
|
Determine if a type meets the requirements of __DynamicBuffer__.
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
|
To suit various needs, several implementation of dynamic buffer are available:
|
||||||
|
|
||||||
|
[table Dynamic Buffer Implementations
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffers_adapter `buffers_adapter`]
|
||||||
|
][
|
||||||
|
This wrapper adapts any __MutableBufferSequence__ into a
|
||||||
|
__DynamicBuffer__ with an upper limit on the total size of the input and
|
||||||
|
output areas equal to the size of the underlying mutable buffer sequence.
|
||||||
|
The implementation does not perform heap allocations.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.flat_buffer `flat_buffer`]
|
||||||
|
[link beast.ref.basic_flat_buffer `basic_flat_buffer`]
|
||||||
|
][
|
||||||
|
Guarantees that input and output areas are buffer sequences with
|
||||||
|
length one. Upon construction an optional upper limit to the total
|
||||||
|
size of the input and output areas may be set. The basic container
|
||||||
|
supports the standard allocator model.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.multi_buffer `multi_buffer`]
|
||||||
|
[link beast.ref.basic_multi_buffer `basic_multi_buffer`]
|
||||||
|
][
|
||||||
|
Uses a sequence of one or more character arrays of varying sizes.
|
||||||
|
Additional character array objects are appended to the sequence to
|
||||||
|
accommodate changes in the size of the character sequence. The basic
|
||||||
|
container supports the standard allocator model.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.static_buffer `static_buffer`]
|
||||||
|
[link beast.ref.static_buffer `static_buffer_n`]
|
||||||
|
][
|
||||||
|
Provides the facilities of a dynamic buffer, subject to an upper
|
||||||
|
limit placed on the total size of the input and output areas defined
|
||||||
|
by a constexpr template parameter. The storage for the sequences are
|
||||||
|
kept in the class; the implementation does not perform heap allocations.
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
|
Network applications frequently need to manipulate buffer sequences. To
|
||||||
|
facilitate working with buffers the library treats these sequences as
|
||||||
|
a special type of range. Algorithms and wrappers are provided which
|
||||||
|
transform these buffer sequences ranges efficiently using lazy evaluation.
|
||||||
|
No memory allocations are used in the transformations; instead, they
|
||||||
|
create lightweight iterators over the existing, unmodified memory
|
||||||
|
buffers. The lifetime of the transformed buffers is retained by the
|
||||||
|
caller; ownership is not transferred.
|
||||||
|
|
||||||
|
[table Buffer Algorithms
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffer_cat `buffer_cat`]
|
||||||
|
][
|
||||||
|
This functions returns a new buffer sequence which, when iterated,
|
||||||
|
traverses the sequence which would be formed if all of the input buffer
|
||||||
|
sequences were concatenated. With this routine, multiple calls to a
|
||||||
|
stream's `write_some` function may be combined into one, eliminating
|
||||||
|
expensive system calls.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffer_cat_view `buffer_cat_view`]
|
||||||
|
][
|
||||||
|
This class represents the buffer sequence formed by concatenating
|
||||||
|
two or more buffer sequences. This is type of object returned by
|
||||||
|
[link beast.ref.buffer_cat `buffer_cat`].
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffer_prefix `buffer_prefix`]
|
||||||
|
][
|
||||||
|
This function returns a new buffer or buffer sequence which represents
|
||||||
|
a prefix of the original buffers.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffer_prefix_view `buffer_prefix_view`]
|
||||||
|
][
|
||||||
|
This class represents the buffer sequence formed from a prefix of
|
||||||
|
an existing buffer sequence. This is the type of buffer returned by
|
||||||
|
[link beast.ref.buffer_prefix `buffer_prefix`].
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.consuming_buffers `consuming_buffers`]
|
||||||
|
][
|
||||||
|
This class wraps the underlying memory of an existing buffer sequence
|
||||||
|
and presents a suffix of the original sequence. The length of the suffix
|
||||||
|
may be progressively shortened. This lets callers work with sequential
|
||||||
|
increments of a buffer sequence.
|
||||||
|
]]
|
||||||
|
]
|
||||||
|
|
||||||
|
These two functions facilitate buffer interoperability with standard
|
||||||
|
output streams.
|
||||||
|
|
||||||
|
[table Buffer Output Streams
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.buffers `buffers`]
|
||||||
|
][
|
||||||
|
This function wraps a __ConstBufferSequence__ so it may be
|
||||||
|
used with `operator<<` and `std::ostream`.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.ostream `ostream`]
|
||||||
|
][
|
||||||
|
This function returns a `std::ostream` which wraps a dynamic buffer.
|
||||||
|
Characters sent to the stream using `operator<<` is stored in the
|
||||||
|
dynamic buffer.
|
||||||
|
]]
|
||||||
|
]
|
100
doc/2_4_async.qbk
Normal file
100
doc/2_4_async.qbk
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading:async Asynchronous Utilities]
|
||||||
|
|
||||||
|
Asynchronous operations are started by calling a free function or member
|
||||||
|
function known as an ['asynchronous initiation function]. The initiation
|
||||||
|
function accepts parameters specific to the operation as well as a "completion
|
||||||
|
token." This token is either a completion handler, or another type allowing for
|
||||||
|
customization of how the result of the asynchronous operation is conveyed to
|
||||||
|
callers. __Asio__ allows the special completion tokens __use_future__ and
|
||||||
|
objects of type __yield_context__ to allow callers to specify the use of futures
|
||||||
|
and coroutines respectively. This system, where the return value and method of
|
||||||
|
indicating completion may be customize at the call site of the asynchronous
|
||||||
|
initiation function, is known as the ['Extensible Asynchronous Model] described
|
||||||
|
in __N3747__, and built-in to __N4588__.
|
||||||
|
|
||||||
|
[note
|
||||||
|
A full explanation of completion handlers, the Extensible Asynchronous
|
||||||
|
Model and how these asynchronous interfaces are used is beyond the
|
||||||
|
scope of this document. Interested readers should consult the
|
||||||
|
__Asio__ documentation.
|
||||||
|
]
|
||||||
|
|
||||||
|
Since the interfaces provided here are low level, authors of libraries
|
||||||
|
may wish to create higher level interfaces using the primitives found
|
||||||
|
in this library. Non-trivial applications will want to provide their own
|
||||||
|
asychronous initiation functions which perform a series of other,
|
||||||
|
intermediate asynchronous operations before invoking the final completion
|
||||||
|
handler. The set of intermediate actions produced by calling an initiation
|
||||||
|
function is known as a ['composed operation]. To ensure full interoperability
|
||||||
|
and well-defined behavior, __Asio__ imposes requirements on the implementation
|
||||||
|
of composed operations. A number of useful classes and macros to facilitate
|
||||||
|
the development of composed operations and the associated asynchronous
|
||||||
|
initiation functions used to launch them are available:
|
||||||
|
|
||||||
|
[table Asynchronous Helpers
|
||||||
|
[[Name][Description]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.async_completion `async_completion`]
|
||||||
|
][
|
||||||
|
This class aggregates the completion handler customization point and
|
||||||
|
the asynchronous initiation function return value customization point
|
||||||
|
into a single object which exposes the appropriate output types for the
|
||||||
|
given input types, and also contains boilerplate that is necessary to
|
||||||
|
implement an initiation function using the Extensible Model.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.async_return_type `async_return_type`]
|
||||||
|
][
|
||||||
|
This template alias determines the return value of an asynchronous
|
||||||
|
initiation function given the completion token and signature. It is used
|
||||||
|
by asynchronous initiation functions to meet the requirements of the
|
||||||
|
Extensible Asynchronous Model.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.bind_handler `bind_handler`]
|
||||||
|
][
|
||||||
|
This function returns a new, nullary completion handler which when
|
||||||
|
invoked with no arguments invokes the original completion handler with a
|
||||||
|
list of bound arguments. The invocation is made from the same implicit
|
||||||
|
or explicit strand as that which would be used to invoke the original
|
||||||
|
handler. This is accomplished by using the correct overload of
|
||||||
|
`asio_handler_invoke` associated with the original completion handler.
|
||||||
|
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.handler_alloc `handler_alloc`]
|
||||||
|
][
|
||||||
|
This class meets the requirements of [*Allocator], and uses any custom
|
||||||
|
memory allocation and deallocation hooks associated with a given handler.
|
||||||
|
It is useful for when a composed operation requires temporary dynamic
|
||||||
|
allocations to achieve its result. Memory allocated using this allocator
|
||||||
|
must be freed before the final completion handler is invoked.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.handler_ptr `handler_ptr`]
|
||||||
|
][
|
||||||
|
This is a smart pointer container used to manage the internal state of a
|
||||||
|
composed operation. It is useful when the state is non trivial. For example
|
||||||
|
when the state has non-copyable or expensive to copy types. The container
|
||||||
|
takes ownership of the final completion handler, and provides boilerplate
|
||||||
|
to invoke the final handler in a way that also deletes the internal state.
|
||||||
|
The internal state is allocated using the final completion handler's
|
||||||
|
associated allocator, benefiting from all handler memory management
|
||||||
|
optimizations transparently.
|
||||||
|
]]
|
||||||
|
[[
|
||||||
|
[link beast.ref.handler_type `handler_type`]
|
||||||
|
][
|
||||||
|
This template alias converts a completion token and signature to the
|
||||||
|
correct completion handler type. It is used in the implementation of
|
||||||
|
asynchronous initiation functions to meet the requirements of the
|
||||||
|
Extensible Asynchronous Model.
|
||||||
|
]]
|
||||||
|
]
|
268
doc/2_5_tutorial.qbk
Normal file
268
doc/2_5_tutorial.qbk
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:tutorial Writing Composed Operations]
|
||||||
|
|
||||||
|
To illustrate the usage of the asynchronous helpers in the core section of
|
||||||
|
this library, we will develop a simple asynchronous composed operation called
|
||||||
|
[*echo]. This operation will read up to the first newline on a stream, and
|
||||||
|
then write the same line including the newline back on the stream.
|
||||||
|
|
||||||
|
First we define the input parameters and results, then declare our initiation
|
||||||
|
function. For our echo operation the only inputs are the stream and the
|
||||||
|
completion token. The output is the error code which is usually included in
|
||||||
|
all completion handler signatures.
|
||||||
|
|
||||||
|
```
|
||||||
|
#include <beast/core.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
// Read a line and echo it back
|
||||||
|
//
|
||||||
|
template<class AsyncStream, class CompletionToken>
|
||||||
|
beast::async_return_type<CompletionToken, void(beast::error_code)>
|
||||||
|
async_echo(AsyncStream& stream, CompletionToken&& token)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now that we have a declaration, we will define the body of the function. We
|
||||||
|
want to achieve the following goals: perform static type checking on the input
|
||||||
|
parameters, set up the return value as per __N3747__, and launch the composed
|
||||||
|
operation by constructing the object and invoking it.
|
||||||
|
|
||||||
|
```
|
||||||
|
template<class AsyncStream, class Handler>
|
||||||
|
class echo_op; // This is our composed operation implementation
|
||||||
|
|
||||||
|
// Read a line and echo it back
|
||||||
|
//
|
||||||
|
template<class AsyncStream, class CompletionToken>
|
||||||
|
beast::async_return_type<CompletionToken, void(beast::error_code)>
|
||||||
|
async_echo(AsyncStream& stream, CompletionToken&& token)
|
||||||
|
{
|
||||||
|
// Make sure stream meets the requirements. We use static_assert
|
||||||
|
// to cause a friendly message instead of an error novel.
|
||||||
|
//
|
||||||
|
static_assert(beast::is_async_stream<AsyncStream>::value,
|
||||||
|
"AsyncStream requirements not met");
|
||||||
|
|
||||||
|
// This helper manages some of the handler's lifetime and
|
||||||
|
// uses the result and handler specializations associated with
|
||||||
|
// the completion token to help customize the return value.
|
||||||
|
//
|
||||||
|
beast::async_completion<CompletionToken, void(beast::error_code)> init{token};
|
||||||
|
|
||||||
|
// Create the composed operation and launch it. This is a constructor
|
||||||
|
// call followed by invocation of operator(). We use handler_type
|
||||||
|
// to convert the completion token into the correct handler type,
|
||||||
|
// allowing user defined specializations of the async result template
|
||||||
|
// to take effect.
|
||||||
|
//
|
||||||
|
echo_op<AsyncStream, beast::handler_type<CompletionToken, void(beast::error_code)>>{
|
||||||
|
stream, init.completion_handler}(beast::error_code{}, 0);
|
||||||
|
|
||||||
|
// This hook lets the caller see a return value when appropriate.
|
||||||
|
// For example this might return std::future<error_code> if
|
||||||
|
// CompletionToken is boost::asio::use_future, or this might
|
||||||
|
// return an error code if CompletionToken specifies a coroutine.
|
||||||
|
//
|
||||||
|
return init.result.get();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The initiation function contains a few relatively simple parts. There is the
|
||||||
|
customization of the return value type, static type checking, building the
|
||||||
|
return value type using the helper, and creating and launching the composed
|
||||||
|
operation object. The [*`echo_op`] object does most of the work here, and has
|
||||||
|
a somewhat non-trivial structure. This structure is necessary to meet the
|
||||||
|
stringent requirements of composed operations (described in more detail in
|
||||||
|
the __Asio__ documentation). We will touch on these requirements without
|
||||||
|
explaining them in depth.
|
||||||
|
|
||||||
|
First we will create boilerplate which is present in all composed operations
|
||||||
|
written in this style:
|
||||||
|
|
||||||
|
```
|
||||||
|
// This composed operation reads a line of input and echoes it back.
|
||||||
|
//
|
||||||
|
template<class AsyncStream, class Handler>
|
||||||
|
class echo_op
|
||||||
|
{
|
||||||
|
// This holds all of the state information required by the operation.
|
||||||
|
struct state
|
||||||
|
{
|
||||||
|
// The stream to read and write to
|
||||||
|
AsyncStream& stream;
|
||||||
|
|
||||||
|
// Indicates what step in the operation's state machine
|
||||||
|
// to perform next, starting from zero.
|
||||||
|
int step = 0;
|
||||||
|
|
||||||
|
// The buffer used to hold the input and output data.
|
||||||
|
// Note that we use a custom allocator for performance,
|
||||||
|
// this allows the implementation of the io_service to
|
||||||
|
// make efficient re-use of memory allocated by composed
|
||||||
|
// operations during continuations.
|
||||||
|
//
|
||||||
|
boost::asio::basic_streambuf<beast::handler_alloc<char, Handler>> buffer;
|
||||||
|
|
||||||
|
// handler_ptr requires that the first parameter to the
|
||||||
|
// contained object constructor is a reference to the
|
||||||
|
// managed final completion handler.
|
||||||
|
//
|
||||||
|
explicit state(Handler& handler, AsyncStream& stream_)
|
||||||
|
: stream(stream_)
|
||||||
|
, buffer((std::numeric_limits<std::size_t>::max)(),
|
||||||
|
beast::handler_alloc<char, Handler>{handler})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This smart pointer container allocates our state using the
|
||||||
|
// memory allocation hooks associated with the final completion
|
||||||
|
// handler, manages the lifetime of that handler for us, and
|
||||||
|
// enforces the destroy-before-invocation requirement on memory
|
||||||
|
// allocated using the hooks.
|
||||||
|
//
|
||||||
|
beast::handler_ptr<state, Handler> p_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Boost.Asio requires that handlers are CopyConstructible.
|
||||||
|
// In some cases, it takes advantage of handlers that are
|
||||||
|
// MoveConstructible. This operation supports both.
|
||||||
|
//
|
||||||
|
echo_op(echo_op&&) = default;
|
||||||
|
echo_op(echo_op const&) = default;
|
||||||
|
|
||||||
|
// The constructor simply creates our state variables in
|
||||||
|
// the smart pointer container.
|
||||||
|
//
|
||||||
|
template<class DeducedHandler, class... Args>
|
||||||
|
echo_op(AsyncStream& stream, DeducedHandler&& handler)
|
||||||
|
: p_(std::forward<DeducedHandler>(handler), stream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the next asynchronous operation represents a
|
||||||
|
// continuation of the asynchronous flow of control associated
|
||||||
|
// with the final handler. If we are past step one, it means
|
||||||
|
// we have performed an asynchronous operation therefore any
|
||||||
|
// subsequent operation would represent a continuation.
|
||||||
|
// Otherwise, we propagate the handler's associated value of
|
||||||
|
// is_continuation. Getting this right means the implementation
|
||||||
|
// may schedule the invokation of the invoked functions more
|
||||||
|
// efficiently.
|
||||||
|
//
|
||||||
|
friend bool asio_handler_is_continuation(echo_op* op)
|
||||||
|
{
|
||||||
|
// This next call is structured to permit argument
|
||||||
|
// dependent lookup to take effect.
|
||||||
|
using boost::asio::asio_handler_is_continuation;
|
||||||
|
|
||||||
|
// Always use std::addressof to pass the pointer to the handler,
|
||||||
|
// otherwise an unwanted overload of operator& may be called instead.
|
||||||
|
return op->p_->step > 1 ||
|
||||||
|
asio_handler_is_continuation(std::addressof(op->p_.handler()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler hook forwarding. These free functions invoke the hooks
|
||||||
|
// associated with the final completion handler. In effect, they
|
||||||
|
// make the Asio implementation treat our composed operation the
|
||||||
|
// same way it would treat the final completion handler for the
|
||||||
|
// purpose of memory allocation and invocation.
|
||||||
|
//
|
||||||
|
// Our implementation just passes through the call to the hook
|
||||||
|
// associated with the final handler.
|
||||||
|
|
||||||
|
friend void* asio_handler_allocate(std::size_t size, echo_op* op)
|
||||||
|
{
|
||||||
|
using boost::asio::asio_handler_allocate;
|
||||||
|
return asio_handler_allocate(size, std::addressof(op->p_.handler()));
|
||||||
|
}
|
||||||
|
|
||||||
|
friend void asio_handler_deallocate(void* p, std::size_t size, echo_op* op)
|
||||||
|
{
|
||||||
|
using boost::asio::asio_handler_deallocate;
|
||||||
|
return asio_handler_deallocate(p, size, std::addressof(op->p_.handler()));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Function>
|
||||||
|
friend void asio_handler_invoke(Function&& f, echo_op* op)
|
||||||
|
{
|
||||||
|
using boost::asio::asio_handler_invoke;
|
||||||
|
return asio_handler_invoke(f, std::addressof(op->p_.handler()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our main entry point. This will get called as our
|
||||||
|
// intermediate operations complete. Definition below.
|
||||||
|
//
|
||||||
|
void operator()(beast::error_code ec, std::size_t bytes_transferred);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
We have the common boilerplate for a composed operation and now we just need
|
||||||
|
to implement the function call operator. Our strategy is to make our composed
|
||||||
|
object meet the requirements of a completion handler by being copyable (also
|
||||||
|
movable), and by providing the function call operator with the correct
|
||||||
|
signature. Rather than using `std::bind` or `boost::bind`, which destroys
|
||||||
|
the type information and therefore breaks the allocation and invocation
|
||||||
|
hooks, we will simply pass `std::move(*this)` as the completion handler
|
||||||
|
parameter for any operations that we initiate. For the move to work correctly,
|
||||||
|
care must be taken to ensure that no access to data members are made after the
|
||||||
|
move takes place. Here is the implementation of the function call operator for
|
||||||
|
this echo operation:
|
||||||
|
```
|
||||||
|
// We are callable with the signature void(error_code, bytes_transferred),
|
||||||
|
// allowing `*this` to be used as both a ReadHandler and a WriteHandler.
|
||||||
|
//
|
||||||
|
template<class AsyncStream, class Handler>
|
||||||
|
void echo_op<AsyncStream, Handler>::
|
||||||
|
operator()(beast::error_code ec, std::size_t bytes_transferred)
|
||||||
|
{
|
||||||
|
// Store a reference to our state. The address of the state won't
|
||||||
|
// change, and this solves the problem where dereferencing the
|
||||||
|
// data member is undefined after a move.
|
||||||
|
auto& p = *p_;
|
||||||
|
|
||||||
|
// Now perform the next step in the state machine
|
||||||
|
switch(ec ? 2 : p.step)
|
||||||
|
{
|
||||||
|
// initial entry
|
||||||
|
case 0:
|
||||||
|
// read up to the first newline
|
||||||
|
p.step = 1;
|
||||||
|
return boost::asio::async_read_until(p.stream, p.buffer, "\n", std::move(*this));
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// write everything back
|
||||||
|
p.step = 2;
|
||||||
|
// async_read_until could have read past the newline,
|
||||||
|
// use buffer_prefix to make sure we only send one line
|
||||||
|
return boost::asio::async_write(p.stream,
|
||||||
|
beast::buffer_prefix(bytes_transferred, p.buffer.data()), std::move(*this));
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
p.buffer.consume(bytes_transferred);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the final handler. If we wanted to pass any arguments
|
||||||
|
// which come from our state, they would have to be moved to the
|
||||||
|
// stack first, since the `handler_ptr` guarantees that the state
|
||||||
|
// is destroyed before the handler is invoked.
|
||||||
|
//
|
||||||
|
p_.invoke(ec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A complete, runnable version of this example may be found in the examples
|
||||||
|
directory.
|
||||||
|
|
||||||
|
[endsect]
|
@@ -109,11 +109,11 @@ A set of free functions allow serialization of an entire HTTP message to
|
|||||||
a stream. This function sends a message synchronously on the socket,
|
a stream. This function sends a message synchronously on the socket,
|
||||||
throwing an exception if an error occurs:
|
throwing an exception if an error occurs:
|
||||||
```
|
```
|
||||||
template<class Body, class Fields>
|
template<class Body, class Fields>
|
||||||
void send(response<Body, Fields> const& res)
|
void send(response<Body, Fields> const& res)
|
||||||
{
|
{
|
||||||
write(sock, res);
|
write(sock, res);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If a response has no declared content length, and no chunked transfer
|
If a response has no declared content length, and no chunked transfer
|
||||||
@@ -124,8 +124,8 @@ to the caller that the connection should be closed. This example
|
|||||||
constructs and sends a response whose body length is determined by
|
constructs and sends a response whose body length is determined by
|
||||||
the number of octets received prior to the server closing the connection:
|
the number of octets received prior to the server closing the connection:
|
||||||
```
|
```
|
||||||
void send()
|
void send()
|
||||||
{
|
{
|
||||||
response<string_body> res;
|
response<string_body> res;
|
||||||
res.version = 11;
|
res.version = 11;
|
||||||
res.status = 200;
|
res.status = 200;
|
||||||
@@ -139,22 +139,22 @@ void send()
|
|||||||
sock.close();
|
sock.close();
|
||||||
else
|
else
|
||||||
BOOST_ASSERT(ec);
|
BOOST_ASSERT(ec);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
An asynchronous version is also available:
|
An asynchronous version is also available:
|
||||||
|
|
||||||
```
|
```
|
||||||
template<class Body, class Fields>
|
template<class Body, class Fields>
|
||||||
void send_async(response<Body, Fields> const& res)
|
void send_async(response<Body, Fields> const& res)
|
||||||
{
|
{
|
||||||
async_write(sock, res,
|
async_write(sock, res,
|
||||||
[&](error_code)
|
[&](error_code)
|
||||||
{
|
{
|
||||||
if(ec)
|
if(ec)
|
||||||
std::cerr << ec.message() << std::endl;
|
std::cerr << ec.message() << std::endl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
[section:custom_body Custom Body Types]
|
[section:custom_body Custom Body Types]
|
||||||
|
|
||||||
User-defined types are possible for the message body, where the type meets the
|
User-defined types are possible for the message body, where the type meets the
|
||||||
[link beast.ref.Body [*`Body`]] requirements. This simplified class declaration
|
__Body__ requirements. This simplified class declaration
|
||||||
shows the customization points available to user-defined body types:
|
shows the customization points available to user-defined body types:
|
||||||
|
|
||||||
[$images/body.png [width 525px] [height 190px]]
|
[$images/body.png [width 525px] [height 190px]]
|
||||||
@@ -21,29 +21,64 @@ The meaning of the nested types is as follows
|
|||||||
[`value_type`]
|
[`value_type`]
|
||||||
[
|
[
|
||||||
Determines the type of the
|
Determines the type of the
|
||||||
[link beast.ref.http__message.body `message::body`] member. If this
|
[link beast.ref.http__message.body `message::body`]
|
||||||
type defines default construction, move, copy, or swap, then message objects
|
member. If this type defines default construction, move, copy,
|
||||||
declared with this [*Body] will have those operations defined.
|
or swap, then message objects declared with this __Body__ will
|
||||||
|
have those operations defined.
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[`reader`]
|
[`reader`]
|
||||||
[
|
[
|
||||||
An optional nested type meeting the requirements of
|
An optional nested type meeting the requirements of __BodyReader__.
|
||||||
[link beast.ref.BodyReader [*BodyReader]]. If present, this defines
|
|
||||||
the algorithm used to obtain buffers representing a body of this type.
|
|
||||||
]
|
]
|
||||||
][
|
][
|
||||||
[`writer`]
|
[`writer`]
|
||||||
[
|
[
|
||||||
An optional nested type meeting the requirements of
|
An optional nested type meeting the requirements of __BodyWriter__.
|
||||||
[link beast.ref.BodyWriter [*BodyWriter]]. If present, this defines the
|
|
||||||
algorithm used to transfer parsed octets into buffers representing the
|
|
||||||
body.
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
The examples included with this library provide a [*Body] implementation that
|
[heading Value Type]
|
||||||
serializing message bodies that come from a file.
|
|
||||||
|
The `value_type` nested type allows the body to define the declaration of
|
||||||
|
the body type as it appears in the message. This can be any type. For
|
||||||
|
example, a body's value type may specify `std::vector<char>` or even
|
||||||
|
`std::list<std::string>`. By also providing suitable definitions of
|
||||||
|
corresponding `reader` and `writer` types, messages with that body
|
||||||
|
become serializable and parseable respectively.
|
||||||
|
|
||||||
|
A custom body may even set the value type to something that is not a container
|
||||||
|
for body octets, such as a
|
||||||
|
[@http://www.boost.org/libs/filesystem/doc/reference.html#class-path `boost::filesystem::path`].
|
||||||
|
In this case the reader may obtain buffers corresponding to a file on disk,
|
||||||
|
while the writer may store incoming buffers to a file on disk.
|
||||||
|
|
||||||
|
Another option is to use a structured container for the value type. For
|
||||||
|
example, a JSON tree structure such as the property tree produced by Boost's
|
||||||
|
[@http://www.boost.org/doc/html/property_tree/parsers.html#property_tree.parsers.json_parser `json_parser`]
|
||||||
|
As long as a suitable reader or writer is available to provide the algorithm
|
||||||
|
for transferring buffers in and out of the value type, even if abstract,
|
||||||
|
those bodies may be serialized or parsed.
|
||||||
|
|
||||||
|
[note
|
||||||
|
The examples included with this library provide a [*Body]
|
||||||
|
implementation that serializes message bodies coming from a file.
|
||||||
|
This is part of the HTTP server example.
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading Reader]
|
||||||
|
|
||||||
|
The reader provides the algorithm for transferring buffers containing body
|
||||||
|
octets obtained during parsing into the body container. The requirements
|
||||||
|
for this type are described in the __BodyReader__ concept. When a body type
|
||||||
|
defines a reader it may then be parsed using a __parser__.
|
||||||
|
|
||||||
|
[heading Writer]
|
||||||
|
|
||||||
|
The writer provides the algorithm for converting the body container into a
|
||||||
|
series of buffers. The requirements for this type are described in the
|
||||||
|
__BodyWriter__ concept. When a body type defines a writer it may then be
|
||||||
|
serialized using a __serializer__.
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
50
doc/4_0_websocket.qbk
Normal file
50
doc/4_0_websocket.qbk
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:websocket Using WebSocket]
|
||||||
|
|
||||||
|
[block '''
|
||||||
|
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
||||||
|
<member><link linkend="beast.websocket.streams">Creating Streams</link></member>
|
||||||
|
<member><link linkend="beast.websocket.connect">Establishing Connections</link></member>
|
||||||
|
<member><link linkend="beast.websocket.client">Handshaking (Clients)</link></member>
|
||||||
|
<member><link linkend="beast.websocket.server">Handshaking (Servers)</link></member>
|
||||||
|
<member><link linkend="beast.websocket.messages">Send and Receive Messages</link></member>
|
||||||
|
<member><link linkend="beast.websocket.control">Control Frames</link></member>
|
||||||
|
<member><link linkend="beast.websocket.notes">Additional Notes</link></member>
|
||||||
|
</simplelist></entry></row></tbody></tgroup></informaltable>
|
||||||
|
''']
|
||||||
|
|
||||||
|
The WebSocket Protocol enables two-way communication between a client
|
||||||
|
running untrusted code in a controlled environment to a remote host that has
|
||||||
|
opted-in to communications from that code. The protocol consists of an opening
|
||||||
|
handshake followed by basic message framing, layered over TCP. The goal of
|
||||||
|
this technology is to provide a mechanism for browser-based applications
|
||||||
|
needing two-way communication with servers without relying on opening multiple
|
||||||
|
HTTP connections.
|
||||||
|
|
||||||
|
Beast provides developers with a robust WebSocket implementation built on
|
||||||
|
Boost.Asio with a consistent asynchronous model using a modern C++ approach.
|
||||||
|
|
||||||
|
[note
|
||||||
|
The WebSocket documentation assumes familiarity the WebSocket protocol
|
||||||
|
specification described in __rfc6455__. Code appearing in these
|
||||||
|
sections is written as if the following declarations are in effect:
|
||||||
|
```
|
||||||
|
#include <beast/websocket.hpp>
|
||||||
|
```
|
||||||
|
]
|
||||||
|
|
||||||
|
[include 4_1_streams.qbk]
|
||||||
|
[include 4_2_connect.qbk]
|
||||||
|
[include 4_3_client.qbk]
|
||||||
|
[include 4_4_server.qbk]
|
||||||
|
[include 4_5_messages.qbk]
|
||||||
|
[include 4_6_control.qbk]
|
||||||
|
[include 4_7_notes.qbk]
|
||||||
|
|
||||||
|
[endsect]
|
78
doc/4_1_streams.qbk
Normal file
78
doc/4_1_streams.qbk
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:streams Creating Streams]
|
||||||
|
|
||||||
|
The interface to the WebSocket implementation is a single template class
|
||||||
|
[link beast.ref.websocket__stream `websocket::stream`]
|
||||||
|
which wraps an existing network transport object or other type of
|
||||||
|
octet oriented stream. The wrapped object is called the "next layer"
|
||||||
|
and must meet the requirements of __SyncStream__ if synchronous
|
||||||
|
operations are performed, __AsyncStream__ if asynchronous operations
|
||||||
|
are performed, or both. Any arguments supplied during construction of
|
||||||
|
the stream wrapper are passed to next layer's constructor.
|
||||||
|
|
||||||
|
Here we declare a websocket stream over a TCP/IP socket with ownership
|
||||||
|
of the socket. The `io_service` argument is forwarded to the wrapped
|
||||||
|
socket's constructor:
|
||||||
|
```
|
||||||
|
boost::asio::io_service ios;
|
||||||
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
||||||
|
```
|
||||||
|
|
||||||
|
[heading Using SSL]
|
||||||
|
|
||||||
|
To use WebSockets over SSL, use an instance of the `boost::asio::ssl::stream`
|
||||||
|
class template as the template type for the stream. The required `io_service`
|
||||||
|
and `ssl::context` arguments are forwarded to the wrapped stream's constructor:
|
||||||
|
```
|
||||||
|
#include <beast/websocket/ssl.hpp>
|
||||||
|
#include <boost/asio/ssl.hpp>
|
||||||
|
|
||||||
|
boost::asio::io_service ios;
|
||||||
|
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
|
||||||
|
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ws{ios, ctx};
|
||||||
|
```
|
||||||
|
|
||||||
|
[note
|
||||||
|
Code which declares stream objects using Asio SSL types must
|
||||||
|
toinclude the file `<beast/websocket/ssl.hpp>`.
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading Non-owning References]
|
||||||
|
|
||||||
|
If a socket type supports move construction, a websocket stream may be
|
||||||
|
constructed around the already existing socket by invoke the move
|
||||||
|
constructor signature:
|
||||||
|
```
|
||||||
|
...
|
||||||
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{std::move(sock)};
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, the wrapper can be constructed with a non-owning reference. In
|
||||||
|
this case, the caller is responsible for managing the lifetime of the
|
||||||
|
underlying socket being wrapped:
|
||||||
|
```
|
||||||
|
...
|
||||||
|
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the WebSocket stream wrapper is created, the wrapped object may be
|
||||||
|
accessed by calling [link beast.ref.websocket__stream.next_layer.overload1 `stream::next_layer`]:
|
||||||
|
```
|
||||||
|
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
|
||||||
|
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws{ios, ctx};
|
||||||
|
...
|
||||||
|
ws.next_layer().shutdown(); // ssl::stream shutdown
|
||||||
|
```
|
||||||
|
|
||||||
|
[warning
|
||||||
|
Initiating operations on the next layer while websocket
|
||||||
|
operations are being performed may result in undefined behavior.
|
||||||
|
]
|
||||||
|
|
||||||
|
[endsect]
|
54
doc/4_2_connect.qbk
Normal file
54
doc/4_2_connect.qbk
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:connect Establishing Connections]
|
||||||
|
|
||||||
|
Connections are established by invoking functions directly on the next layer
|
||||||
|
object. For example, to make an outgoing connection using a standard TCP/IP
|
||||||
|
socket:
|
||||||
|
```
|
||||||
|
std::string const host = "mywebapp.com";
|
||||||
|
boost::asio::io_service ios;
|
||||||
|
boost::asio::ip::tcp::resolver r{ios};
|
||||||
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
||||||
|
boost::asio::connect(ws.next_layer(),
|
||||||
|
r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"}));
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, to accept an incoming connection using a standard TCP/IP
|
||||||
|
socket, pass the next layer object to the acceptor:
|
||||||
|
```
|
||||||
|
void do_accept(boost::asio::ip::tcp::acceptor& acceptor)
|
||||||
|
{
|
||||||
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{acceptor.get_io_service()};
|
||||||
|
acceptor.accept(ws.next_layer());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When using SSL, which itself wraps a next layer object that is usually a
|
||||||
|
TCP/IP socket, multiple calls to retrieve the next layer may be required.
|
||||||
|
In this example, the websocket stream wraps the SSL stream which wraps
|
||||||
|
the TCP/IP socket:
|
||||||
|
```
|
||||||
|
beast::websocket::stream<boost::asio::ssl::stream<
|
||||||
|
boost::asio::ip::tcp::socket>> ws{ios, ctx};
|
||||||
|
|
||||||
|
// connect the underlying TCP/IP socket
|
||||||
|
ws.next_layer().next_layer().connect(ep);
|
||||||
|
|
||||||
|
// perform SSL handshake
|
||||||
|
ws.next_layer().handshake(boost::asio::ssl::stream_base::client);
|
||||||
|
|
||||||
|
// perform WebSocket handshake
|
||||||
|
ws.handshake("localhost", "/");
|
||||||
|
```
|
||||||
|
|
||||||
|
[note
|
||||||
|
Examples use synchronous interfaces for clarity of exposition.
|
||||||
|
]
|
||||||
|
|
||||||
|
[endsect]
|
109
doc/4_3_client.qbk
Normal file
109
doc/4_3_client.qbk
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:client Handshaking (Clients)]
|
||||||
|
|
||||||
|
A WebSocket session begins when a client sends the HTTP
|
||||||
|
[@https://tools.ietf.org/html/rfc7230#section-6.7 Upgrade]
|
||||||
|
request for
|
||||||
|
[@https://tools.ietf.org/html/rfc6455#section-1.3 websocket],
|
||||||
|
and the server sends an appropriate HTTP response indicating that
|
||||||
|
the request was accepted and that the connection has been upgraded.
|
||||||
|
The HTTP Upgrade request must include the
|
||||||
|
[@https://tools.ietf.org/html/rfc7230#section-5.4 Host]
|
||||||
|
field, and the
|
||||||
|
[@https://tools.ietf.org/html/rfc7230#section-5.3 target]
|
||||||
|
of the resource to request. The stream member functions
|
||||||
|
[link beast.ref.websocket__stream.handshake.overload1 `handshake`] and
|
||||||
|
[link beast.ref.websocket__stream.async_handshake.overload1 `async_handshake`]
|
||||||
|
are used to send the request with the required host and target strings.
|
||||||
|
```
|
||||||
|
...
|
||||||
|
ws.set_option(websocket::keep_alive(true));
|
||||||
|
ws.handshake("localhost", "/");
|
||||||
|
```
|
||||||
|
|
||||||
|
The implementation will create and send an HTTP request that typically
|
||||||
|
looks like this:
|
||||||
|
|
||||||
|
[table WebSocket Upgrade HTTP Request
|
||||||
|
[[Serialized Octets][Description]]
|
||||||
|
[[
|
||||||
|
```
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: upgrade
|
||||||
|
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
|
||||||
|
Sec-WebSocket-Version: 13
|
||||||
|
User-Agent: Beast
|
||||||
|
```
|
||||||
|
][
|
||||||
|
The host and target parameters become part of the Host field
|
||||||
|
and request-target in the resulting HTTP request. The key is
|
||||||
|
generated by the implementation. Callers may add fields or
|
||||||
|
modify fields by providing a ['decorator], described below.
|
||||||
|
]]]
|
||||||
|
|
||||||
|
[heading Decorators]
|
||||||
|
|
||||||
|
If the caller wishes to add or modify fields, the member functions
|
||||||
|
[link beast.ref.websocket__stream.handshake_ex `handshake_ex`] and
|
||||||
|
[link beast.ref.websocket__stream.async_handshake_ex `async_handshake_ex`]
|
||||||
|
are provided which allow an additional function object, called a
|
||||||
|
['decorator], to be passed. The decorator is invoked to modify
|
||||||
|
the HTTP Upgrade request as needed. This example sets a subprotocol
|
||||||
|
on the request:
|
||||||
|
```
|
||||||
|
void decorate(websocket::request_type& req)
|
||||||
|
{
|
||||||
|
req.fields.insert("Sec-WebSocket-Protocol", "xmpp;ws-chat");
|
||||||
|
}
|
||||||
|
...
|
||||||
|
ws.handshake_ex("localhost", "/", &decorate);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The HTTP Upgrade request produced by the previous call will look thusly:
|
||||||
|
|
||||||
|
[table Decorated WebSocket Upgrade HTTP Request
|
||||||
|
[[Serialized Octets][Description]]
|
||||||
|
[[
|
||||||
|
```
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: upgrade
|
||||||
|
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
|
||||||
|
Sec-WebSocket-Version: 13
|
||||||
|
Sec-WebSocket-Protocol: xmpp;ws-chat
|
||||||
|
User-Agent: Beast
|
||||||
|
```
|
||||||
|
][
|
||||||
|
Undefined behavior results if the decorator modifies the fields
|
||||||
|
specific to perform the WebSocket Upgrade , such as the Upgrade
|
||||||
|
and Connection fields.
|
||||||
|
]]]
|
||||||
|
|
||||||
|
[heading Filtering]
|
||||||
|
|
||||||
|
When a client receives an HTTP Upgrade response from the server indicating
|
||||||
|
a successful upgrade, the caller may wish to perform additional validation
|
||||||
|
on the received HTTP response message. For example, to check that the
|
||||||
|
response to a basic authentication challenge is valid. To achieve this,
|
||||||
|
overloads of the handshake member function allow the caller to store the
|
||||||
|
received HTTP message in an output reference argument as
|
||||||
|
[link beast.ref.websocket__response_type `response_type`]
|
||||||
|
as follows:
|
||||||
|
```
|
||||||
|
websocket::response_type res;
|
||||||
|
ws.handshake(res, "localhost", "/");
|
||||||
|
if(! res.fields.exists("Sec-WebSocket-Protocol"))
|
||||||
|
throw std::invalid_argument("missing subprotocols");
|
||||||
|
```
|
||||||
|
|
||||||
|
[endsect]
|
144
doc/4_4_server.qbk
Normal file
144
doc/4_4_server.qbk
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:server Handshaking (Servers)]
|
||||||
|
|
||||||
|
A
|
||||||
|
[link beast.ref.websocket__stream `stream`]
|
||||||
|
automatically handles receiving and processing the HTTP response to the
|
||||||
|
handshake request. The call to handshake is successful if a HTTP response
|
||||||
|
is received with the 101 "Switching Protocols" status code. On failure,
|
||||||
|
an error is returned or an exception is thrown. Depending on the keep alive
|
||||||
|
setting, the connection may remain open for a subsequent handshake attempt.
|
||||||
|
|
||||||
|
Performing a handshake for an incoming websocket upgrade request operates
|
||||||
|
similarly. If the handshake fails, an error is returned or exception thrown:
|
||||||
|
```
|
||||||
|
...
|
||||||
|
ws.accept();
|
||||||
|
```
|
||||||
|
|
||||||
|
Successful WebSocket Upgrade responses generated by the implementation will
|
||||||
|
typically look like this:
|
||||||
|
|
||||||
|
[table Decorated WebSocket Upgrade HTTP Request
|
||||||
|
[[Serialized Octets][Description]]
|
||||||
|
[[
|
||||||
|
```
|
||||||
|
HTTP/1.1 101 Switching Protocols
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: upgrade
|
||||||
|
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
Server: Beast/40
|
||||||
|
```
|
||||||
|
][
|
||||||
|
The Sec-WebSocket-Accept field value is generated from the
|
||||||
|
request in a fashion specified by the WebSocket protocol.
|
||||||
|
]]]
|
||||||
|
|
||||||
|
[heading Decorators]
|
||||||
|
|
||||||
|
If the caller wishes to add or modify fields, the member functions
|
||||||
|
[link beast.ref.websocket__stream.accept_ex `accept_ex`] and
|
||||||
|
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
||||||
|
are provided which allow an additional function object, called a
|
||||||
|
['decorator], to be passed. The decorator is invoked to modify
|
||||||
|
the HTTP Upgrade request as needed. This example sets the Server
|
||||||
|
field on the response:
|
||||||
|
```
|
||||||
|
ws.accept_ex(
|
||||||
|
[](websocket::response_type& res)
|
||||||
|
{
|
||||||
|
res.fields.insert("Server", "AcmeServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The HTTP Upgrade response produced by the previous call will look thusly:
|
||||||
|
|
||||||
|
[table Decorated WebSocket Upgrade HTTP Request
|
||||||
|
[[Serialized Octets][Description]]
|
||||||
|
[[
|
||||||
|
```
|
||||||
|
HTTP/1.1 101 Switching Protocols
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: upgrade
|
||||||
|
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
Server: AcmeServer
|
||||||
|
```
|
||||||
|
][
|
||||||
|
When the Upgrade request fails, the implementation will still invoke
|
||||||
|
the decorator to modify the response. In this case, the response
|
||||||
|
object will have a status code other than 101.
|
||||||
|
|
||||||
|
Undefined behavior results when the upgrade request is successful
|
||||||
|
and the decorator modifies the fields specific to perform the
|
||||||
|
WebSocket Upgrade , such as the Upgrade and Connection fields.
|
||||||
|
]]]
|
||||||
|
|
||||||
|
[heading Passing HTTP Requests]
|
||||||
|
|
||||||
|
When implementing an HTTP server that also supports WebSocket, the
|
||||||
|
server usually reads the HTTP request from the client. To detect when
|
||||||
|
the incoming HTTP request is a WebSocket Upgrade request, the function
|
||||||
|
[link beast.ref.websocket__is_upgrade `is_upgrade`] may be used.
|
||||||
|
|
||||||
|
Once the caller determines that the HTTP request is a WebSocket Upgrade,
|
||||||
|
additional overloads of
|
||||||
|
[link beast.ref.websocket__stream.accept `accept`],
|
||||||
|
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
|
||||||
|
[link beast.ref.websocket__stream.async_accept `async_accept`], and
|
||||||
|
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
||||||
|
are provided which receive the entire HTTP request header as an object
|
||||||
|
to perform the handshake. In this example, the request is first read
|
||||||
|
in using the HTTP algorithms, and then passed to a newly constructed
|
||||||
|
stream:
|
||||||
|
```
|
||||||
|
void handle_connection(boost::asio::ip::tcp::socket& sock)
|
||||||
|
{
|
||||||
|
flat_buffer buffer;
|
||||||
|
http::request<http::string_body> req;
|
||||||
|
http::read(sock, buffer, req);
|
||||||
|
if(websocket::is_upgrade(req))
|
||||||
|
{
|
||||||
|
websocket::stream<decltype(sock)> ws{std::move(sock)};
|
||||||
|
ws.accept(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[heading Buffered Handshakes]
|
||||||
|
|
||||||
|
Sometimes a server implementation wishes to read octets on the stream
|
||||||
|
in order to route the incoming request. For example, a server may read
|
||||||
|
the first 6 octets after accepting an incoming connection to determine
|
||||||
|
if a TLS protocol is being negotiated, and choose a suitable implementation
|
||||||
|
at run-time. In the case where the server wishes to accept the incoming
|
||||||
|
request as an HTTP WebSocket Upgrade request, additional overloads of
|
||||||
|
[link beast.ref.websocket__stream.accept `accept`],
|
||||||
|
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
|
||||||
|
[link beast.ref.websocket__stream.async_accept `async_accept`], and
|
||||||
|
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
||||||
|
are provided which receive the additional buffered octects and consume
|
||||||
|
them as part of the handshake.
|
||||||
|
|
||||||
|
In this example, the server reads the initial HTTP message into the
|
||||||
|
specified dynamic buffer as an octet sequence in the buffer's output
|
||||||
|
area, and later uses those octets to attempt an HTTP WebSocket Upgrade:
|
||||||
|
```
|
||||||
|
void do_accept(boost::asio::ip::tcp::socket& sock)
|
||||||
|
{
|
||||||
|
boost::asio::streambuf sb;
|
||||||
|
boost::asio::read_until(sock, sb, "\r\n\r\n");
|
||||||
|
...
|
||||||
|
websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
|
||||||
|
ws.accept(sb.data());
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[endsect]
|
77
doc/4_5_messages.qbk
Normal file
77
doc/4_5_messages.qbk
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:messages Send and Receive Messages]
|
||||||
|
|
||||||
|
After the WebSocket handshake is accomplished, callers may send and receive
|
||||||
|
messages using the message oriented interface. This interface requires that
|
||||||
|
all of the buffers representing the message are known ahead of time:
|
||||||
|
```
|
||||||
|
template<class NextLayer>
|
||||||
|
void echo(websocket::stream<NextLayer>& ws)
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
websocket::opcode::value op;
|
||||||
|
ws.read(op, b);
|
||||||
|
|
||||||
|
ws.set_option(websocket::message_type{op});
|
||||||
|
ws.write(b.data());
|
||||||
|
b.consume(b.size());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[important
|
||||||
|
Calls to [link beast.ref.websocket__stream.set_option `set_option`]
|
||||||
|
must be made from the same implicit or explicit strand as that used
|
||||||
|
to perform other operations.
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading Frames]
|
||||||
|
|
||||||
|
Some use-cases make it impractical or impossible to buffer the entire
|
||||||
|
message ahead of time:
|
||||||
|
|
||||||
|
* Streaming multimedia to an endpoint.
|
||||||
|
* Sending a message that does not fit in memory at once.
|
||||||
|
* Providing incremental results as they become available.
|
||||||
|
|
||||||
|
For these cases, the frame oriented interface may be used. This
|
||||||
|
example reads and echoes a complete message using this interface:
|
||||||
|
```
|
||||||
|
template<class NextLayer>
|
||||||
|
void echo(websocket::stream<NextLayer>& ws)
|
||||||
|
{
|
||||||
|
multi_buffer b;
|
||||||
|
websocket::frame_info fi;
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
ws.read_frame(fi, b);
|
||||||
|
if(fi.fin)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ws.set_option(websocket::message_type{fi.op});
|
||||||
|
consuming_buffers<
|
||||||
|
multi_buffer::const_buffers_type> cb{b.data()};
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
using boost::asio::buffer_size;
|
||||||
|
std::size_t size = std::min(buffer_size(cb));
|
||||||
|
if(size > 512)
|
||||||
|
{
|
||||||
|
ws.write_frame(false, prepare_buffers(512, cb));
|
||||||
|
cb.consume(512);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ws.write_frame(true, cb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[endsect]
|
116
doc/4_6_control.qbk
Normal file
116
doc/4_6_control.qbk
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:control Control Frames]
|
||||||
|
|
||||||
|
Control frames are small (less than 128 bytes) messages entirely contained
|
||||||
|
in an individual WebSocket frame. They may be sent at any time by either
|
||||||
|
peer on an established connection, and can appear in between continuation
|
||||||
|
frames for a message. There are three types of control frames: ping, pong,
|
||||||
|
and close.
|
||||||
|
|
||||||
|
A sent ping indicates a request that the sender wants to receive a pong. A
|
||||||
|
pong is a response to a ping. Pongs may be sent unsolicited, at any time.
|
||||||
|
One use for an unsolicited pong is to inform the remote peer that the
|
||||||
|
session is still active after a long period of inactivity. A close frame
|
||||||
|
indicates that the remote peer wishes to close the WebSocket connection.
|
||||||
|
The connection is considered gracefully closed when each side has sent
|
||||||
|
and received a close frame.
|
||||||
|
|
||||||
|
During read operations, Beast automatically reads and processes control
|
||||||
|
frames. Pings are replied to as soon as possible with a pong, received
|
||||||
|
ping and pongs are delivered to the ping callback. The receipt of a close
|
||||||
|
frame initiates the WebSocket close procedure, eventually resulting in the
|
||||||
|
error code [link beast.ref.websocket__error `error::closed`] being delivered
|
||||||
|
to the caller in a subsequent read operation, assuming no other error
|
||||||
|
takes place.
|
||||||
|
|
||||||
|
A consequence of this automatic behavior is that caller-initiated read
|
||||||
|
operations can cause socket writes. However, these writes will not
|
||||||
|
compete with caller-initiated write operations. For the purposes of
|
||||||
|
correctness with respect to the stream invariants, caller-initiated
|
||||||
|
read operations still only count as a read. This means that callers can
|
||||||
|
have a simultaneously active read, write, and ping operation in progress,
|
||||||
|
while the implementation also automatically handles control frames.
|
||||||
|
|
||||||
|
[heading Ping and Pong Frames]
|
||||||
|
|
||||||
|
Ping and pong messages are control frames which may be sent at any time
|
||||||
|
by either peer on an established WebSocket connection. They are sent
|
||||||
|
using the functions
|
||||||
|
[link beast.ref.websocket__stream.ping `ping`] and
|
||||||
|
[link beast.ref.websocket__stream.pong `pong`].
|
||||||
|
|
||||||
|
To be notified of ping and pong control frames, callers may register a
|
||||||
|
"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`].
|
||||||
|
The object provided with this option should be callable with the following
|
||||||
|
signature:
|
||||||
|
```
|
||||||
|
void on_ping(bool is_pong, websocket::ping_data const& payload);
|
||||||
|
...
|
||||||
|
ws.set_option(ping_callback{&on_ping});
|
||||||
|
```
|
||||||
|
|
||||||
|
When a ping callback is registered, all pings and pongs received through
|
||||||
|
either synchronous read functions or asynchronous read functions will
|
||||||
|
invoke the ping callback, with the value of `is_pong` set to `true` if a
|
||||||
|
pong was received else `false` if a ping was received. The payload of
|
||||||
|
the ping or pong control frame is passed in the payload argument.
|
||||||
|
|
||||||
|
Unlike regular completion handlers used in calls to asynchronous initiation
|
||||||
|
functions, the ping callback only needs to be set once. The callback is not
|
||||||
|
reset when a ping or pong is received. The same callback is used for both
|
||||||
|
synchronous and asynchronous reads. The ping callback is passive; in order
|
||||||
|
to receive pings and pongs, a synchronous or asynchronous stream read
|
||||||
|
function must be active.
|
||||||
|
|
||||||
|
[note
|
||||||
|
When an asynchronous read function receives a ping or pong, the
|
||||||
|
ping callback is invoked in the same manner as that used to invoke
|
||||||
|
the final completion handler of the corresponding read function.
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading Close Frames]
|
||||||
|
|
||||||
|
The WebSocket protocol defines a procedure and control message for initiating
|
||||||
|
a close of the session. Handling of close initiated by the remote end of the
|
||||||
|
connection is performed automatically. To manually initiate a close, use
|
||||||
|
the
|
||||||
|
[link beast.ref.websocket__stream.close `close`]
|
||||||
|
function:
|
||||||
|
```
|
||||||
|
ws.close();
|
||||||
|
```
|
||||||
|
|
||||||
|
When the remote peer initiates a close by sending a close frame, Beast
|
||||||
|
will handle it for you by causing the next read to return `error::closed`.
|
||||||
|
When this error code is delivered, it indicates to the application that
|
||||||
|
the WebSocket connection has been closed cleanly, and that the TCP/IP
|
||||||
|
connection has been closed. After initiating a close, it is necessary to
|
||||||
|
continue reading messages until receiving the error `error::closed`. This
|
||||||
|
is because the remote peer may still be sending message and control frames
|
||||||
|
before it receives and responds to the close frame.
|
||||||
|
|
||||||
|
[important
|
||||||
|
To receive the
|
||||||
|
[link beast.ref.websocket__error `error::closed`]
|
||||||
|
error, a read operation is required.
|
||||||
|
]
|
||||||
|
|
||||||
|
[heading Auto-fragment]
|
||||||
|
|
||||||
|
To ensure timely delivery of control frames, large messages can be broken up
|
||||||
|
into smaller sized frames. The automatic fragment option turns on this
|
||||||
|
feature, and the write buffer size option determines the maximum size of
|
||||||
|
the fragments:
|
||||||
|
```
|
||||||
|
...
|
||||||
|
ws.set_option(websocket::auto_fragment{true});
|
||||||
|
ws.set_option(websocket::write_buffer_size{16384});
|
||||||
|
```
|
||||||
|
|
||||||
|
[endsect]
|
69
doc/4_7_notes.qbk
Normal file
69
doc/4_7_notes.qbk
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
[/
|
||||||
|
Copyright (c) 2013-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)
|
||||||
|
]
|
||||||
|
|
||||||
|
[section:notes Notes]
|
||||||
|
|
||||||
|
Because calls to read data may return a variable amount of bytes, the
|
||||||
|
interface to calls that read data require an object that meets the requirements
|
||||||
|
of __DynamicBuffer__. This concept is modeled on __streambuf__.
|
||||||
|
|
||||||
|
The implementation does not perform queueing or buffering of messages. If
|
||||||
|
desired, these features should be provided by callers. The impact of this
|
||||||
|
design is that library users are in full control of the allocation strategy
|
||||||
|
used to store data and the back-pressure applied on the read and write side
|
||||||
|
of the underlying TCP/IP connection.
|
||||||
|
|
||||||
|
[heading Asynchronous Operations]
|
||||||
|
|
||||||
|
Asynchronous versions are available for all functions:
|
||||||
|
```
|
||||||
|
websocket::opcode op;
|
||||||
|
ws.async_read(op, sb,
|
||||||
|
[](boost::system::error_code const& ec)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Calls to asynchronous initiation functions support the extensible asynchronous
|
||||||
|
model developed by the Boost.Asio author, allowing for traditional completion
|
||||||
|
handlers, stackful or stackless coroutines, and even futures:
|
||||||
|
```
|
||||||
|
void echo(websocket::stream<ip::tcp::socket>& ws,
|
||||||
|
boost::asio::yield_context yield)
|
||||||
|
{
|
||||||
|
ws.async_read(sb, yield);
|
||||||
|
std::future<websocket::error_code> fut =
|
||||||
|
ws.async_write, sb.data(), boost::use_future);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[heading The io_service]
|
||||||
|
|
||||||
|
The creation and operation of the __io_service__ associated with the
|
||||||
|
underlying stream is left to the callers, permitting any implementation
|
||||||
|
strategy including one that does not require threads for environments
|
||||||
|
where threads are unavailable. Beast WebSocket itself does not use
|
||||||
|
or require threads.
|
||||||
|
|
||||||
|
[heading Thread Safety]
|
||||||
|
|
||||||
|
Like a regular __Asio__ socket, a
|
||||||
|
[link beast.ref.websocket__stream `stream`]
|
||||||
|
is not thread safe. Callers are responsible for synchronizing operations on
|
||||||
|
the socket using an implicit or explicit strand, as per the Asio documentation.
|
||||||
|
The asynchronous interface supports one active read and one active write
|
||||||
|
simultaneously. Undefined behavior results if two or more reads or two or
|
||||||
|
more writes are attempted concurrently. Caller initiated WebSocket ping, pong,
|
||||||
|
and close operations each count as an active write.
|
||||||
|
|
||||||
|
The implementation uses composed asynchronous operations internally; a high
|
||||||
|
level read can cause both reads and writes to take place on the underlying
|
||||||
|
stream. This behavior is transparent to callers.
|
||||||
|
|
||||||
|
[endsect]
|
@@ -1,651 +0,0 @@
|
|||||||
[/
|
|
||||||
Copyright (c) 2013-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)
|
|
||||||
]
|
|
||||||
|
|
||||||
[section:websocket WebSocket]
|
|
||||||
|
|
||||||
[block '''
|
|
||||||
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
|
|
||||||
<member><link linkend="beast.websocket.creation">Creation</link></member>
|
|
||||||
<member><link linkend="beast.websocket.connections">Making connections</link></member>
|
|
||||||
<member><link linkend="beast.websocket.handshaking">Handshaking</link></member>
|
|
||||||
<member><link linkend="beast.websocket.accepting">Accepting</link></member>
|
|
||||||
<member><link linkend="beast.websocket.messages">Messages</link></member>
|
|
||||||
<member><link linkend="beast.websocket.control">Control Frames</link></member>
|
|
||||||
<member><link linkend="beast.websocket.notes">Notes</link></member>
|
|
||||||
</simplelist></entry></row></tbody></tgroup></informaltable>
|
|
||||||
''']
|
|
||||||
|
|
||||||
The WebSocket Protocol enables two-way communication between a client
|
|
||||||
running untrusted code in a controlled environment to a remote host that has
|
|
||||||
opted-in to communications from that code. The protocol consists of an opening
|
|
||||||
handshake followed by basic message framing, layered over TCP. The goal of
|
|
||||||
this technology is to provide a mechanism for browser-based applications that
|
|
||||||
need two-way communication with servers that does not rely on opening multiple
|
|
||||||
HTTP connections.
|
|
||||||
|
|
||||||
Beast provides developers with a robust WebSocket implementation built on
|
|
||||||
Boost.Asio with a consistent asynchronous model using a modern C++ approach.
|
|
||||||
|
|
||||||
The WebSocket protocol is described fully in
|
|
||||||
[@https://tools.ietf.org/html/rfc6455 rfc6455]
|
|
||||||
|
|
||||||
[note
|
|
||||||
The following documentation assumes familiarity with both
|
|
||||||
Boost.Asio and the WebSocket protocol specification described in __rfc6455__.
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:creation Creation]
|
|
||||||
|
|
||||||
The interface to Beast's WebSocket implementation is a single template
|
|
||||||
class [link beast.ref.websocket__stream `websocket::stream`] which
|
|
||||||
wraps a "next layer" object. The next layer object must meet the requirements
|
|
||||||
of [link beast.ref.streams.SyncStream [*SyncStream]] if synchronous
|
|
||||||
operations are performed, or
|
|
||||||
[link beast.ref.streams.AsyncStream [*AsyncStream]] if asynchronous
|
|
||||||
operations are performed, or both. Arguments supplied during construction are
|
|
||||||
passed to next layer's constructor. Here we declare a websocket stream over
|
|
||||||
a TCP/IP socket with ownership of the socket:
|
|
||||||
```
|
|
||||||
boost::asio::io_service ios;
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
|
||||||
```
|
|
||||||
|
|
||||||
[heading Using SSL]
|
|
||||||
|
|
||||||
To use WebSockets over SSL, choose an SSL stream for the next layer template
|
|
||||||
argument when constructing the stream.
|
|
||||||
```
|
|
||||||
#include <beast/websocket/ssl.hpp>
|
|
||||||
#include <beast/websocket.hpp>
|
|
||||||
#include <boost/asio/ssl.hpp>
|
|
||||||
|
|
||||||
boost::asio::io_service ios;
|
|
||||||
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
|
|
||||||
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ws{ios, ctx};
|
|
||||||
```
|
|
||||||
|
|
||||||
[note
|
|
||||||
When creating websocket stream objects using SSL, it is necessary
|
|
||||||
to include the file `<beast/websocket/ssl.hpp>`.
|
|
||||||
]
|
|
||||||
|
|
||||||
[heading Non-owning references]
|
|
||||||
|
|
||||||
For servers that can handshake in multiple protocols, it may be desired
|
|
||||||
to wrap an object that already exists. This socket can be moved in:
|
|
||||||
```
|
|
||||||
boost::asio::ip::tcp::socket sock;
|
|
||||||
...
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{std::move(sock)};
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, the wrapper can be constructed with a non-owning reference. In
|
|
||||||
this case, the caller is responsible for managing the lifetime of the
|
|
||||||
underlying socket being wrapped:
|
|
||||||
```
|
|
||||||
boost::asio::ip::tcp::socket sock;
|
|
||||||
...
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
|
|
||||||
```
|
|
||||||
|
|
||||||
The layer being wrapped can be accessed through the websocket's "next layer",
|
|
||||||
permitting callers to interact directly with its interface.
|
|
||||||
```
|
|
||||||
boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23};
|
|
||||||
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws{ios, ctx};
|
|
||||||
...
|
|
||||||
ws.next_layer().shutdown(); // ssl::stream shutdown
|
|
||||||
```
|
|
||||||
|
|
||||||
[warning
|
|
||||||
Initiating read and write operations on the next layer while
|
|
||||||
stream operations are being performed can break invariants, and
|
|
||||||
result in undefined behavior.
|
|
||||||
]
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:connections Making connections]
|
|
||||||
|
|
||||||
Connections are established by using the interfaces which already exist
|
|
||||||
for the next layer. For example, making an outgoing connection:
|
|
||||||
```
|
|
||||||
std::string const host = "mywebapp.com";
|
|
||||||
boost::asio::io_service ios;
|
|
||||||
boost::asio::ip::tcp::resolver r{ios};
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
|
||||||
boost::asio::connect(ws.next_layer(),
|
|
||||||
r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"}));
|
|
||||||
```
|
|
||||||
|
|
||||||
Accepting an incoming connection:
|
|
||||||
```
|
|
||||||
void do_accept(boost::asio::ip::tcp::acceptor& acceptor)
|
|
||||||
{
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{acceptor.get_io_service()};
|
|
||||||
acceptor.accept(ws.next_layer());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When using SSL, which itself wraps a next layer object that is usualy a
|
|
||||||
TCP/IP socket, multiple calls to retrieve the next layer may be required:
|
|
||||||
```
|
|
||||||
beast::websocket::stream<boost::asio::ssl::stream<
|
|
||||||
boost::asio::ip::tcp::socket>> ws{ios, ctx};
|
|
||||||
|
|
||||||
// connect the underlying TCP/IP socket
|
|
||||||
ws.next_layer().next_layer().connect(ep);
|
|
||||||
|
|
||||||
// perform SSL handshake
|
|
||||||
ws.next_layer().handshake(boost::asio::ssl::stream_base::client);
|
|
||||||
|
|
||||||
// perform WebSocket handshake
|
|
||||||
ws.handshake("localhost", "/");
|
|
||||||
```
|
|
||||||
|
|
||||||
[note
|
|
||||||
Examples use synchronous interfaces for clarity of exposition.
|
|
||||||
]
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:handshaking Handshaking]
|
|
||||||
|
|
||||||
A WebSocket session begins when one side sends the HTTP Upgrade request
|
|
||||||
for websocket, and the other side sends an appropriate HTTP response
|
|
||||||
indicating that the request was accepted and that the connection has
|
|
||||||
been upgraded. The HTTP Upgrade request must include the Host HTTP field,
|
|
||||||
and the target of the resource to request. The stream member functions
|
|
||||||
[link beast.ref.websocket__stream.handshake `handshake`] and
|
|
||||||
[link beast.ref.websocket__stream.async_handshake `async_handshake`]
|
|
||||||
are used to send the request with the required host and target strings.
|
|
||||||
```
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
|
||||||
...
|
|
||||||
ws.set_option(beast::websocket::keep_alive(true));
|
|
||||||
ws.handshake("localhost", "/");
|
|
||||||
```
|
|
||||||
|
|
||||||
The implementation will create and send an HTTP request that typically
|
|
||||||
looks like this:
|
|
||||||
|
|
||||||
[table WebSocket Upgrade HTTP Request
|
|
||||||
[[Serialized Octets][Description]]
|
|
||||||
[[
|
|
||||||
```
|
|
||||||
GET / HTTP/1.1
|
|
||||||
Host: localhost
|
|
||||||
Upgrade: websocket
|
|
||||||
Connection: upgrade
|
|
||||||
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
|
|
||||||
Sec-WebSocket-Version: 13
|
|
||||||
User-Agent: Beast
|
|
||||||
```
|
|
||||||
][
|
|
||||||
The host and target parameters become part of the Host field
|
|
||||||
and request-target in the resulting HTTP request. The key is
|
|
||||||
generated by the implementation. Callers may add fields or
|
|
||||||
modify fields by providing a ['decorator], described below.
|
|
||||||
]]]
|
|
||||||
|
|
||||||
[heading Decorators]
|
|
||||||
|
|
||||||
If the caller wishes to add or modify fields, the member functions
|
|
||||||
[link beast.ref.websocket__stream.handshake_ex `handshake_ex`] and
|
|
||||||
[link beast.ref.websocket__stream.async_handshake_ex `async_handshake_ex`]
|
|
||||||
are provided which allow an additional function object, called a
|
|
||||||
['decorator], to be passed. The decorator is invoked to modify
|
|
||||||
the HTTP Upgrade request as needed. This example sets a subprotocol
|
|
||||||
on the request:
|
|
||||||
```
|
|
||||||
void decorate(beast::websocket::request_type& req)
|
|
||||||
{
|
|
||||||
req.fields.insert("Sec-WebSocket-Protocol", "xmpp;ws-chat");
|
|
||||||
}
|
|
||||||
...
|
|
||||||
ws.handshake_ex("localhost", "/", &decorate);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The HTTP Upgrade request produced by the previous call will look thusly:
|
|
||||||
|
|
||||||
[table Decorated WebSocket Upgrade HTTP Request
|
|
||||||
[[Serialized Octets][Description]]
|
|
||||||
[[
|
|
||||||
```
|
|
||||||
GET / HTTP/1.1
|
|
||||||
Host: localhost
|
|
||||||
Upgrade: websocket
|
|
||||||
Connection: upgrade
|
|
||||||
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
|
|
||||||
Sec-WebSocket-Version: 13
|
|
||||||
Sec-WebSocket-Protocol: xmpp;ws-chat
|
|
||||||
User-Agent: Beast
|
|
||||||
```
|
|
||||||
][
|
|
||||||
Undefined behavior results if the decorator modifies the fields
|
|
||||||
specific to perform the WebSocket Upgrade , such as the Upgrade
|
|
||||||
and Connection fields.
|
|
||||||
]]]
|
|
||||||
|
|
||||||
[heading Filtering]
|
|
||||||
|
|
||||||
When a client receives an HTTP Upgrade response from the server indicating
|
|
||||||
a successful upgrade, the caller may wish to perform additional validation
|
|
||||||
on the received HTTP response message. For example, to check that the
|
|
||||||
response to a basic authentication challenge is valid. To achieve this,
|
|
||||||
overloads of the handshake member function allow the caller to store the
|
|
||||||
received HTTP message in an output reference argument as
|
|
||||||
[link beast.ref.websocket__response_type `response_type`]
|
|
||||||
as follows:
|
|
||||||
```
|
|
||||||
beast::websocket::response_type res;
|
|
||||||
ws.handshake(res, "localhost", "/");
|
|
||||||
if(! res.fields.exists("Sec-WebSocket-Protocol"))
|
|
||||||
throw std::invalid_argument("missing subprotocols");
|
|
||||||
```
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:accepting Accepting]
|
|
||||||
|
|
||||||
A [link beast.ref.websocket__stream `stream`] automatically
|
|
||||||
handles receiving and processing the HTTP response to the handshake request.
|
|
||||||
The call to handshake is successful if a HTTP response is received with the
|
|
||||||
101 "Switching Protocols" status code. On failure, an error is returned or an
|
|
||||||
exception is thrown. Depending on the keep alive setting, the socket may remain
|
|
||||||
open for a subsequent handshake attempt
|
|
||||||
|
|
||||||
Performing a handshake for an incoming websocket upgrade request operates
|
|
||||||
similarly. If the handshake fails, an error is returned or exception thrown:
|
|
||||||
```
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
|
|
||||||
...
|
|
||||||
ws.accept();
|
|
||||||
```
|
|
||||||
|
|
||||||
Successful WebSocket Upgrade responses generated by the implementation will
|
|
||||||
typically look like this:
|
|
||||||
|
|
||||||
[table Decorated WebSocket Upgrade HTTP Request
|
|
||||||
[[Serialized Octets][Description]]
|
|
||||||
[[
|
|
||||||
```
|
|
||||||
HTTP/1.1 101 Switching Protocols
|
|
||||||
Upgrade: websocket
|
|
||||||
Connection: upgrade
|
|
||||||
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
Server: Beast/40
|
|
||||||
```
|
|
||||||
][
|
|
||||||
The Sec-WebSocket-Accept field value is generated from the
|
|
||||||
request in a fashion specified by the WebSocket protocol.
|
|
||||||
]]]
|
|
||||||
|
|
||||||
[heading Decorators]
|
|
||||||
|
|
||||||
If the caller wishes to add or modify fields, the member functions
|
|
||||||
[link beast.ref.websocket__stream.accept_ex `accept_ex`] and
|
|
||||||
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
|
||||||
are provided which allow an additional function object, called a
|
|
||||||
['decorator], to be passed. The decorator is invoked to modify
|
|
||||||
the HTTP Upgrade request as needed. This example sets the Server
|
|
||||||
field on the response:
|
|
||||||
```
|
|
||||||
ws.accept_ex(
|
|
||||||
[](beast::websocket::response_type& res)
|
|
||||||
{
|
|
||||||
res.fields.insert("Server", "AcmeServer");
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The HTTP Upgrade response produced by the previous call will look thusly:
|
|
||||||
|
|
||||||
[table Decorated WebSocket Upgrade HTTP Request
|
|
||||||
[[Serialized Octets][Description]]
|
|
||||||
[[
|
|
||||||
```
|
|
||||||
HTTP/1.1 101 Switching Protocols
|
|
||||||
Upgrade: websocket
|
|
||||||
Connection: upgrade
|
|
||||||
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
Server: AcmeServer
|
|
||||||
```
|
|
||||||
][
|
|
||||||
When the Upgrade request fails, the implementation will still invoke
|
|
||||||
the decorator to modify the response. In this case, the response
|
|
||||||
object will have a status code other than 101.
|
|
||||||
|
|
||||||
Undefined behavior results when the upgrade request is successful
|
|
||||||
and the decorator modifies the fields specific to perform the
|
|
||||||
WebSocket Upgrade , such as the Upgrade and Connection fields.
|
|
||||||
]]]
|
|
||||||
|
|
||||||
[heading Passing HTTP Requests]
|
|
||||||
|
|
||||||
When implementing an HTTP server that also supports WebSocket, the
|
|
||||||
server usually reads the HTTP request from the client. To detect when
|
|
||||||
the incoming HTTP request is a WebSocket Upgrade request, the function
|
|
||||||
[link beast.ref.websocket__is_upgrade `is_upgrade`] may be used.
|
|
||||||
|
|
||||||
Once the caller determines that the HTTP request is a WebSocket Upgrade,
|
|
||||||
additional overloads of
|
|
||||||
[link beast.ref.websocket__stream.accept `accept`],
|
|
||||||
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
|
|
||||||
[link beast.ref.websocket__stream.async_accept `async_accept`], and
|
|
||||||
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
|
||||||
are provided which receive the entire HTTP request header as an object
|
|
||||||
to perform the handshake. In this example, the request is first read
|
|
||||||
in using the HTTP algorithms, and then passed to a newly constructed
|
|
||||||
stream:
|
|
||||||
```
|
|
||||||
void handle_connection(boost::asio::ip::tcp::socket& sock)
|
|
||||||
{
|
|
||||||
beast::flat_buffer buffer;
|
|
||||||
beast::http::request<beast::http::string_body> req;
|
|
||||||
beast::http::read(sock, buffer, req);
|
|
||||||
if(beast::websocket::is_upgrade(req))
|
|
||||||
{
|
|
||||||
beast::websocket::stream<decltype(sock)> ws{std::move(sock)};
|
|
||||||
ws.accept(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[heading Buffered Handshakes]
|
|
||||||
|
|
||||||
Sometimes a server implementation wishes to read octets on the stream
|
|
||||||
in order to route the incoming request. For example, a server may read
|
|
||||||
the first 6 octets after accepting an incoming connection to determine
|
|
||||||
if a TLS protocol is being negotiated, and choose a suitable implementation
|
|
||||||
at run-time. In the case where the server wishes to accept the incoming
|
|
||||||
request as an HTTP WebSocket Upgrade request, additional overloads of
|
|
||||||
[link beast.ref.websocket__stream.accept `accept`],
|
|
||||||
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
|
|
||||||
[link beast.ref.websocket__stream.async_accept `async_accept`], and
|
|
||||||
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
|
|
||||||
are provided which receive the additional buffered octects and consume
|
|
||||||
them as part of the handshake.
|
|
||||||
|
|
||||||
In this example, the server reads the initial HTTP message into the
|
|
||||||
specified dynamic buffer as an octet sequence in the buffer's output
|
|
||||||
area, and later uses those octets to attempt an HTTP WebSocket Upgrade:
|
|
||||||
```
|
|
||||||
void do_accept(boost::asio::ip::tcp::socket& sock)
|
|
||||||
{
|
|
||||||
boost::asio::streambuf sb;
|
|
||||||
boost::asio::read_until(sock, sb, "\r\n\r\n");
|
|
||||||
...
|
|
||||||
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
|
|
||||||
ws.accept(sb.data());
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:messages Messages]
|
|
||||||
|
|
||||||
After the WebSocket handshake is accomplished, callers may send and receive
|
|
||||||
messages using the message oriented interface. This interface requires that
|
|
||||||
all of the buffers representing the message are known ahead of time:
|
|
||||||
```
|
|
||||||
void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
|
|
||||||
{
|
|
||||||
beast::multi_buffer b;
|
|
||||||
beast::websocket::opcode::value op;
|
|
||||||
ws.read(op, b);
|
|
||||||
|
|
||||||
ws.set_option(beast::websocket::message_type{op});
|
|
||||||
ws.write(b.data());
|
|
||||||
sb.consume(b.size());
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[important
|
|
||||||
Calls to [link beast.ref.websocket__stream.set_option `set_option`]
|
|
||||||
must be made from the same implicit or explicit strand as that used
|
|
||||||
to perform other operations.
|
|
||||||
]
|
|
||||||
|
|
||||||
[heading Frames]
|
|
||||||
|
|
||||||
Some use-cases make it impractical or impossible to buffer the entire
|
|
||||||
message ahead of time:
|
|
||||||
|
|
||||||
* Streaming multimedia to an endpoint.
|
|
||||||
* Sending a message that does not fit in memory at once.
|
|
||||||
* Providing incremental results as they become available.
|
|
||||||
|
|
||||||
For these cases, the frame oriented interface may be used. This
|
|
||||||
example reads and echoes a complete message using this interface:
|
|
||||||
```
|
|
||||||
void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
|
|
||||||
{
|
|
||||||
beast::multi_buffer b;
|
|
||||||
beast::websocket::frame_info fi;
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
ws.read_frame(fi, b);
|
|
||||||
if(fi.fin)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ws.set_option(beast::websocket::message_type{fi.op});
|
|
||||||
beast::consuming_buffers<
|
|
||||||
beast::multi_buffer::const_buffers_type> cb{b.data()};
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
using boost::asio::buffer_size;
|
|
||||||
std::size_t size = std::min(buffer_size(cb));
|
|
||||||
if(size > 512)
|
|
||||||
{
|
|
||||||
ws.write_frame(false, beast::prepare_buffers(512, cb));
|
|
||||||
cb.consume(512);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ws.write_frame(true, cb);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:control Control Frames]
|
|
||||||
|
|
||||||
Control frames are small (less than 128 bytes) messages entirely contained
|
|
||||||
in an individual WebSocket frame. They may be sent at any time by either
|
|
||||||
peer on an established connection, and can appear in between continuation
|
|
||||||
frames for a message. There are three types of control frames: ping, pong,
|
|
||||||
and close.
|
|
||||||
|
|
||||||
A sent ping indicates a request that the sender wants to receive a pong. A
|
|
||||||
pong is a response to a ping. Pongs may be sent unsolicited, at any time.
|
|
||||||
One use for an unsolicited pong is to inform the remote peer that the
|
|
||||||
session is still active after a long period of inactivity. A close frame
|
|
||||||
indicates that the remote peer wishes to close the WebSocket connection.
|
|
||||||
The connection is considered gracefully closed when each side has sent
|
|
||||||
and received a close frame.
|
|
||||||
|
|
||||||
During read operations, Beast automatically reads and processes control
|
|
||||||
frames. Pings are replied to as soon as possible with a pong, received
|
|
||||||
ping and pongs are delivered to the ping callback. The receipt of a close
|
|
||||||
frame initiates the WebSocket close procedure, eventually resulting in the
|
|
||||||
error code [link beast.ref.websocket__error `error::closed`] being delivered
|
|
||||||
to the caller in a subsequent read operation, assuming no other error
|
|
||||||
takes place.
|
|
||||||
|
|
||||||
A consequence of this automatic behavior is that caller-initiated read
|
|
||||||
operations can cause socket writes. However, these writes will not
|
|
||||||
compete with caller-initiated write operations. For the purposes of
|
|
||||||
correctness with respect to the stream invariants, caller-initiated
|
|
||||||
read operations still only count as a read. This means that callers can
|
|
||||||
have a simultaneously active read, write, and ping operation in progress,
|
|
||||||
while the implementation also automatically handles control frames.
|
|
||||||
|
|
||||||
[heading Ping and Pong Frames]
|
|
||||||
|
|
||||||
Ping and pong messages are control frames which may be sent at any time
|
|
||||||
by either peer on an established WebSocket connection. They are sent
|
|
||||||
using the functions
|
|
||||||
[link beast.ref.websocket__stream.ping `ping`] and
|
|
||||||
[link beast.ref.websocket__stream.pong `pong`].
|
|
||||||
|
|
||||||
To be notified of ping and pong control frames, callers may register a
|
|
||||||
"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`].
|
|
||||||
The object provided with this option should be callable with the following
|
|
||||||
signature:
|
|
||||||
```
|
|
||||||
void on_ping(bool is_pong, ping_data const& payload);
|
|
||||||
...
|
|
||||||
ws.set_option(ping_callback{&on_ping});
|
|
||||||
```
|
|
||||||
|
|
||||||
When a ping callback is registered, all pings and pongs received through
|
|
||||||
either synchronous read functions or asynchronous read functions will
|
|
||||||
invoke the ping callback, with the value of `is_pong` set to `true` if a
|
|
||||||
pong was received else `false` if a ping was received. The payload of
|
|
||||||
the ping or pong control frame is passed in the payload argument.
|
|
||||||
|
|
||||||
Unlike regular completion handlers used in calls to asynchronous initiation
|
|
||||||
functions, the ping callback only needs to be set once. The callback is not
|
|
||||||
reset when a ping or pong is received. The same callback is used for both
|
|
||||||
synchronous and asynchronous reads. The ping callback is passive; in order
|
|
||||||
to receive pings and pongs, a synchronous or asynchronous stream read
|
|
||||||
function must be active.
|
|
||||||
|
|
||||||
[note
|
|
||||||
When an asynchronous read function receives a ping or pong, the
|
|
||||||
ping callback is invoked in the same manner as that used to invoke
|
|
||||||
the final completion handler of the corresponding read function.
|
|
||||||
]
|
|
||||||
|
|
||||||
[heading Close Frames]
|
|
||||||
|
|
||||||
The WebSocket protocol defines a procedure and control message for initiating
|
|
||||||
a close of the session. Handling of close initiated by the remote end of the
|
|
||||||
connection is performed automatically. To manually initiate a close, use
|
|
||||||
the [link beast.ref.websocket__stream.close `close`] function:
|
|
||||||
```
|
|
||||||
ws.close();
|
|
||||||
```
|
|
||||||
|
|
||||||
When the remote peer initiates a close by sending a close frame, Beast
|
|
||||||
will handle it for you by causing the next read to return `error::closed`.
|
|
||||||
When this error code is delivered, it indicates to the application that
|
|
||||||
the WebSocket connection has been closed cleanly, and that the TCP/IP
|
|
||||||
connection has been closed. After initiating a close, it is necessary to
|
|
||||||
continue reading messages until receiving the error `error::closed`. This
|
|
||||||
is because the remote peer may still be sending message and control frames
|
|
||||||
before it receives and responds to the close frame.
|
|
||||||
|
|
||||||
[important
|
|
||||||
To receive the [link beast.ref.websocket__error `error::closed`]
|
|
||||||
error, a read operation is required.
|
|
||||||
]
|
|
||||||
|
|
||||||
[heading Auto-fragment]
|
|
||||||
|
|
||||||
To ensure timely delivery of control frames, large messages can be broken up
|
|
||||||
into smaller sized frames. The automatic fragment option turns on this
|
|
||||||
feature, and the write buffer size option determines the maximum size of
|
|
||||||
the fragments:
|
|
||||||
```
|
|
||||||
...
|
|
||||||
ws.set_option(beast::websocket::auto_fragment{true});
|
|
||||||
ws.set_option(beast::websocket::write_buffer_size{16384});
|
|
||||||
```
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[section:notes Notes]
|
|
||||||
|
|
||||||
Because calls to read data may return a variable amount of bytes, the
|
|
||||||
interface to calls that read data require an object that meets the requirements
|
|
||||||
of __DynamicBuffer__. This concept is modeled on
|
|
||||||
[@http://www.boost.org/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`].
|
|
||||||
|
|
||||||
The implementation does not perform queueing or buffering of messages. If
|
|
||||||
desired, these features should be provided by callers. The impact of this
|
|
||||||
design is that library users are in full control of the allocation strategy
|
|
||||||
used to store data and the back-pressure applied on the read and write side
|
|
||||||
of the underlying TCP/IP connection.
|
|
||||||
|
|
||||||
[heading Asynchronous Operations]
|
|
||||||
|
|
||||||
Asynchronous versions are available for all functions:
|
|
||||||
```
|
|
||||||
websocket::opcode op;
|
|
||||||
ws.async_read(op, sb,
|
|
||||||
[](boost::system::error_code const& ec)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Calls to asynchronous initiation functions support the extensible asynchronous
|
|
||||||
model developed by the Boost.Asio author, allowing for traditional completion
|
|
||||||
handlers, stackful or stackless coroutines, and even futures:
|
|
||||||
```
|
|
||||||
void echo(websocket::stream<ip::tcp::socket>& ws,
|
|
||||||
boost::asio::yield_context yield)
|
|
||||||
{
|
|
||||||
ws.async_read(sb, yield);
|
|
||||||
std::future<websocket::error_code> fut =
|
|
||||||
ws.async_write, sb.data(), boost::use_future);
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[heading The io_service]
|
|
||||||
|
|
||||||
The creation and operation of the
|
|
||||||
[@http://www.boost.org/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`]
|
|
||||||
associated with the underlying stream is left to the callers, permitting any
|
|
||||||
implementation strategy including one that does not require threads for
|
|
||||||
environments where threads are unavailable. Beast WebSocket itself does not
|
|
||||||
use or require threads.
|
|
||||||
|
|
||||||
[heading Thread Safety]
|
|
||||||
|
|
||||||
Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is
|
|
||||||
not thread safe. Callers are responsible for synchronizing operations on the
|
|
||||||
socket using an implicit or explicit strand, as per the Asio documentation.
|
|
||||||
The asynchronous interface supports one active read and one active write
|
|
||||||
simultaneously. Undefined behavior results if two or more reads or two or
|
|
||||||
more writes are attempted concurrently. Caller initiated WebSocket ping, pong,
|
|
||||||
and close operations each count as an active write.
|
|
||||||
|
|
||||||
The implementation uses composed asynchronous operations internally; a high
|
|
||||||
level read can cause both reads and writes to take place on the underlying
|
|
||||||
stream. This behavior is transparent to callers.
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[endsect]
|
|
||||||
|
|
||||||
[include quickref.xml]
|
|
@@ -12,7 +12,7 @@
|
|||||||
<member><link linkend="beast.design.http_message">HTTP Message Container</link></member>
|
<member><link linkend="beast.design.http_message">HTTP Message Container</link></member>
|
||||||
<member><link linkend="beast.design.http_comparison">HTTP Comparison to Other Libraries</link></member>
|
<member><link linkend="beast.design.http_comparison">HTTP Comparison to Other Libraries</link></member>
|
||||||
<member><link linkend="beast.design.websocket_zaphoyd">Comparison to Zaphoyd Studios WebSocket++</link></member>
|
<member><link linkend="beast.design.websocket_zaphoyd">Comparison to Zaphoyd Studios WebSocket++</link></member>
|
||||||
<member><link linkend="beast.design.review">Boost Formal Review FAQ</link></member>
|
<member><link linkend="beast.design.faq">FAQ</link></member>
|
||||||
</simplelist></entry></row></tbody></tgroup></informaltable>
|
</simplelist></entry></row></tbody></tgroup></informaltable>
|
||||||
''']
|
''']
|
||||||
|
|
||||||
@@ -62,6 +62,6 @@ start. Other design goals:
|
|||||||
[include 6_1_http_message.qbk]
|
[include 6_1_http_message.qbk]
|
||||||
[include 6_2_http_comparison.qbk]
|
[include 6_2_http_comparison.qbk]
|
||||||
[include 6_3_websocket_zaphoyd.qbk]
|
[include 6_3_websocket_zaphoyd.qbk]
|
||||||
[include 6_4_review.qbk]
|
[include 6_4_faq.qbk]
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
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:review Boost Formal Review FAQ]
|
[section:faq Boost Formal Review FAQ]
|
||||||
|
|
||||||
To set realistic expectations and prevent a litany of duplicate review
|
To set realistic expectations and prevent a litany of duplicate review
|
||||||
statements, these notes address the most common questions and comments
|
statements, these notes address the most common questions and comments
|
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
@@ -54,14 +54,14 @@ invoke library algorithms it is necessary to first have a connected socket,
|
|||||||
SSL stream, or other object which meets the required stream concepts. This
|
SSL stream, or other object which meets the required stream concepts. This
|
||||||
example is provided as a reminder of how to work with sockets:
|
example is provided as a reminder of how to work with sockets:
|
||||||
```
|
```
|
||||||
auto host = "www.example.com";
|
auto host = "www.example.com";
|
||||||
boost::asio::ip::tcp::resolver r{ios};
|
boost::asio::ip::tcp::resolver r{ios};
|
||||||
boost::asio::ip::tcp::socket sock{ios};
|
boost::asio::ip::tcp::socket sock{ios};
|
||||||
boost::asio::connect(sock, r.resolve(
|
boost::asio::connect(sock, r.resolve(
|
||||||
boost::asio::ip::tcp::resolver::query{host, "http"}));
|
boost::asio::ip::tcp::resolver::query{host, "http"}));
|
||||||
|
|
||||||
// At this point `sock` is a connected to a remote
|
// At this point `sock` is a connected to a remote
|
||||||
// host and may be used to perform stream operations.
|
// host and may be used to perform stream operations.
|
||||||
```
|
```
|
||||||
|
|
||||||
Throughout this documentation identifiers with the following names have
|
Throughout this documentation identifiers with the following names have
|
||||||
@@ -94,9 +94,6 @@ special meaning:
|
|||||||
]]
|
]]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[heading:streams Stream Concepts]
|
[heading:streams Stream Concepts]
|
||||||
|
|
||||||
A __Stream__ is communication channel where data expressed as octet
|
A __Stream__ is communication channel where data expressed as octet
|
@@ -36,6 +36,36 @@ namespace beast {
|
|||||||
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.
|
||||||
|
|
||||||
|
@par Example
|
||||||
|
|
||||||
|
The example shows how to define an asynchronous initiation function
|
||||||
|
whose completion handler receives an error code:
|
||||||
|
|
||||||
|
@code
|
||||||
|
template<
|
||||||
|
class AsyncStream, // A stream supporting asynchronous read and write
|
||||||
|
class Handler // The handler to call with signature void(error_code)
|
||||||
|
>
|
||||||
|
async_return_type< // This provides the return type customization
|
||||||
|
Handler, void(error_code)>
|
||||||
|
do_async(
|
||||||
|
AsyncStream& stream, // The stream to work on
|
||||||
|
Handler&& handler) // Could be an rvalue or const reference
|
||||||
|
{
|
||||||
|
// Make sure we have an async stream
|
||||||
|
static_assert(is_async_stream<AsyncWriteStream>::value,
|
||||||
|
"AsyncStream requirements not met");
|
||||||
|
|
||||||
|
// This helper converts the handler into the real handler type
|
||||||
|
async_completion<WriteHandler, void(error_code)> init{handler};
|
||||||
|
|
||||||
|
... // Create and the composed operation
|
||||||
|
|
||||||
|
// This provides the return value and executor customization
|
||||||
|
return init.result.get();
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
@see @ref async_completion, @ref async_return_type, @ref handler_type
|
@see @ref async_completion, @ref async_return_type, @ref handler_type
|
||||||
*/
|
*/
|
||||||
template<class CompletionToken, class Signature>
|
template<class CompletionToken, class Signature>
|
||||||
|
@@ -28,6 +28,10 @@ namespace beast {
|
|||||||
construction. Attempts to exceed the buffer size will throw
|
construction. Attempts to exceed the buffer size will throw
|
||||||
`std::length_error`.
|
`std::length_error`.
|
||||||
|
|
||||||
|
Upon construction, a maximum size for the buffer may be
|
||||||
|
specified. If this limit is exceeded, the `std::length_error`
|
||||||
|
exception will be thrown.
|
||||||
|
|
||||||
@note This class is designed for use with algorithms that
|
@note This class is designed for use with algorithms that
|
||||||
take dynamic buffers as parameters, and are optimized
|
take dynamic buffers as parameters, and are optimized
|
||||||
for the case where the input sequence or output sequence
|
for the case where the input sequence or output sequence
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
namespace beast {
|
namespace beast {
|
||||||
|
|
||||||
/** A string with a fixed-size storage area.
|
/** A modifiable string with a fixed-size storage area.
|
||||||
|
|
||||||
These objects behave like `std::string` except that the storage
|
These objects behave like `std::string` except that the storage
|
||||||
is not dynamically allocated but rather fixed in size.
|
is not dynamically allocated but rather fixed in size.
|
||||||
|
@@ -90,10 +90,11 @@ struct no_chunk_decorator
|
|||||||
//
|
//
|
||||||
struct decorator
|
struct decorator
|
||||||
{
|
{
|
||||||
// Returns the chunk-extension for each chunk.
|
// Returns the chunk-extension for each chunk,
|
||||||
// The buffer returned must include a trailing "\r\n",
|
// or an empty string for no chunk extension. The
|
||||||
// and the leading semicolon (";") if one or more
|
// buffer must include the leading semicolon (";")
|
||||||
// chunk extensions are specified.
|
// and follow the format for chunk extensions defined
|
||||||
|
// in rfc7230.
|
||||||
//
|
//
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
string_view
|
string_view
|
||||||
@@ -130,6 +131,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
static_assert(is_body<Body>::value,
|
static_assert(is_body<Body>::value,
|
||||||
"Body requirements not met");
|
"Body requirements not met");
|
||||||
|
|
||||||
static_assert(is_body_reader<Body>::value,
|
static_assert(is_body_reader<Body>::value,
|
||||||
"BodyReader requirements not met");
|
"BodyReader requirements not met");
|
||||||
|
|
||||||
|
@@ -318,8 +318,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -346,8 +345,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -384,8 +382,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -412,8 +409,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -451,8 +447,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -489,8 +484,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -538,8 +532,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -576,8 +569,7 @@ public:
|
|||||||
Upgrade request and send the HTTP response. The call blocks
|
Upgrade request and send the HTTP response. The call blocks
|
||||||
until one of the following conditions is true:
|
until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -626,7 +618,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -658,7 +650,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -700,7 +692,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -733,7 +725,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -776,7 +768,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -814,7 +806,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -863,7 +855,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -901,7 +893,7 @@ public:
|
|||||||
to an HTTP request possibly containing a WebSocket Upgrade.
|
to an HTTP request possibly containing a WebSocket Upgrade.
|
||||||
The call blocks until one of the following conditions is true:
|
The call blocks until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -952,8 +944,7 @@ public:
|
|||||||
always returns immediately. The asynchronous operation will
|
always returns immediately. The asynchronous operation will
|
||||||
continue until one of the following conditions is true:
|
continue until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1002,8 +993,7 @@ public:
|
|||||||
always returns immediately. The asynchronous operation will
|
always returns immediately. The asynchronous operation will
|
||||||
continue until one of the following conditions is true:
|
continue until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1062,8 +1052,7 @@ public:
|
|||||||
always returns immediately. The asynchronous operation will
|
always returns immediately. The asynchronous operation will
|
||||||
continue until one of the following conditions is true:
|
continue until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1121,8 +1110,7 @@ public:
|
|||||||
always returns immediately. The asynchronous operation will
|
always returns immediately. The asynchronous operation will
|
||||||
continue until one of the following conditions is true:
|
continue until one of the following conditions is true:
|
||||||
|
|
||||||
@li The HTTP request finishes receiving, and the HTTP response
|
@li The request is received and the response finishes sending.
|
||||||
finishes sending.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1192,7 +1180,7 @@ public:
|
|||||||
asynchronous operation will continue until one of the following
|
asynchronous operation will continue until one of the following
|
||||||
conditions is true:
|
conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1247,7 +1235,7 @@ public:
|
|||||||
asynchronous operation will continue until one of the following
|
asynchronous operation will continue until one of the following
|
||||||
conditions is true:
|
conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1313,7 +1301,7 @@ public:
|
|||||||
asynchronous operation will continue until one of the following
|
asynchronous operation will continue until one of the following
|
||||||
conditions is true:
|
conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1377,7 +1365,7 @@ public:
|
|||||||
asynchronous operation will continue until one of the following
|
asynchronous operation will continue until one of the following
|
||||||
conditions is true:
|
conditions is true:
|
||||||
|
|
||||||
@li The HTTP response finishes sending.
|
@li The response finishes sending.
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream.
|
||||||
|
|
||||||
@@ -1449,8 +1437,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1493,8 +1480,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1542,8 +1528,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1601,8 +1586,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1665,8 +1649,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1707,8 +1690,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1755,8 +1737,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1814,8 +1795,7 @@ public:
|
|||||||
upgrade HTTP request. The call blocks until one of the
|
upgrade HTTP request. The call blocks until one of the
|
||||||
following conditions is true:
|
following conditions is true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream
|
@li An error occurs on the stream
|
||||||
|
|
||||||
@@ -1880,10 +1860,9 @@ public:
|
|||||||
operation will continue until one of the following conditions is
|
operation will continue until one of the following conditions is
|
||||||
true:
|
true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls to the
|
This operation is implemented in terms of one or more calls to the
|
||||||
next layer's `async_read_some` and `async_write_some` functions, and
|
next layer's `async_read_some` and `async_write_some` functions, and
|
||||||
@@ -1932,10 +1911,9 @@ public:
|
|||||||
operation will continue until one of the following conditions is
|
operation will continue until one of the following conditions is
|
||||||
true:
|
true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls to the
|
This operation is implemented in terms of one or more calls to the
|
||||||
next layer's `async_read_some` and `async_write_some` functions, and
|
next layer's `async_read_some` and `async_write_some` functions, and
|
||||||
@@ -1989,10 +1967,9 @@ public:
|
|||||||
operation will continue until one of the following conditions is
|
operation will continue until one of the following conditions is
|
||||||
true:
|
true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls to the
|
This operation is implemented in terms of one or more calls to the
|
||||||
next layer's `async_read_some` and `async_write_some` functions, and
|
next layer's `async_read_some` and `async_write_some` functions, and
|
||||||
@@ -2051,10 +2028,9 @@ public:
|
|||||||
operation will continue until one of the following conditions is
|
operation will continue until one of the following conditions is
|
||||||
true:
|
true:
|
||||||
|
|
||||||
@li A HTTP request finishes sending and an HTTP response finishes
|
@li The request is sent and the response is received.
|
||||||
receiving.
|
|
||||||
|
|
||||||
@li An error occurs on the stream.
|
@li An error occurs on the stream
|
||||||
|
|
||||||
This operation is implemented in terms of one or more calls to the
|
This operation is implemented in terms of one or more calls to the
|
||||||
next layer's `async_read_some` and `async_write_some` functions, and
|
next layer's `async_read_some` and `async_write_some` functions, and
|
||||||
|
Reference in New Issue
Block a user