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 581bc57c..91671f52 100644 Binary files a/doc/images/body.png and b/doc/images/body.png differ diff --git a/doc/images/body.psd b/doc/images/body.psd index ab83b5d5..420565d1 100644 Binary files a/doc/images/body.psd and b/doc/images/body.psd differ diff --git a/doc/2_core.qbk b/doc/x2_0_core.qbk similarity index 98% rename from doc/2_core.qbk rename to doc/x2_0_core.qbk index 1c82e07f..2067e932 100644 --- a/doc/2_core.qbk +++ b/doc/x2_0_core.qbk @@ -54,14 +54,14 @@ invoke library algorithms it is necessary to first have a connected socket, SSL stream, or other object which meets the required stream concepts. This 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"})); + 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. + // 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 @@ -94,9 +94,6 @@ special meaning: ]] ] - - - [heading:streams Stream Concepts] A __Stream__ is communication channel where data expressed as octet diff --git a/include/beast/core/async_result.hpp b/include/beast/core/async_result.hpp index 36cc2828..8380e7f8 100644 --- a/include/beast/core/async_result.hpp +++ b/include/beast/core/async_result.hpp @@ -36,6 +36,36 @@ namespace beast { completion token types. The primary template assumes that the @b CompletionToken is the completion handler. + @par Example + + The example shows how to define an asynchronous initiation function + whose completion handler receives an error code: + + @code + template< + class AsyncStream, // A stream supporting asynchronous read and write + class Handler // The handler to call with signature void(error_code) + > + async_return_type< // This provides the return type customization + Handler, void(error_code)> + do_async( + AsyncStream& stream, // The stream to work on + Handler&& handler) // Could be an rvalue or const reference + { + // Make sure we have an async stream + static_assert(is_async_stream::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