From 5c46845208e68364947d4747b1b60267f24f6bb4 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 3 Jun 2017 18:40:28 -0700 Subject: [PATCH] Documentation work --- doc/0_main.qbk | 9 +- doc/1_overview.qbk | 2 +- doc/2_0_core.qbk | 53 +++ doc/2_1_asio.qbk | 61 +++ doc/2_2_streams.qbk | 129 ++++++ doc/2_3_buffers.qbk | 147 ++++++ doc/2_4_async.qbk | 100 ++++ doc/2_5_tutorial.qbk | 268 +++++++++++ doc/3_3_streams.qbk | 60 +-- doc/3_9_custom_body.qbk | 61 ++- doc/4_0_websocket.qbk | 50 ++ doc/4_1_streams.qbk | 78 ++++ doc/4_2_connect.qbk | 54 +++ doc/4_3_client.qbk | 109 +++++ doc/4_4_server.qbk | 144 ++++++ doc/4_5_messages.qbk | 77 ++++ doc/4_6_control.qbk | 116 +++++ doc/4_7_notes.qbk | 69 +++ doc/4_websocket.qbk | 651 --------------------------- doc/6_0_design.qbk | 4 +- doc/{6_4_review.qbk => 6_4_faq.qbk} | 2 +- doc/images/body.png | Bin 4027 -> 4028 bytes doc/images/body.psd | Bin 137148 -> 134065 bytes doc/{2_core.qbk => x2_0_core.qbk} | 17 +- include/beast/core/async_result.hpp | 30 ++ include/beast/core/flat_buffer.hpp | 4 + include/beast/core/static_string.hpp | 2 +- include/beast/http/serializer.hpp | 50 +- include/beast/websocket/stream.hpp | 104 ++--- 29 files changed, 1650 insertions(+), 801 deletions(-) create mode 100644 doc/2_0_core.qbk create mode 100644 doc/2_1_asio.qbk create mode 100644 doc/2_2_streams.qbk create mode 100644 doc/2_3_buffers.qbk create mode 100644 doc/2_4_async.qbk create mode 100644 doc/2_5_tutorial.qbk create mode 100644 doc/4_0_websocket.qbk create mode 100644 doc/4_1_streams.qbk create mode 100644 doc/4_2_connect.qbk create mode 100644 doc/4_3_client.qbk create mode 100644 doc/4_4_server.qbk create mode 100644 doc/4_5_messages.qbk create mode 100644 doc/4_6_control.qbk create mode 100644 doc/4_7_notes.qbk delete mode 100644 doc/4_websocket.qbk rename doc/{6_4_review.qbk => 6_4_faq.qbk} (99%) rename doc/{2_core.qbk => x2_0_core.qbk} (98%) diff --git a/doc/0_main.qbk b/doc/0_main.qbk index d8b727c9..a1425c0a 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -49,6 +49,8 @@ [def __AsyncStream__ [link beast.ref.streams.AsyncStream [*AsyncStream]]] [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 __FieldSequence__ [link beast.ref.FieldSequence [*FieldSequence]]] [def __Stream__ [link beast.ref.streams [*Stream]]] @@ -100,8 +102,7 @@ asynchronous model of __Asio__. [[ [link beast.design Design] ][ - Design rationale, answers to questions, - library comparisons, and Boost Formal Review FAQ. + Rationale, comparison to other libraries, and FAQ. ]] [[ [link beast.ref Reference] @@ -116,9 +117,9 @@ asynchronous model of __Asio__. ] [include 1_overview.qbk] -[include 2_core.qbk] +[include 2_0_core.qbk] [include 3_0_http.qbk] -[include 4_websocket.qbk] +[include 4_0_websocket.qbk] [include 5_examples.qbk] [include 6_0_design.qbk] diff --git a/doc/1_overview.qbk b/doc/1_overview.qbk index a07e9d11..e22768f0 100644 --- a/doc/1_overview.qbk +++ b/doc/1_overview.qbk @@ -122,7 +122,7 @@ and [@https://www.ripple.com Ripple Labs] for supporting its early development. Also thanks to Agustín Bergé, -Glen Fernandes, +[@http://www.boost.org/users/people/glen_fernandes.html Glen Fernandes], and Peter Dimov for helping me considerably on Slack. diff --git a/doc/2_0_core.qbk b/doc/2_0_core.qbk new file mode 100644 index 00000000..cc0a773a --- /dev/null +++ b/doc/2_0_core.qbk @@ -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 ''' + + Working With Asio + Stream Concepts + Buffer Concepts + Asynchronous Utilities + Writing Composed Operations + +'''] + +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 + #include + #include + #include + + 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] diff --git a/doc/2_1_asio.qbk b/doc/2_1_asio.qbk new file mode 100644 index 00000000..78d0b63e --- /dev/null +++ b/doc/2_1_asio.qbk @@ -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`] + 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`] + which is already connected with a remote host. +]] +] diff --git a/doc/2_2_streams.qbk b/doc/2_2_streams.qbk new file mode 100644 index 00000000..9d63e411 --- /dev/null +++ b/doc/2_2_streams.qbk @@ -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 + void write_string(SyncWriteStream& stream, string_view s) + { + static_assert(is_sync_write_stream::value, + "SyncWriteStream requirements not met"); + boost::asio::write(stream, boost::asio::const_buffers_1(s.data(), s.size())); + } +``` diff --git a/doc/2_3_buffers.qbk b/doc/2_3_buffers.qbk new file mode 100644 index 00000000..639d2c8d --- /dev/null +++ b/doc/2_3_buffers.qbk @@ -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. +]] +] diff --git a/doc/2_4_async.qbk b/doc/2_4_async.qbk new file mode 100644 index 00000000..2f2a7974 --- /dev/null +++ b/doc/2_4_async.qbk @@ -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. +]] +] diff --git a/doc/2_5_tutorial.qbk b/doc/2_5_tutorial.qbk new file mode 100644 index 00000000..535470c0 --- /dev/null +++ b/doc/2_5_tutorial.qbk @@ -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 + #include + #include + #include + #include + + // Read a line and echo it back + // + template + beast::async_return_type + 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 echo_op; // This is our composed operation implementation + + // Read a line and echo it back + // + template + beast::async_return_type + 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::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 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>{ + 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 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 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> 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::max)(), + beast::handler_alloc{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 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 + echo_op(AsyncStream& stream, DeducedHandler&& handler) + : p_(std::forward(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 + 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 + void echo_op:: + 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] diff --git a/doc/3_3_streams.qbk b/doc/3_3_streams.qbk index 047d6e32..847e4f48 100644 --- a/doc/3_3_streams.qbk +++ b/doc/3_3_streams.qbk @@ -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, throwing an exception if an error occurs: ``` -template -void send(response const& res) -{ - write(sock, res); -} + template + void send(response const& res) + { + write(sock, res); + } ``` If a response has no declared content length, and no chunked transfer @@ -124,37 +124,37 @@ to the caller that the connection should be closed. This example constructs and sends a response whose body length is determined by the number of octets received prior to the server closing the connection: ``` -void send() -{ - response res; - res.version = 11; - res.status = 200; - res.reason("OK"); - res.fields.insert("Server", "Beast"); - res.body = "Hello, world!"; + void send() + { + response res; + res.version = 11; + res.status = 200; + res.reason("OK"); + res.fields.insert("Server", "Beast"); + res.body = "Hello, world!"; - error_code ec; - write(sock, res, ec); - if(ec == boost::asio::error::eof) - sock.close(); - else - BOOST_ASSERT(ec); -} + error_code ec; + write(sock, res, ec); + if(ec == boost::asio::error::eof) + sock.close(); + else + BOOST_ASSERT(ec); + } ``` An asynchronous version is also available: ``` -template -void send_async(response const& res) -{ - async_write(sock, res, - [&](error_code) - { - if(ec) - std::cerr << ec.message() << std::endl; - }); -} + template + void send_async(response const& res) + { + async_write(sock, res, + [&](error_code) + { + if(ec) + std::cerr << ec.message() << std::endl; + }); + } ``` [endsect] diff --git a/doc/3_9_custom_body.qbk b/doc/3_9_custom_body.qbk index 2b139424..45ee1e10 100644 --- a/doc/3_9_custom_body.qbk +++ b/doc/3_9_custom_body.qbk @@ -8,7 +8,7 @@ [section:custom_body Custom Body Types] 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: [$images/body.png [width 525px] [height 190px]] @@ -21,29 +21,64 @@ The meaning of the nested types is as follows [`value_type`] [ Determines the type of the - [link beast.ref.http__message.body `message::body`] member. If this - type defines default construction, move, copy, or swap, then message objects - declared with this [*Body] will have those operations defined. + [link beast.ref.http__message.body `message::body`] + member. If this type defines default construction, move, copy, + or swap, then message objects declared with this __Body__ will + have those operations defined. ] ][ [`reader`] [ - An optional nested type meeting the requirements of - [link beast.ref.BodyReader [*BodyReader]]. If present, this defines - the algorithm used to obtain buffers representing a body of this type. + An optional nested type meeting the requirements of __BodyReader__. ] ][ [`writer`] [ - An optional nested type meeting the requirements of - [link beast.ref.BodyWriter [*BodyWriter]]. If present, this defines the - algorithm used to transfer parsed octets into buffers representing the - body. + An optional nested type meeting the requirements of __BodyWriter__. ] ] ] -The examples included with this library provide a [*Body] implementation that -serializing message bodies that come from a file. +[heading Value Type] + +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` or even +`std::list`. 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] diff --git a/doc/4_0_websocket.qbk b/doc/4_0_websocket.qbk new file mode 100644 index 00000000..a56560bd --- /dev/null +++ b/doc/4_0_websocket.qbk @@ -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 ''' + + Creating Streams + Establishing Connections + Handshaking (Clients) + Handshaking (Servers) + Send and Receive Messages + Control Frames + Additional Notes + +'''] + +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 + ``` +] + +[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] diff --git a/doc/4_1_streams.qbk b/doc/4_1_streams.qbk new file mode 100644 index 00000000..ac41330c --- /dev/null +++ b/doc/4_1_streams.qbk @@ -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 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 + #include + + boost::asio::io_service ios; + boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; + beast::websocket::stream ws{ios, ctx}; +``` + +[note + Code which declares stream objects using Asio SSL types must + toinclude the file ``. +] + +[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 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 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> 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] diff --git a/doc/4_2_connect.qbk b/doc/4_2_connect.qbk new file mode 100644 index 00000000..f5c97592 --- /dev/null +++ b/doc/4_2_connect.qbk @@ -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 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 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> 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] diff --git a/doc/4_3_client.qbk b/doc/4_3_client.qbk new file mode 100644 index 00000000..2f69bb86 --- /dev/null +++ b/doc/4_3_client.qbk @@ -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] diff --git a/doc/4_4_server.qbk b/doc/4_4_server.qbk new file mode 100644 index 00000000..9928f73a --- /dev/null +++ b/doc/4_4_server.qbk @@ -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 req; + http::read(sock, buffer, req); + if(websocket::is_upgrade(req)) + { + websocket::stream 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 ws{sock}; + ws.accept(sb.data()); + ... +} +``` + +[endsect] diff --git a/doc/4_5_messages.qbk b/doc/4_5_messages.qbk new file mode 100644 index 00000000..801aa3a9 --- /dev/null +++ b/doc/4_5_messages.qbk @@ -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 + void echo(websocket::stream& 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 + void echo(websocket::stream& 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] diff --git a/doc/4_6_control.qbk b/doc/4_6_control.qbk new file mode 100644 index 00000000..cb09f343 --- /dev/null +++ b/doc/4_6_control.qbk @@ -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] diff --git a/doc/4_7_notes.qbk b/doc/4_7_notes.qbk new file mode 100644 index 00000000..7c567976 --- /dev/null +++ b/doc/4_7_notes.qbk @@ -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& ws, + boost::asio::yield_context yield) + { + ws.async_read(sb, yield); + std::future 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] diff --git a/doc/4_websocket.qbk b/doc/4_websocket.qbk deleted file mode 100644 index f28eb553..00000000 --- a/doc/4_websocket.qbk +++ /dev/null @@ -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 ''' - - Creation - Making connections - Handshaking - Accepting - Messages - Control Frames - Notes - -'''] - -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 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 -#include -#include - -boost::asio::io_service ios; -boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; -beast::websocket::stream ws{ios, ctx}; -``` - -[note - When creating websocket stream objects using SSL, it is necessary - to include the file ``. -] - -[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 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 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> 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 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 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> 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 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 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 req; - beast::http::read(sock, buffer, req); - if(beast::websocket::is_upgrade(req)) - { - beast::websocket::stream 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 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& 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& 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& ws, - boost::asio::yield_context yield) -{ - ws.async_read(sb, yield); - std::future 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] diff --git a/doc/6_0_design.qbk b/doc/6_0_design.qbk index 619bb31c..bc34d674 100644 --- a/doc/6_0_design.qbk +++ b/doc/6_0_design.qbk @@ -12,7 +12,7 @@ HTTP Message Container HTTP Comparison to Other Libraries Comparison to Zaphoyd Studios WebSocket++ - Boost Formal Review FAQ + FAQ '''] @@ -62,6 +62,6 @@ start. Other design goals: [include 6_1_http_message.qbk] [include 6_2_http_comparison.qbk] [include 6_3_websocket_zaphoyd.qbk] -[include 6_4_review.qbk] +[include 6_4_faq.qbk] [endsect] diff --git a/doc/6_4_review.qbk b/doc/6_4_faq.qbk similarity index 99% rename from doc/6_4_review.qbk rename to doc/6_4_faq.qbk index 2369625f..d768a0ff 100644 --- a/doc/6_4_review.qbk +++ b/doc/6_4_faq.qbk @@ -5,7 +5,7 @@ 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 statements, these notes address the most common questions and comments diff --git a/doc/images/body.png b/doc/images/body.png index 581bc57cab2edbbb9ba31baac14b786dc59abac1..91671f52afcb17f38e13898c247f726f538e97c8 100644 GIT binary patch delta 3039 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 II;Vst0KZa}mjD0& delta 3012 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 diff --git a/doc/images/body.psd b/doc/images/body.psd index ab83b5d5f7e2ba259e902ce9f028bc6a81ea1500..420565d17b2df839ccb302740f58b6cb7ae88e86 100644 GIT binary patch delta 24921 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 W)*oz*Y>jOH*brUxMg|c4!T + 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::value, + "AsyncStream requirements not met"); + + // This helper converts the handler into the real handler type + async_completion 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 */ template diff --git a/include/beast/core/flat_buffer.hpp b/include/beast/core/flat_buffer.hpp index bbb5e78d..a592ea1f 100644 --- a/include/beast/core/flat_buffer.hpp +++ b/include/beast/core/flat_buffer.hpp @@ -28,6 +28,10 @@ namespace beast { construction. Attempts to exceed the buffer size will throw `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 take dynamic buffers as parameters, and are optimized for the case where the input sequence or output sequence diff --git a/include/beast/core/static_string.hpp b/include/beast/core/static_string.hpp index 2a189864..8574fe03 100644 --- a/include/beast/core/static_string.hpp +++ b/include/beast/core/static_string.hpp @@ -22,7 +22,7 @@ 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 is not dynamically allocated but rather fixed in size. diff --git a/include/beast/http/serializer.hpp b/include/beast/http/serializer.hpp index 4e45c362..c0b53f48 100644 --- a/include/beast/http/serializer.hpp +++ b/include/beast/http/serializer.hpp @@ -81,32 +81,33 @@ struct no_chunk_decorator struct with a templated operator() thusly: @code - // The implementation guarantees that operator() - // will be called only after the view returned by - // any previous calls to operator() are no longer - // needed. The decorator instance is intended to - // manage the lifetime of the storage for all returned - // views. + // The implementation guarantees that operator() + // will be called only after the view returned by + // any previous calls to operator() are no longer + // needed. The decorator instance is intended to + // manage the lifetime of the storage for all returned + // views. + // + struct decorator + { + // Returns the chunk-extension for each chunk, + // or an empty string for no chunk extension. The + // buffer must include the leading semicolon (";") + // and follow the format for chunk extensions defined + // in rfc7230. // - struct decorator - { - // Returns the chunk-extension for each chunk. - // The buffer returned must include a trailing "\r\n", - // and the leading semicolon (";") if one or more - // chunk extensions are specified. - // - template - string_view - operator()(ConstBufferSequence const&) const; + template + string_view + operator()(ConstBufferSequence const&) const; - // Returns a set of field trailers for the final chunk. - // Each field should be formatted according to rfc7230 - // including the trailing "\r\n" for each field. If - // no trailers are indicated, an empty string is returned. - // - string_view - operator()(boost::asio::null_buffers) const; - }; + // Returns a set of field trailers for the final chunk. + // Each field should be formatted according to rfc7230 + // including the trailing "\r\n" for each field. If + // no trailers are indicated, an empty string is returned. + // + string_view + operator()(boost::asio::null_buffers) const; + }; @endcode @tparam isRequest `true` if the message is a request. @@ -130,6 +131,7 @@ class serializer { static_assert(is_body::value, "Body requirements not met"); + static_assert(is_body_reader::value, "BodyReader requirements not met"); diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index 98560567..773d5697 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -318,8 +318,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -346,8 +345,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -384,8 +382,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -412,8 +409,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -451,8 +447,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -489,8 +484,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -538,8 +532,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -576,8 +569,7 @@ public: Upgrade request and send the HTTP response. The call blocks until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -626,7 +618,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -658,7 +650,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -700,7 +692,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -733,7 +725,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -776,7 +768,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -814,7 +806,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -863,7 +855,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -901,7 +893,7 @@ public: to an HTTP request possibly containing a WebSocket Upgrade. 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. @@ -952,8 +944,7 @@ public: always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -1002,8 +993,7 @@ public: always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -1062,8 +1052,7 @@ public: always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -1121,8 +1110,7 @@ public: always returns immediately. The asynchronous operation will continue until one of the following conditions is true: - @li The HTTP request finishes receiving, and the HTTP response - finishes sending. + @li The request is received and the response finishes sending. @li An error occurs on the stream. @@ -1192,7 +1180,7 @@ public: asynchronous operation will continue 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. @@ -1247,7 +1235,7 @@ public: asynchronous operation will continue 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. @@ -1313,7 +1301,7 @@ public: asynchronous operation will continue 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. @@ -1377,7 +1365,7 @@ public: asynchronous operation will continue 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. @@ -1449,8 +1437,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1493,8 +1480,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1542,8 +1528,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1601,8 +1586,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1665,8 +1649,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1707,8 +1690,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1755,8 +1737,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1814,8 +1795,7 @@ public: upgrade HTTP request. The call blocks until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. @li An error occurs on the stream @@ -1880,10 +1860,9 @@ public: operation will continue until one of the following conditions is true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. - @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 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 true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. - @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 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 true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. - @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 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 true: - @li A HTTP request finishes sending and an HTTP response finishes - receiving. + @li The request is sent and the response is received. - @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 next layer's `async_read_some` and `async_write_some` functions, and