diff --git a/doc/0_main.qbk b/doc/0_main.qbk index 79773f64..a185927e 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -51,6 +51,8 @@ [def __SyncReadStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncReadStream.html [*SyncReadStream]]] [def __SyncWriteStream__ [@http://www.boost.org/doc/html/boost_asio/reference/SyncWriteStream.html [*SyncWriteStream]]] +[def __async_initfn__ [@http://www.boost.org/doc/html/boost_asio/reference/asynchronous_operations.html initiating function]] + [def __AsyncStream__ [link beast.concept.streams.AsyncStream [*AsyncStream]]] [def __Body__ [link beast.concept.Body [*Body]]] [def __BodyReader__ [link beast.concept.BodyReader [*BodyReader]]] diff --git a/doc/3_2_streams.qbk b/doc/3_2_streams.qbk index 13773125..1778dff5 100644 --- a/doc/3_2_streams.qbk +++ b/doc/3_2_streams.qbk @@ -11,9 +11,9 @@ A __Stream__ is a communication channel where data is transferred as an ordered sequence of octet buffers. Streams are either synchronous 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 __socket__ and __ssl_stream__ and support everything. -All stream algorithms in Beast are declared as template functions -with specific concept requirements chosen from this list: +Asio types __socket__ and __ssl_stream__ support both __SyncStream__ +and __AsyncStream__. All stream algorithms in Beast are declared as +template functions using these concepts: [table Stream Concepts [[Concept][Description]] diff --git a/doc/3_3_buffers.qbk b/doc/3_3_buffers.qbk index 6fdc2632..52972591 100644 --- a/doc/3_3_buffers.qbk +++ b/doc/3_3_buffers.qbk @@ -35,7 +35,8 @@ These metafunctions check if types match the buffer concepts: ]] ] -To suit various needs, several implementation of dynamic buffer are available: +Beast provides several dynamic buffer implementations for a variety +of scenarios: [table Dynamic Buffer Implementations [[Name][Description]] @@ -63,7 +64,8 @@ To suit various needs, several implementation of dynamic buffer are available: Guarantees that input and output areas are buffer sequences with 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. + is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. ]] [[ [link beast.ref.beast__multi_buffer `multi_buffer`] @@ -72,7 +74,8 @@ To suit various needs, several implementation of dynamic buffer are available: Uses a sequence of one or more character arrays of varying sizes. 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. + container is an + [@http://en.cppreference.com/w/cpp/concept/AllocatorAwareContainer [*AllocatorAwareContainer]]. ]] [[ [link beast.ref.beast__static_buffer `static_buffer`] @@ -150,7 +153,7 @@ output streams. [link beast.ref.beast__ostream `ostream`] ][ This function returns a `std::ostream` which wraps a dynamic buffer. - Characters sent to the stream using `operator<<` is stored in the + Characters sent to the stream using `operator<<` are stored in the dynamic buffer. ]] ] diff --git a/doc/3_4_composed.qbk b/doc/3_4_composed.qbk index 2e7e70a2..4d3eaa1e 100644 --- a/doc/3_4_composed.qbk +++ b/doc/3_4_composed.qbk @@ -5,40 +5,34 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section Composed Operations] +[section Writing Composed Operations] [block''''''] 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 a type defining how -the caller is informed of the asynchronous operation result. __Asio__ comes -with the special completion tokens __use_future__ and __yield_context__ for -using futures and coroutines respectively. This system of customizing the -return value and method of completion notification is known as the +function known as an asynchronous ['__async_initfn__]. This function accepts +parameters specific to the operation as well as a "completion token." The +token is either a completion handler, or a type defining how the caller is +informed of the asynchronous operation result. __Asio__ comes with the +special tokens __use_future__ and __yield_context__ for using futures +and coroutines respectively. This system of customizing the return value +and method of completion notification is known as the ['Extensible Asynchronous Model] described in __N3747__, and a built in -to __N4588__. +to __N4588__. Here is an example of an initiating function which reads a +line from the stream and echoes it back. This function is developed +further in the next section: -[note - 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. -] +[example_core_echo_op_1] -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 -asynchronous 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 +Authors using Beast can reuse the library's primitives to create their +own initiating functions for performing a series of other, intermediate +asynchronous operations before invoking a final completion handler. +The set of intermediate actions produced by an initiating function is +known as a [@http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html ['composed operation]]. To ensure full interoperability and well-defined behavior, __Asio__ imposes -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: +requirements on the implementation of composed operations. These classes +and functions make it easier to develop initiating functions and their +composed operations: [table Asynchronous Helpers [[Name][Description]] @@ -105,8 +99,8 @@ available: [section Echo] -Here we develop an asynchronous composed operation called [*echo]. -This operation will read up to the first newline on a stream, and +This example develops an initiating function called [*echo]. +The operation will read up to the first newline on a stream, and then write the same line including the newline back on the stream. The implementation performs both reading and writing, and has a non-trivially-copyable state. @@ -115,16 +109,16 @@ initiation function. For our echo operation the only inputs are the stream and the completion token. The output is the error code which is usually included in all completion handler signatures. -[example_core_echo_op_1] +[example_core_echo_op_2] Now that we have a declaration, we will define the body of the function. 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. -[example_core_echo_op_2] +[example_core_echo_op_3] -The initiation function contains a few relatively simple parts. There is +The initiating function contains a few relatively simple parts. There is the customization of the return value type, static type checking, building the return value type using the helper, and creating and launching the composed operation object. The [*`echo_op`] object does most of the work @@ -136,12 +130,11 @@ without explaining them in depth. Here is the boilerplate present in all composed operations written in this style: -[example_core_echo_op_3] +[example_core_echo_op_4] -We have the common boilerplate for a composed operation and now we just need -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 +Next is 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 @@ -150,7 +143,88 @@ care must be taken to ensure that no access to data members are made after the move takes place. Here is the implementation of the function call operator for this echo operation: -[example_core_echo_op_4] +[example_core_echo_op_5] + +This is the most important element of writing a composed operation, and +the part which is often neglected or implemented incorrectly. It is the +declaration and definition of the "handler hooks". There are four hooks: + +[table Handler Hooks +[[Name][Description]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`] +][ + Default invoke function for handlers. This hooking function ensures + that the invoked method used for the final handler is accessible at + each intermediate step. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`] +][ + Default allocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_deallocate.html `asio_handler_deallocate`] +][ + Default deallocation function for handlers. Implement `asio_handler_allocate` + and `asio_handler_deallocate` for your own handlers to provide custom + allocation for temporary objects. +]] +[[ + [@http://www.boost.org/doc/html/boost_asio/reference/asio_handler_is_continuation.html `asio_handler_is_continuation`] +][ + Default continuation function for handlers. Implement + `asio_handler_is_continuation` for your own handlers to indicate when + a handler represents a continuation. +]] +] + +Our composed operation stores the final handler and performs its own +intermediate asynchronous operations. To ensure that I/O objects, in this +case the stream, are accessed safely it is important to use the same method +to invoke intermediate handlers as that used to invoke the final handler. +Similarly, for the memory allocation hooks our composed operation should use +the same hooks as those used by the final handler. And finally for the +`asio_is_continuation` hook, we want to return `true` for any intermediate +asynchronous operations we perform after the first one, since those represent +continuations. For the first asynchronous operation we perform, the hook should +return `true` only if the final handler also represents a continuation. Our +implementation of the hooks will forward the call to the corresponding +overloads of the final handler: + +[example_core_echo_op_6] + +There are some common mistakes that should be avoided when writing +composed operations: + +* Type erasing the final handler. This will cause undefined behavior. + +* Not using `std::addressof` to get the address of the handler. + +* Forgetting to include a return statement after calling an + initiating function. + +* Calling a synchronous function by accident. In general composed + operations should not block for long periods of time, since this + ties up a thread running on the __io_service__. + +* Forgetting to overload `asio_handler_invoke` for the composed + operation. This will cause undefined behavior if someone calls + the initiating function with a strand-wrapped function object, + and there is more than thread running on the `io_service`. + +* For operations which complete immediately (i.e. without calling an + intermediate initiating function), forgetting to use `io_service::post` + to invoke the final handler. This breaks the following initiating + function guarantee: ['Regardless of whether the asynchronous operation + completes immediately or not, the handler will not be invoked from + within this function. Invocation of the handler will be performed + in a manner equivalent to using `boost::asio::io_service::post`]. + The function + [link beast.ref.beast__bind_handler `bind_handler`] + is provided for this purpose. A complete, runnable version of this example may be found in the examples directory. diff --git a/doc/3_5_detect_ssl.qbk b/doc/3_5_detect_ssl.qbk index a803e40f..da6350b8 100644 --- a/doc/3_5_detect_ssl.qbk +++ b/doc/3_5_detect_ssl.qbk @@ -9,7 +9,7 @@ In this example we will build a simple function to detect the presence of the SSL handshake given an input buffer sequence. Then we build on -the example by adding synchronous stream algorithms. Finally, we +the example by adding a synchronous stream algorithm. Finally, we implemement an asynchronous detection function using a composed operation. This SSL detector may be used to allow a server to accept both SSL/TLS and unencrypted connections at the same port. @@ -32,11 +32,11 @@ synchronous version which takes the stream and buffer as input: The synchronous algorithm is the model for building the asynchronous operation which has more boilerplate. First, we declare the asynchronous -initiation function: +initiating function: [example_core_detect_ssl_4] -The implementation of the initiation function is straightforward +The implementation of the initiating function is straightforward and contains mostly boilerplate. It is to construct the return type customization helper to obtain the actual handler, and then create the composed operation and launch it. The actual @@ -51,7 +51,7 @@ is worth the effort. [example_core_detect_ssl_6] -The boilerplate is all done, and now we need to implemnt the function +The boilerplate is all done, and now we need to implement the function call operator that turns this composed operation a completion handler with the signature `void(error_code, std::size_t)` which is exactly the signature needed when performing asynchronous reads. This function diff --git a/doc/5_01_primer.qbk b/doc/5_01_primer.qbk index 26501748..78bd5d05 100644 --- a/doc/5_01_primer.qbk +++ b/doc/5_01_primer.qbk @@ -9,15 +9,19 @@ The HTTP protocol defines the [@https://tools.ietf.org/html/rfc7230#section-2.1 client and server roles]: -clients send requests and servers send back responses. A request -or response is an +clients send requests and servers send back responses. When a client and +server have established a connection, the client sends a series of requests +while the server sends back at least one response for each received request +in the order those requests were received. + +A request or response is an [@https://tools.ietf.org/html/rfc7230#section-3 HTTP message] -(referred to hereafter as "message"), with two parts: -a header containing structured metadata and an optional variable-length -body containing arbitrary data. A serialized header is one or more text -lines where each line ends in a carriage return followed by linefeed -(`"\r\n"`). An empty line marks the end of the header. The first line -in the header is called the ['start-line]. The start line contents are +(referred to hereafter as "message") having two parts: +a header with structured metadata and an optional variable-length body +holding arbitrary data. A serialized header is one or more text lines +where each line ends in a carriage return followed by linefeed (`"\r\n"`). +An empty line marks the end of the header. The first line in the header +is called the ['start-line]. The contents of the start line contents are different for requests and responses. Every message contains a set of zero or more field name/value pairs, @@ -26,11 +30,6 @@ text strings with various requirements. A serialized field contains the field name, then a colon followed by a space (`": "`), and finally the field value with a trailing CRLF. -When a client and server have established a connection and intend to -use HTTP, the client sends a series of requests while the server reads -sends back at least one response for each request in the order those -requests were received. - [heading Requests] Clients send requests, which contain a @@ -90,11 +89,21 @@ field informs the remote host of the size of the body which follows. ]] ] +[heading Body] + +Messages may optionally carry a body. The size of the message body +is determined by the semantics of the message and the special fields +Content-Length and Transfer-Encoding. +[@https://tools.ietf.org/html/rfc7230#section-3.3 rfc7230 section 3.3] +provides a comprehensive description for how the body length is +determined. + [heading Special Fields] Certain fields appearing in messages are special. The library understands these fields when performing serialization and parsing, taking automatic -action as needed when the fields are present: +action as needed when the fields are parsed in a message and also setting +the fields if the caller requests it. [table Special Fields [[Field][Description]] @@ -126,8 +135,8 @@ action as needed when the fields are present: Beast understands the "chunked" coding scheme when it is the last (outermost) applied coding. The library will automatically apply chunked encoding when the content length is not known ahead of time - during serialization, and the library will automatically removed chunked - encoding from parsed messages. + during serialization, and the library will automatically remove chunked + encoding from parsed messages when present. ] ][ [ diff --git a/doc/5_02_message.qbk b/doc/5_02_message.qbk index ab2a22df..a794bd9b 100644 --- a/doc/5_02_message.qbk +++ b/doc/5_02_message.qbk @@ -44,22 +44,21 @@ class header; Requests and responses share the version, fields, and body but have a few members unique to the type. This is implemented by declaring the -header classes as partial specializations of `isRequest`. Furthermore, -__message__ is derived from __header__; a message may be passed as an -argument to a function taking a suitably typed header as a parameter. -Furthermore, `header` is publicly derived from `Fields`; a message -inherits all of the fields member functions. Since `fields` is a -container, iterating a message iterates its header fields. This -diagram shows the inheritance relationship between header and message, -along with the fields from the different partial specializations for -each possible value of `isRequest`: +header classes as partial specializations of `isRequest`. __message__ +is derived from __header__; a message may be passed as an argument to +a function taking a suitably typed header as a parameter. Additionally, +`header` is publicly derived from `Fields`; a message inherits all the +member functions of `Fields`. This diagram shows the inheritance +relationship between header and message, along with some of the +notable differences in members in each partial specialization: [$images/message.png [width 730px] [height 410px]] The template type aliases [link beast.ref.beast__http__request `request`] and [link beast.ref.beast__http__response `response`] -are provided for brevity. They specify the common default of `fields`. +are provided for brevity. They specify __fields__ as the default for [*Fields]. + ``` /// A typical HTTP request @@ -77,9 +76,8 @@ Beast defines the __Body__ concept, which determines both the type of the [link beast.ref.beast__http__message.body `message::body`] member (as seen in the diagram above) and may also include algorithms for transferring buffers in and out. These algorithms are used during -parsing and serialization. These body types are available within the -library, and users may define their own body types which meet the -__Body__ requirements: +parsing and serialization. Users may define their own body types which +meet the requirements, or use the ones that come with the library: [table [[Name][Description]] @@ -130,10 +128,10 @@ __Body__ requirements: [heading Usage] -The code examples below show how to create and fill in request and response -objects: Here is simple example of building an +These examples show how to create and fill in request and response +objects: Here we build an [@https://tools.ietf.org/html/rfc7231#section-4.3.1 HTTP GET] -request. +request with an empty message body: [table Create Request [[Statements] [Serialized Result]] @@ -149,12 +147,12 @@ request. ]] ] -Here we create an HTTP response indicating success. Note that this -message has a body. The function +In this code we create an HTTP response with a status code indicating success. +This message has a body with a non-zero length. The function [link beast.ref.beast__http__message.prepare_payload `message::prepare_payload`] automatically sets the Content-Length or Transfer-Encoding field -depending on the content and type of the `body` member. The use -of prepare is optional; these fields may also be set explicitly. +depending on the content and type of the `body` member. Use of this function +is optional; these fields may also be set explicitly. [table Create Response [[Statements] [Serialized Result]] diff --git a/doc/5_03_streams.qbk b/doc/5_03_streams.qbk index 8209078b..31cd8abc 100644 --- a/doc/5_03_streams.qbk +++ b/doc/5_03_streams.qbk @@ -67,11 +67,11 @@ into a single buffer for parsing. ] Messages may also be read asynchronously. When performing asynchronous -stream read operations, the buffer and message variables must remain -valid until the operation has completed. Beast asynchronous initiation -functions use Asio's completion handler model. Here we read a message -asynchronously. When the operation completes the message in the error -code indicating the result is printed: +stream read operations the stream, buffer, and message variables must +remain valid until the operation has completed. Beast asynchronous +initiation functions use Asio's completion handler model. This call +reads a message asynchronously and report the error code upon +completion: [http_snippet_5] @@ -79,8 +79,8 @@ If a read stream algorithm cannot complete its operation without exceeding the maximum specified size of the dynamic buffer provided, the error [link beast.ref.beast__http__error `buffer_overflow`] is returned. This may be used to impose a limit on the maximum size of an -HTTP message header for protection from buffer overflow attacks. The following -code will generate an error: +HTTP message header for protection from buffer overflow attacks. The +following code will print the error message: [http_snippet_6] @@ -89,9 +89,9 @@ code will generate an error: [heading Writing] A set of free functions allow serialization of an entire HTTP message to -a stream. If a response has no declared content length, and no chunked -transfer encoding, the end of the message is indicated by the server closing -the connection. When sending such a response, Beast will return the +a stream. If a response has no declared content length and no chunked +transfer encoding, then the end of the message is indicated by the server +closing the connection. When sending such a response, Beast will return the [link beast.ref.beast__http__error `error::end_of_stream`] from the write algorithm to indicate to the caller that the connection should be closed. This example diff --git a/doc/9_1_http_message.qbk b/doc/9_1_http_message.qbk index 861cd067..8910493c 100644 --- a/doc/9_1_http_message.qbk +++ b/doc/9_1_http_message.qbk @@ -32,7 +32,7 @@ These relations are true: * `P(S(m)) == m` We would also like our message container to have customization points -permitting the following: full allocator support, user-defined containers +permitting the following: allocator awareness, user-defined containers to represent header fields, and user-defined types and algorithms to represent the body. And finally, because requests and responses have different fields in the ['start-line], we would like the containers for @@ -48,10 +48,11 @@ Here is our first attempt at declaring some message containers: template struct request { - int version; + int version; std::string method; std::string target; - Fields fields; + Fields fields; + typename Body::value_type body; }; ``` @@ -61,10 +62,11 @@ struct request template struct response { - int version; - int status; + int version; + int status; std::string reason; - Fields fields; + Fields fields; + typename Body::value_type body; }; ``` @@ -83,8 +85,8 @@ However, a problem arises. How do we write a function which can accept an object that is either a request or a response? As written, the only obvious solution is to make the message a template type. Additional traits classes would then be needed to make sure that the passed object has a -valid type which meets the requirements. Instead, bypass those complexities -by making each container a partial specialization of one class: +valid type which meets the requirements. These unnecessary complexities +are bypassed by making each container a partial specialization: ``` /// An HTTP message template @@ -94,10 +96,11 @@ struct message; template struct message { - int version; + int version; std::string method; std::string target; - Fields fields; + Fields fields; + typename Body::value_type body; }; @@ -105,10 +108,11 @@ struct message template struct message { - int version; - int status; + int version; + int status; std::string reason; - Fields fields; + Fields fields; + typename Body::value_type body; }; ``` @@ -121,7 +125,7 @@ void f(message& msg); This function can manipulate the fields common to requests and responses. If it needs to access the other fields, it can do so using tag dispatch -with an object of type `std::integral_constant`. +with an object of type `std::integral_constant`. Often, in non-trivial HTTP applications, we want to read the HTTP header and examine its contents before choosing a type for [*Body]. To accomplish @@ -129,30 +133,36 @@ this, there needs to be a way to model the header portion of a message. And we'd like to do this in a way that allows functions which take the header as a parameter, to also accept a type representing the whole message (the function will see just the header part). This suggests -inheritance: +inheritance, by splitting a new base class off of the message: ``` /// An HTTP message header template struct header; +``` +Code which accesses the fields has to laboriously mention the `fields` +member, so we'll not only make `header` a base class but we'll make +a quality of life improvement and derive the header from the fields +for notational convenience. In order to properly support all forms +of construction of [*Fields] there will need to be a set of suitable +constructor overloads (not shown): +``` /// An HTTP request header template -struct header +struct header : Fields { - int version; + int version; std::string method; std::string target; - Fields fields; }; /// An HTTP response header template -struct header +struct header : Fields { - int version; - int status; + int version; + int status; std::string reason; - Fields fields; }; /// An HTTP message @@ -170,7 +180,7 @@ struct message : header Note that the `message` class now has a constructor allowing messages to be constructed from a similarly typed `header`. This handles the case where the user already has the header and wants to make a commitment to the -type for [*Body]. This also lets us declare a function accepting any header: +type for [*Body]. A function can be declared which accepts any header: ``` template void f(header& msg); @@ -202,21 +212,22 @@ support the scheme. It also means that request messages could have [*four] different allocators: two for the fields and body, and two for the method and target strings. A better solution is needed. -To get around this we make a simple change to the interface and then -engineer a clever concession. First, the interface change: +To get around this we make an interface modification and then add +a requirement to the [*Fields] type. First, the interface change: ``` /// An HTTP request header template -struct header +struct header : Fields { - int version; - verb method() const; + int version; + + verb method() const; string_view method_string() const; - void method(verb); - void method(string_view); + void method(verb); + void method(string_view); + string_view target(); const; - void target(string_view); - Fields fields; + void target(string_view); private: verb method_; @@ -224,13 +235,12 @@ private: /// An HTTP response header template -struct header +struct header : Fields { - int version; - int status; + int version; + int result; string_view reason() const; - void reason(string_view); - Fields fields; + void reason(string_view); }; ``` @@ -239,40 +249,41 @@ non-owning references to string buffers. The method is stored using a simple integer instead of the entire string, for the case where the method is recognized from the set of known verb strings. -Now we make a concession: management of the corresponding string is -delegated to the [*Fields] container, which can already be allocator -aware and constructed with the necessary allocator parameter via the -provided constructor overloads for `message`. The delegation -implementation looks like this (only the response header specialization -is shown): +Now we add a requirement to the fields type: management of the +corresponding string is delegated to the [*Fields] container, which can +already be allocator aware and constructed with the necessary allocator +parameter via the provided constructor overloads for `message`. The +delegation implementation looks like this (only the response header +specialization is shown): ``` /// An HTTP response header template -struct header +struct header : Fields { - int version; - int status; + int version; + int status; string_view reason() const { - return fields.reason_impl(); + return this->reason_impl(); // protected member of Fields } void reason(string_view s) { - fields.reason_impl(s); + this->reason_impl(s); // protected member of Fields } - - Fields fields; }; ``` -Now that we've accomplished our initial goals and more, there is one small -quality of life improvement to make. Users will choose different types for -`Body` far more often than they will for `Fields`. Thus, we swap the order -of these types and provide a default: +Now that we've accomplished our initial goals and more, there are a few +more quality of life improvements to make. Users will choose different +types for `Body` far more often than they will for `Fields`. Thus, we +swap the order of these types and provide a default. Then, we provide +type aliases for requests and responses to soften the impact of using +`bool` to choose the specialization: + ``` /// An HTTP header template @@ -281,6 +292,22 @@ struct header; /// An HTTP message template struct message; + +/// An HTTP request +template +using request = message; + +/// An HTTP response +template +using response = message; +``` + +This allows concise specification for the common cases, while +allowing for maximum customization for edge cases: +``` +request req; + +response res; ``` This container is also capable of representing complete HTTP/2 messages. diff --git a/doc/9_3_websocket_zaphoyd.qbk b/doc/9_3_websocket_zaphoyd.qbk index 99ea62c3..80fbe793 100644 --- a/doc/9_3_websocket_zaphoyd.qbk +++ b/doc/9_3_websocket_zaphoyd.qbk @@ -13,417 +13,431 @@ How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], an alternate header-only WebSocket implementation? ][ - [variablelist +[variablelist - [[1. Synchronous Interface][ +[[1. Synchronous Interface][ - Beast offers full support for WebSockets using a synchronous interface. It - uses the same style of interfaces found in Boost.Asio: versions that throw - exceptions, or versions that return the error code in a reference parameter: +Beast offers full support for WebSockets using a synchronous interface. It +uses the same style of interfaces found in Boost.Asio: versions that throw +exceptions, or versions that return the error code in a reference parameter: - [table +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [websocketpp] + ][ + [``` + template + void + read(DynamicBuffer& dynabuf) + ```] [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [websocketpp] - ][ - [``` - template - void - read(opcode& op, DynamicBuffer& dynabuf) - ```] - [ - // - ] - ]]]] + // + ] +]]]] - [[2. Connection Model][ +[[2. Connection Model][ - websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` - ([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) - To get an idea of the complexity involved with implementing a transport, - compare the asio transport to the - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] - (a layer that allows websocket communication over a `std::iostream`). +websocketpp supports multiple transports by utilizing a trait, the `config::transport_type` +([@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/connection.hpp#L60 asio transport example]) +To get an idea of the complexity involved with implementing a transport, +compare the asio transport to the +[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L59 `iostream` transport] +(a layer that allows websocket communication over a `std::iostream`). - In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] - template argument The type requirements for [*`NextLayer`] are - already familiar to users as they are documented in Asio: - __AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. +In contrast, Beast abstracts the transport by defining just one [*`NextLayer`] +template argument The type requirements for [*`NextLayer`] are +already familiar to users as they are documented in Asio: +__AsyncReadStream__, __AsyncWriteStream__, __SyncReadStream__, __SyncWriteStream__. - The type requirements for instantiating `beast::websocket::stream` versus - `websocketpp::connection` with user defined types are vastly reduced - (18 functions versus 2). Note that websocketpp connections are passed by - `shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. - A `beast::websocket::stream` is constructible and movable in a manner identical - to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a - `shared_ptr` if they want to, but there is no requirement to do so. +The type requirements for instantiating `beast::websocket::stream` versus +`websocketpp::connection` with user defined types are vastly reduced +(18 functions versus 2). Note that websocketpp connections are passed by +`shared_ptr`. Beast does not use `shared_ptr` anywhere in its public interface. +A `beast::websocket::stream` is constructible and movable in a manner identical +to a `boost::asio::ip::tcp::socket`. Callers can put such objects in a +`shared_ptr` if they want to, but there is no requirement to do so. - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] - ][ - [``` - template - class stream - { - NextLayer next_layer_; - ... - } - ```] - [``` - template - class connection - : public config::transport_type::transport_con_type - , public config::connection_base - { - public: - typedef lib::shared_ptr ptr; - ... - } - ```] - ]]]] - - [[3. Client and Server Role][ - - websocketpp provides multi-role support through a hierarchy of - different classes. A `beast::websocket::stream` is role-agnostic, it - offers member functions to perform both client and server handshakes - in the same class. The same types are used for client and server - streams. - - [table - [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] - ][ - [ - // - ] - [``` - template - class client : public endpoint,config>; - template - class server : public endpoint,config>; - ```] - ]]]] - - [[4. Thread Safety][ - - websocketpp uses mutexes to protect shared data from concurrent - access. In contrast, Beast does not use mutexes anywhere in its - implementation. Instead, it follows the Asio pattern. Calls to - asynchronous initiation functions use the same method to invoke - intermediate handlers as the method used to invoke the final handler, - through the __asio_handler_invoke__ mechanism. - - The only requirement in Beast is that calls to asynchronous initiation - functions are made from the same implicit or explicit strand. For - example, if the `io_service` associated with a `beast::websocket::stream` - is single threaded, this counts as an implicit strand and no performance - costs associated with mutexes are incurred. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] - ][ - [``` - template - friend - void asio_handler_invoke(Function&& f, read_frame_op* op) - { - return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); - } - ```] - [``` - mutex_type m_read_mutex; - ```] - ]]]] - - [[5. Callback Model][ - - websocketpp requires a one-time call to set the handler for each event - in its interface (for example, upon message receipt). The handler is - represented by a `std::function` equivalent. Its important to recognize - that the websocketpp interface performs type-erasure on this handler. - - In comparison, Beast handlers are specified in a manner identical to - Boost.Asio. They are function objects which can be copied or moved but - most importantly they are not type erased. The compiler can see - through the type directly to the implementation, permitting - optimization. Furthermore, Beast follows the Asio rules for treatment - of handlers. It respects any allocation, continuation, or invocation - customizations associated with the handler through the use of argument - dependent lookup overloads of functions such as `asio_handler_allocate`. - - The Beast completion handler is provided at the call site. For each - call to an asynchronous initiation function, it is guaranteed that - there will be exactly one final call to the handler. This functions - exactly the same way as the asynchronous initiation functions found in - Boost.Asio, allowing the composition of higher level abstractions. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], - [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] - ][ - [``` - template - typename async_completion::result_type - async_read(opcode& op, DynamicBuffer& dynabuf, ReadHandler&& handler); - ```] - [``` - typedef lib::function message_handler; - void set_message_handler(message_handler h); - ```] - ]]]] - - [[6. Extensible Asynchronous Model][ - - Beast fully supports the - [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] - developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). - - Beast websocket asynchronous interfaces may be used seamlessly with - `std::future` stackful/stackless coroutines, or user defined customizations. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] - [websocketpp] - ][ - [``` - beast::async_completion completion(handler); - read_op{ - completion.handler, *this, op, buffer}; - return completion.result.get(); - ```] - [ - // - ] - ]]]] - - [[7. Message Buffering][ - - websocketpp defines a message buffer, passed in arguments by - `shared_ptr`, and an associated message manager which permits - aggregation and reuse of memory. The implementation of - `websocketpp::message` uses a `std::string` to hold the payload. If an - incoming message is broken up into multiple frames, the string may be - reallocated for each continuation frame. The `std::string` always uses - the standard allocator, it is not possible to customize the choice of - allocator. - - Beast allows callers to specify the object for receiving the message - or frame data, which is of any type meeting the requirements of - __DynamicBuffer__ (modeled after `boost::asio::streambuf`). - - Beast comes with the class __basic_multi_buffer__, an efficient - implementation of the __DynamicBuffer__ concept which makes use of multiple - allocated octet arrays. If an incoming message is broken up into - multiple pieces, no reallocation occurs. Instead, new allocations are - appended to the sequence when existing allocations are filled. Beast - does not impose any particular memory management model on callers. The - __basic_multi_buffer__ provided by beast supports standard allocators through - a template argument. Use the __DynamicBuffer__ that comes with beast, - customize the allocator if you desire, or provide your own type that - meets the requirements. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] - ][ - [``` - template - read(opcode& op, DynamicBuffer& dynabuf); - ```] - [``` - template class con_msg_manager> - class message { - public: - typedef lib::shared_ptr ptr; - ... - std::string m_payload; - ... - }; - ```] - ]]]] - - [[8. Sending Messages][ - - When sending a message, websocketpp requires that the payload is - packaged in a `websocketpp::message` object using `std::string` as the - storage, or it requires a copy of the caller provided buffer by - constructing a new message object. Messages are placed onto an - outgoing queue. An asynchronous write operation runs in the background - to clear the queue. No user facing handler can be registered to be - notified when messages or frames have completed sending. - - Beast doesn't allocate or make copies of buffers when sending data. The - caller's buffers are sent in-place. You can use any object meeting the - requirements of - [@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], - permitting efficient scatter-gather I/O. - - The [*ConstBufferSequence] interface allows callers to send data from - memory-mapped regions (not possible in websocketpp). Callers can also - use the same buffers to send data to multiple streams, for example - broadcasting common subscription data to many clients at once. For - each call to `async_write` the completion handler is called once when - the data finishes sending, in a manner identical to `boost::asio::async_write`. - - [table - [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] - ][ - [``` - template - void - write(ConstBufferSequence const& buffers); - ```] - [``` - lib::error_code send(std::string const & payload, - frame::opcode::value op = frame::opcode::text); +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L234 websocketpp]] + ][ + [``` + template + class stream + { + NextLayer next_layer_; ... - lib::error_code send(message_ptr msg); - ```] - ]]]] + } + ```] + [``` + template + class connection + : public config::transport_type::transport_con_type + , public config::connection_base + { + public: + typedef lib::shared_ptr ptr; + ... + } + ```] +]]]] - [[9. Streaming Messages][ +[[3. Client and Server Role][ - websocketpp requires that the entire message fit into memory, and that - the size is known ahead of time. +websocketpp provides multi-role support through a hierarchy of +different classes. A `beast::websocket::stream` is role-agnostic, it +offers member functions to perform both client and server handshakes +in the same class. The same types are used for client and server +streams. - Beast allows callers to compose messages in individual frames. This is - useful when the size of the data is not known ahead of time or if it - is not desired to buffer the entire message in memory at once before - sending it. For example, sending periodic output of a database query - running on a coroutine. Or sending the contents of a file in pieces, - without bringing it all into memory. - - [table +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/server_endpoint.hpp#L39 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/roles/client_endpoint.hpp#L42 also]] + ][ [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] - [websocketpp] - ][ - [``` - template - void - write_frame(bool fin, - ConstBufferSequence const& buffers); - ```] - [ - // - ] - ]]]] + // + ] + [``` + template + class client : public endpoint,config>; + template + class server : public endpoint,config>; + ```] +]]]] - [[10. Flow Control][ +[[4. Thread Safety][ - The websocketpp read implementation continuously reads asynchronously - from the network and buffers message data. To prevent unbounded growth - and leverage TCP/IP's flow control mechanism, callers can periodically - turn this 'read pump' off and back on. +websocketpp uses mutexes to protect shared data from concurrent +access. In contrast, Beast does not use mutexes anywhere in its +implementation. Instead, it follows the Asio pattern. Calls to +asynchronous initiation functions use the same method to invoke +intermediate handlers as the method used to invoke the final handler, +through the __asio_handler_invoke__ mechanism. - In contrast a `beast::websocket::stream` does not independently begin - background activity, nor does it buffer messages. It receives data only - when there is a call to an asynchronous initiation function (for - example `beast::websocket::stream::async_read`) with an associated handler. - Applications do not need to implement explicit logic to regulate the - flow of data. Instead, they follow the traditional model of issuing a - read, receiving a read completion, processing the message, then - issuing a new read and repeating the process. +The only requirement in Beast is that calls to asynchronous initiation +functions are made from the same implicit or explicit strand. For +example, if the `io_service` associated with a `beast::websocket::stream` +is single threaded, this counts as an implicit strand and no performance +costs associated with mutexes are incurred. - [table +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/read_frame_op.ipp#L118 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/iostream/connection.hpp#L706 websocketpp]] + ][ + [``` + template + friend + void asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers::invoke(f, op->d_->h); + } + ```] + [``` + mutex_type m_read_mutex; + ```] +]]]] + +[[5. Callback Model][ + +websocketpp requires a one-time call to set the handler for each event +in its interface (for example, upon message receipt). The handler is +represented by a `std::function` equivalent. Its important to recognize +that the websocketpp interface performs type-erasure on this handler. + +In comparison, Beast handlers are specified in a manner identical to +Boost.Asio. They are function objects which can be copied or moved but +most importantly they are not type erased. The compiler can see +through the type directly to the implementation, permitting +optimization. Furthermore, Beast follows the Asio rules for treatment +of handlers. It respects any allocation, continuation, or invocation +customizations associated with the handler through the use of argument +dependent lookup overloads of functions such as `asio_handler_allocate`. + +The Beast completion handler is provided at the call site. For each +call to an asynchronous initiation function, it is guaranteed that +there will be exactly one final call to the handler. This functions +exactly the same way as the asynchronous initiation functions found in +Boost.Asio, allowing the composition of higher level abstractions. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L834 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L281 websocketpp], + [@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L473 also]] + ][ + [``` + template< + class DynamicBuffer, // Supports user defined types + class ReadHandler // Handler is NOT type-erased + > + typename async_completion< // Return value customization + ReadHandler, // supports futures and coroutines + void(error_code) + >::result_type + async_read( + DynamicBuffer& dynabuf, + ReadHandler&& handler); + ```] + [``` + typedef lib::function< + void(connection_hdl,message_ptr) + > message_handler; + void set_message_handler(message_handler h); + ```] +]]]] + +[[6. Extensible Asynchronous Model][ + +Beast fully supports the +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3896.pdf Extensible Asynchronous Model] +developed by Christopher Kohlhoff, author of Boost.Asio (see Section 8). + +Beast websocket asynchronous interfaces may be used seamlessly with +`std::future` stackful/stackless coroutines, or user defined customizations. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/impl/stream.ipp#L378 Beast]] + [websocketpp] + ][ + [``` + beast::async_completion< + ReadHandler, + void(error_code)> completion{handler}; + read_op< + DynamicBuffer, decltype(completion.handler)>{ + completion.handler, *this, op, buffer}; + + return completion.result.get(); // Customization point + ```] [ - [Beast] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] - ][ - [ - // - ] - [``` - lib::error_code pause_reading(); - lib::error_code resume_reading(); - ```] - ]]]] + // + ] +]]]] - [[11. Connection Establishment][ +[[7. Message Buffering][ - websocketpp offers the `endpoint` class which can handle binding and - listening to a port, and spawning connection objects. +websocketpp defines a message buffer, passed in arguments by +`shared_ptr`, and an associated message manager which permits +aggregation and reuse of memory. The implementation of +`websocketpp::message` uses a `std::string` to hold the payload. If an +incoming message is broken up into multiple frames, the string may be +reallocated for each continuation frame. The `std::string` always uses +the standard allocator, it is not possible to customize the choice of +allocator. - Beast does not reinvent the wheel here, callers use the interfaces - already in `boost::asio` for receiving incoming connections resolving - host names, or establishing outgoing connections. After the socket (or - `boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` - is constructed around it and the WebSocket handshake can be performed. +Beast allows callers to specify the object for receiving the message +or frame data, which is of any type meeting the requirements of +__DynamicBuffer__ (modeled after `boost::asio::streambuf`). - Beast users are free to implement their own "connection manager", but - there is no requirement to do so. +Beast comes with the class __basic_multi_buffer__, an efficient +implementation of the __DynamicBuffer__ concept which makes use of multiple +allocated octet arrays. If an incoming message is broken up into +multiple pieces, no reallocation occurs. Instead, new allocations are +appended to the sequence when existing allocations are filled. Beast +does not impose any particular memory management model on callers. The +__basic_multi_buffer__ provided by beast supports standard allocators through +a template argument. Use the __DynamicBuffer__ that comes with beast, +customize the allocator if you desire, or provide your own type that +meets the requirements. - [table +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L774 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/message_buffer/message.hpp#L78 websocketpp]] + ][ + [``` + template + read(DynamicBuffer& dynabuf); + ```] + [``` + template class con_msg_manager> + class message { + public: + typedef lib::shared_ptr ptr; + ... + std::string m_payload; + ... + }; + ```] +]]]] + +[[8. Sending Messages][ + +When sending a message, websocketpp requires that the payload is +packaged in a `websocketpp::message` object using `std::string` as the +storage, or it requires a copy of the caller provided buffer by +constructing a new message object. Messages are placed onto an +outgoing queue. An asynchronous write operation runs in the background +to clear the queue. No user facing handler can be registered to be +notified when messages or frames have completed sending. + +Beast doesn't allocate or make copies of buffers when sending data. The +caller's buffers are sent in-place. You can use any object meeting the +requirements of +[@http://www.boost.org/doc/html/boost_asio/reference/ConstBufferSequence.html ConstBufferSequence], +permitting efficient scatter-gather I/O. + +The [*ConstBufferSequence] interface allows callers to send data from +memory-mapped regions (not possible in websocketpp). Callers can also +use the same buffers to send data to multiple streams, for example +broadcasting common subscription data to many clients at once. For +each call to `async_write` the completion handler is called once when +the data finishes sending, in a manner identical to `boost::asio::async_write`. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1048 Beast]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L672 websocketpp]] + ][ + [``` + template + void + write(ConstBufferSequence const& buffers); + ```] + [``` + lib::error_code send(std::string const & payload, + frame::opcode::value op = frame::opcode::text); + ... + lib::error_code send(message_ptr msg); + ```] +]]]] + +[[9. Streaming Messages][ + +websocketpp requires that the entire message fit into memory, and that +the size is known ahead of time. + +Beast allows callers to compose messages in individual frames. This is +useful when the size of the data is not known ahead of time or if it +is not desired to buffer the entire message in memory at once before +sending it. For example, sending periodic output of a database query +running on a coroutine. Or sending the contents of a file in pieces, +without bringing it all into memory. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L1151 Beast]] + [websocketpp] + ][ + [``` + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers); + ```] [ - [[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast], - [@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] - [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] - ][ - [``` - #include - ```] - [``` - template - class endpoint : public config::socket_type; - ```] - ]]]] + // + ] +]]]] - [[12. WebSocket Handshaking][ +[[10. Flow Control][ - Callers invoke `beast::websocket::accept` to perform the WebSocket - handshake, but there is no requirement to use this function. Advanced - users can perform the WebSocket handshake themselves. Beast WebSocket - provides the tools for composing the request or response, and the - Beast HTTP interface provides the container and algorithms for sending - and receiving HTTP/1 messages including the necessary HTTP Upgrade - request for establishing the WebSocket session. +The websocketpp read implementation continuously reads asynchronously +from the network and buffers message data. To prevent unbounded growth +and leverage TCP/IP's flow control mechanism, callers can periodically +turn this 'read pump' off and back on. - Beast allows the caller to pass the incoming HTTP Upgrade request for - the cases where the caller has already received an HTTP message. - This flexibility permits novel and robust implementations. For example, - a listening socket that can handshake in multiple protocols on the - same port. +In contrast a `beast::websocket::stream` does not independently begin +background activity, nor does it buffer messages. It receives data only +when there is a call to an asynchronous initiation function (for +example `beast::websocket::stream::async_read`) with an associated handler. +Applications do not need to implement explicit logic to regulate the +flow of data. Instead, they follow the traditional model of issuing a +read, receiving a read completion, processing the message, then +issuing a new read and repeating the process. - Sometimes callers want to read some bytes on the socket before reading - the WebSocket HTTP Upgrade request. Beast allows these already-received - bytes to be supplied to an overload of the accepting function to permit - sophisticated features. For example, a listening socket that can - accept both regular WebSocket and Secure WebSocket (SSL) connections. - - [table +[table + [ + [Beast] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/connection.hpp#L728 websocketpp]] + ][ [ - [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], - [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] - [websocketpp] - ][ - [``` - template - void - accept(ConstBufferSequence const& buffers); + // + ] + [``` + lib::error_code pause_reading(); + lib::error_code resume_reading(); + ```] +]]]] - template - void - accept(http::request_v1 const& request); - ```] - [ - // - ] - ]]]] +[[11. Connection Establishment][ - ] +websocketpp offers the `endpoint` class which can handle binding and +listening to a port, and spawning connection objects. + +Beast does not reinvent the wheel here, callers use the interfaces +already in `boost::asio` for receiving incoming connections resolving +host names, or establishing outgoing connections. After the socket (or +`boost::asio::ssl::stream`) is connected, the `beast::websocket::stream` +is constructed around it and the WebSocket handshake can be performed. + +Beast users are free to implement their own "connection manager", but +there is no requirement to do so. + +[table + [ + [[@http://www.boost.org/doc/html/boost_asio/reference/async_connect.html Beast], + [@http://www.boost.org/doc/html/boost_asio/reference/basic_socket_acceptor/async_accept.html also]] + [[@https://github.com/zaphoyd/websocketpp/blob/378437aecdcb1dfe62096ffd5d944bf1f640ccc3/websocketpp/transport/asio/endpoint.hpp#L52 websocketpp]] + ][ + [``` + #include + ```] + [``` + template + class endpoint : public config::socket_type; + ```] +]]]] + +[[12. WebSocket Handshaking][ + +Callers invoke `beast::websocket::accept` to perform the WebSocket +handshake, but there is no requirement to use this function. Advanced +users can perform the WebSocket handshake themselves. Beast WebSocket +provides the tools for composing the request or response, and the +Beast HTTP interface provides the container and algorithms for sending +and receiving HTTP/1 messages including the necessary HTTP Upgrade +request for establishing the WebSocket session. + +Beast allows the caller to pass the incoming HTTP Upgrade request for +the cases where the caller has already received an HTTP message. +This flexibility permits novel and robust implementations. For example, +a listening socket that can handshake in multiple protocols on the +same port. + +Sometimes callers want to read some bytes on the socket before reading +the WebSocket HTTP Upgrade request. Beast allows these already-received +bytes to be supplied to an overload of the accepting function to permit +sophisticated features. For example, a listening socket that can +accept both regular WebSocket and Secure WebSocket (SSL) connections. + +[table + [ + [[@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L501 Beast], + [@https://github.com/vinniefalco/Beast/blob/6c8b4b2f8dde72b01507e4ac7fde4ffea57ebc99/include/beast/websocket/stream.hpp#L401 also]] + [websocketpp] + ][ + [``` + template + void + accept(ConstBufferSequence const& buffers); + + template + void + accept(http::header> const& req); + ```] + [ + // + ] +]]]] + +] ]] ] diff --git a/doc/images/message.png b/doc/images/message.png index e78bbcb0..b1675df8 100644 Binary files a/doc/images/message.png and b/doc/images/message.png differ diff --git a/example/echo-op/echo_op.cpp b/example/echo-op/echo_op.cpp index 108c1853..ecccc2f2 100644 --- a/example/echo-op/echo_op.cpp +++ b/example/echo-op/echo_op.cpp @@ -13,13 +13,24 @@ //[example_core_echo_op_1 +template< + class AsyncStream, + class CompletionToken> +auto +async_echo(AsyncStream& stream, CompletionToken&& token) + +//] + -> beast::async_return_type; + +//[example_core_echo_op_2 + /** Asynchronously read a line and echo it back. This function is used to asynchronously read a line ending - in a carriage-return linefeed ("CRLF") from the stream, - and then write it back. The function call always returns - immediately. The asynchronous operation will continue until - one of the following conditions is true: + in a carriage-return ("CR") from the stream, and then write + it back. The function call always returns immediately. The + asynchronous operation will continue until one of the + following conditions is true: @li A line was read in and sent back on the stream @@ -38,10 +49,10 @@ @param token The completion token to use. If this is a completion handler, copies will be made as required. - The signature of the handler must be: + The equivalent signature of the handler must be: @code void handler( - error_code& ec // result of operation + error_code ec // result of operation ); @endcode Regardless of whether the asynchronous operation completes @@ -61,7 +72,7 @@ async_echo( //] -//[example_core_echo_op_3 +//[example_core_echo_op_4 // This composed operation reads a line of input and echoes it back. // @@ -79,10 +90,11 @@ class echo_op 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. + // + // 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 + // a continuation. // boost::asio::basic_streambuf> buffer; @@ -98,11 +110,17 @@ class echo_op } }; - // 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. + // The operation's data is kept in a cheap-to-copy smart + // pointer container called `handler_ptr`. This efficiently + // satisfies the CopyConstructible requirements of completion + // handlers. + // + // `handler_ptr` uses these memory allocation hooks associated + // with the final completion handler, in order to allocate the + // storage for `state`: + // + // asio_handler_allocate + // asio_handler_deallocate // beast::handler_ptr p_; @@ -123,65 +141,35 @@ public: { } - // 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. + // The entry point for this handler. This will get called + // as our intermediate operations complete. Definition below. // void operator()(beast::error_code ec, std::size_t bytes_transferred); + + // The next four functions are required for our class + // to meet the requirements for composed operations. + // Definitions and exposition will follow. + + template + friend void asio_handler_invoke( + Function&& f, echo_op* op); + + template + friend void* asio_handler_allocate( + std::size_t size, echo_op* op); + + template + friend void asio_handler_deallocate( + void* p, std::size_t size, echo_op* op); + + template + friend bool asio_handler_is_continuation( + echo_op* op); }; //] -//[example_core_echo_op_4 +//[example_core_echo_op_5 // echo_op is callable with the signature void(error_code, bytes_transferred), // allowing `*this` to be used as both a ReadHandler and a WriteHandler. @@ -202,7 +190,7 @@ operator()(beast::error_code ec, std::size_t bytes_transferred) case 0: // read up to the first newline p.step = 1; - return boost::asio::async_read_until(p.stream, p.buffer, "\n", std::move(*this)); + return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this)); case 1: // write everything back @@ -217,10 +205,15 @@ operator()(beast::error_code ec, std::size_t 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. + // Invoke the final handler. The implementation of `handler_ptr` + // will deallocate the storage for the state before the handler + // is invoked. This is necessary to provide the + // destroy-before-invocation guarantee on handler memory + // customizations. + // + // If we wanted to pass any arguments to the handler which come + // from the `state`, they would have to be moved to the stack + // first or else undefined behavior results. // p_.invoke(ec); return; @@ -228,7 +221,72 @@ operator()(beast::error_code ec, std::size_t bytes_transferred) //] -//[example_core_echo_op_2 +//[example_core_echo_op_6 + +// Handler hook forwarding. These free functions invoke the hooks +// associated with the final completion handler. In effect, they +// make the Asio implementation treat our composed operation the +// same way it would treat the final completion handler for the +// purpose of memory allocation and invocation. +// +// Our implementation just passes the call through to the hook +// associated with the final handler. The "using" statements are +// structured to permit argument dependent lookup. Always use +// `std::addressof` or its equivalent to pass the pointer to the +// handler, otherwise an unwanted overload of `operator&` may be +// called instead. + +template +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())); +} + +template +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())); +} + +template +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())); +} + +// Determines if the next asynchronous operation represents a +// continuation of the asynchronous flow of control associated +// with the final handler. If we are past step one, it means +// we have performed an asynchronous operation therefore any +// subsequent operation would represent a continuation. +// Otherwise, we propagate the handler's associated value of +// is_continuation. Getting this right means the implementation +// may schedule the invokation of the invoked functions more +// efficiently. +// +template +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())); +} + +//] + +//[example_core_echo_op_3 template class echo_op; @@ -254,8 +312,8 @@ async_echo(AsyncStream& stream, CompletionToken&& 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. + // allowing user-defined specializations of the async_result template + // to be used. // echo_op>{ stream, init.completion_handler}(beast::error_code{}, 0); diff --git a/include/beast/core/async_result.hpp b/include/beast/core/async_result.hpp index 1cad245f..8465ba0b 100644 --- a/include/beast/core/async_result.hpp +++ b/include/beast/core/async_result.hpp @@ -32,7 +32,7 @@ namespace beast { where the specific completion handler signature is known. This template takes advantage of specializations of both - `boost::asio_async_result` and `boost::asio::handler_type` for user-defined + `boost::asio::async_result` and `boost::asio::handler_type` for user-defined completion token types. The primary template assumes that the @b CompletionToken is the completion handler. diff --git a/include/beast/core/drain_buffer.hpp b/include/beast/core/drain_buffer.hpp index 6b02fb89..15f89a69 100644 --- a/include/beast/core/drain_buffer.hpp +++ b/include/beast/core/drain_buffer.hpp @@ -42,6 +42,7 @@ public: /// Copy constructor drain_buffer(drain_buffer const&) { + // Previously returned ranges are invalidated } /// Copy assignment diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index 623508f3..dcbce060 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -35,10 +35,10 @@ namespace http { value. Field names are stored as-is, but comparisons are case-insensitive. - When the container is iterated, the fields are presented in the order - of insertion. For fields with the same name, the container behaves - as a `std::multiset`; there will be a separate value for each occurrence - of the field name. + The container behaves as a `std::multiset`; there will be a separate + value for each occurrence of the same field name. When the container + is iterated the fields are presented in the order of insertion, with + fields having the same name following each other consecutively. Meets the requirements of @b Fields diff --git a/include/beast/http/message.hpp b/include/beast/http/message.hpp index 01b84298..fdb13461 100644 --- a/include/beast/http/message.hpp +++ b/include/beast/http/message.hpp @@ -53,7 +53,7 @@ struct header : Fields /// Indicates if the header is a request or response. #if BEAST_DOXYGEN - using is_request = isRequest; + using is_request = std::integral_constant; #else using is_request = std::true_type; #endif @@ -203,7 +203,11 @@ struct header : Fields "Fields requirements not met"); /// Indicates if the header is a request or response. +#if BEAST_DOXYGEN + using is_request = std::integral_constant; +#else using is_request = std::false_type; +#endif /// The type representing the fields. using fields_type = Fields; diff --git a/include/beast/websocket/impl/write.ipp b/include/beast/websocket/impl/write.ipp index 36204694..b533423b 100644 --- a/include/beast/websocket/impl/write.ipp +++ b/include/beast/websocket/impl/write.ipp @@ -546,27 +546,120 @@ upcall: d_.invoke(ec); } +//------------------------------------------------------------------------------ + template -template -async_return_type< - WriteHandler, void(error_code)> -stream:: -async_write_frame(bool fin, - ConstBufferSequence const& bs, WriteHandler&& handler) +template +class stream::write_op { - static_assert(is_async_stream::value, - "AsyncStream requirements not met"); - static_assert(beast::is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence requirements not met"); - async_completion init{handler}; - write_frame_op>{init.completion_handler, - *this, fin, bs}; - return init.result.get(); + struct data : op + { + int step = 0; + stream& ws; + consuming_buffers cb; + std::size_t remain; + + data(Handler& handler, stream& ws_, + Buffers const& bs) + : ws(ws_) + , cb(bs) + , remain(boost::asio::buffer_size(cb)) + { + } + }; + + handler_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + stream& ws, Args&&... args) + : d_(std::forward(h), + ws, std::forward(args)...) + { + } + + void operator()(error_code ec); + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + using boost::asio::asio_handler_allocate; + return asio_handler_allocate( + size, std::addressof(op->d_.handler())); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + using boost::asio::asio_handler_deallocate; + asio_handler_deallocate( + p, size, std::addressof(op->d_.handler())); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + using boost::asio::asio_handler_is_continuation; + return op->d_->step > 2 || + asio_handler_is_continuation( + std::addressof(op->d_.handler())); + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + using boost::asio::asio_handler_invoke; + asio_handler_invoke( + f, std::addressof(op->d_.handler())); + } +}; + +template +template +void +stream:: +write_op:: +operator()(error_code ec) +{ + auto& d = *d_; + switch(d.step) + { + case 2: + d.step = 3; + BOOST_FALLTHROUGH; + case 3: + case 0: + { + auto const n = d.remain; + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.step = d.step ? 4 : 1; + else + d.step = d.step ? 3 : 2; + auto const pb = buffer_prefix(n, d.cb); + d.cb.consume(n); + return d.ws.async_write_frame( + fin, pb, std::move(*this)); + } + + case 1: + case 4: + break; + } + d_.invoke(ec); } +//------------------------------------------------------------------------------ + template template void @@ -790,124 +883,13 @@ write_frame(bool fin, } } -//------------------------------------------------------------------------------ - -template -template -class stream::write_op -{ - struct data : op - { - int step = 0; - stream& ws; - consuming_buffers cb; - std::size_t remain; - - data(Handler& handler, stream& ws_, - Buffers const& bs) - : ws(ws_) - , cb(bs) - , remain(boost::asio::buffer_size(cb)) - { - } - }; - - handler_ptr d_; - -public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - explicit - write_op(DeducedHandler&& h, - stream& ws, Args&&... args) - : d_(std::forward(h), - ws, std::forward(args)...) - { - } - - void operator()(error_code ec); - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - using boost::asio::asio_handler_allocate; - return asio_handler_allocate( - size, std::addressof(op->d_.handler())); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - using boost::asio::asio_handler_deallocate; - asio_handler_deallocate( - p, size, std::addressof(op->d_.handler())); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - using boost::asio::asio_handler_is_continuation; - return op->d_->step > 2 || - asio_handler_is_continuation( - std::addressof(op->d_.handler())); - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - using boost::asio::asio_handler_invoke; - asio_handler_invoke( - f, std::addressof(op->d_.handler())); - } -}; - -template -template -void -stream:: -write_op:: -operator()(error_code ec) -{ - auto& d = *d_; - switch(d.step) - { - case 2: - d.step = 3; - BOOST_FALLTHROUGH; - case 3: - case 0: - { - auto const n = d.remain; - d.remain -= n; - auto const fin = d.remain <= 0; - if(fin) - d.step = d.step ? 4 : 1; - else - d.step = d.step ? 3 : 2; - auto const pb = buffer_prefix(n, d.cb); - d.cb.consume(n); - return d.ws.async_write_frame( - fin, pb, std::move(*this)); - } - - case 1: - case 4: - break; - } - d_.invoke(ec); -} - template template async_return_type< WriteHandler, void(error_code)> stream:: -async_write(ConstBufferSequence const& bs, WriteHandler&& handler) +async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) { static_assert(is_async_stream::value, "AsyncStream requirements not met"); @@ -916,13 +898,14 @@ async_write(ConstBufferSequence const& bs, WriteHandler&& handler) "ConstBufferSequence requirements not met"); async_completion init{handler}; - write_op>{ - init.completion_handler, *this, bs}( - error_code{}); + write_frame_op>{init.completion_handler, + *this, fin, bs}; return init.result.get(); } +//------------------------------------------------------------------------------ + template template void @@ -954,6 +937,28 @@ write(ConstBufferSequence const& buffers, error_code& ec) write_frame(true, buffers, ec); } +template +template +async_return_type< + WriteHandler, void(error_code)> +stream:: +async_write( + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(is_async_stream::value, + "AsyncStream requirements not met"); + static_assert(beast::is_const_buffer_sequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + async_completion init{handler}; + write_op>{ + init.completion_handler, *this, bs}( + error_code{}); + return init.result.get(); +} + } // websocket } // beast diff --git a/include/beast/websocket/stream.hpp b/include/beast/websocket/stream.hpp index a3212491..ceee5774 100644 --- a/include/beast/websocket/stream.hpp +++ b/include/beast/websocket/stream.hpp @@ -47,17 +47,15 @@ using response_type = http::response; that they are are all performed within the same implicit or explicit strand. + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. + @par Example To use the @ref stream template with an `ip::tcp::socket`, you would write: - @tparam NextLayer The type representing the next layer, to which - data will be read and written during operations. For synchronous - operations, the type must support the @b SyncStream concept. - For asynchronous operations, the type must support the - @b AsyncStream concept. - @code websocket::stream ws{io_service}; @endcode @@ -67,9 +65,11 @@ using response_type = http::response; websocket::stream ws{sock}; @endcode - @par Thread Safety - @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. + @tparam NextLayer The type representing the next layer, to which + data will be read and written during operations. For synchronous + operations, the type must support the @b SyncStream concept. + For asynchronous operations, the type must support the + @b AsyncStream concept. @note A stream object must not be moved or destroyed while there are pending asynchronous operations associated with it. diff --git a/test/http/doc_snippets.cpp b/test/http/doc_snippets.cpp index 99e7418f..3abf435d 100644 --- a/test/http/doc_snippets.cpp +++ b/test/http/doc_snippets.cpp @@ -31,7 +31,7 @@ void fxx() { { //[http_snippet_2 - request req; + request req; req.version = 11; // HTTP/1.1 req.method(verb::get); req.target("/index.htm");