From c29974eec9f432fea8674ca27d2075fc71de5175 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 26 May 2017 20:46:43 -0700 Subject: [PATCH] Documentation work --- CHANGELOG.md | 1 + doc/design.qbk | 602 +------------------------------ doc/design/http_comparison.qbk | 454 +++++++++++++++++++++++ doc/design/http_message.qbk | 284 +++++++++++++++ doc/{ => design}/review.qbk | 135 ++++++- doc/design/websocket_zaphoyd.qbk | 431 ++++++++++++++++++++++ doc/master.qbk | 10 +- 7 files changed, 1313 insertions(+), 604 deletions(-) create mode 100644 doc/design/http_comparison.qbk create mode 100644 doc/design/http_message.qbk rename doc/{ => design}/review.qbk (51%) create mode 100644 doc/design/websocket_zaphoyd.qbk diff --git a/CHANGELOG.md b/CHANGELOG.md index 309f8352..244c69f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Version 45 * Better test::enable_yield_to * Add test::pipe * Fix header::reason +* Documentation work -------------------------------------------------------------------------------- diff --git a/doc/design.qbk b/doc/design.qbk index 438770e8..055dcf43 100644 --- a/doc/design.qbk +++ b/doc/design.qbk @@ -9,9 +9,10 @@ [block ''' - HTTP FAQ - WebSocket FAQ - Comparison to Zaphoyd Studios WebSocket++ + HTTP Message Container + HTTP Comparison to Other Libraries + Comparison to Zaphoyd Studios WebSocket++ + Boost Formal Review FAQ '''] @@ -46,27 +47,6 @@ of other packages offering similar functionality. In this section and the FAQs that follow we attempt to answer those questions that are also applicable to Beast. -[variablelist -[[ - "I would also like to see instances of this library being used - in production. That would give some evidence that the design - works in practice." -][ - Beast.HTTP and Beast.WebSocket are production ready and currently - running on public servers receiving traffic and handling millions of - dollars worth of financial transactions daily. The servers run [*rippled], - open source software ([@https://github.com/ripple/rippled repository]) - implementing the - [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], - technology provided by [@http://ripple.com Ripple]. -]] - -] - - - -[section:http HTTP FAQ] - For HTTP we model the message to maximize flexibility of implementation strategies while allowing familiar verbs such as [*`read`] and [*`write`]. The HTTP interface is further driven by the needs of the WebSocket module, @@ -79,575 +59,9 @@ start. Other design goals: * Allow for customizations, if the user needs it. -[variablelist - -[[ - "Some more advanced examples, e.g. including TLS with client/server - certificates would help." -][ - The HTTP interface doesn't try to reinvent the wheel, it just uses - the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that - you set up beforehand. Callers use the interfaces already existing - on those objects to make outgoing connections, accept incoming connections, - or establish TLS sessions with certificates. We find the available Asio - examples for performing these tasks sufficient. -]] - -[[ - "A built-in HTTP router?" -][ - We presume this means a facility to match expressions against the URI - in HTTP requests, and dispatch them to calling code. The authors feel - that this is a responsibility of higher level code. Beast does - not try to offer a web server. -]] - -[[ - "HTTP Cookies? Forms/File Uploads?" -][ - Cookies, or managing these types of HTTP headers in general, is the - responsibility of higher levels. Beast.HTTP just tries to get complete - messages to and from the calling code. It deals in the HTTP headers just - enough to process the message body and leaves the rest to callers. However, - for forms and file uploads the symmetric interface of the message class - allows HTTP requests to include arbitrary body types including those needed - to upload a file or fill out a form. -]] - -[[ - "...supporting TLS (is this a feature? If not this would be a show-stopper), - etc." -][ - Beast does not provide direct facilities for implementing TLS connections; - however, the interfaces already existing on the `boost::asio::ssl::stream` - are available and can be used to establish secure connections. Then, - functions like `http::read` or `http::async_write` can work with those - encrypted connections with no problem. -]] - -[[ - "There should also be more examples of how to integrate the http service - with getting files from the file system, generating responses CGI-style" -][ - The design goal for the library is to not try to invent a web server. - We feel that there is a strong need for a basic implementation that - models the HTTP message and provides functions to send and receive them - over Asio. Such an implementation should serve as a building block upon - which higher abstractions such as the aforementioned HTTP service or - cgi-gateway can be built. - - One of the example programs implements a simple HTTP server that - delivers files from the filesystem. -]] - -[[ - "You should send a 100-continue to ask for the rest of the body if required." -][ - The Beast interface supporst this functionality (by allowing this - special case of partial message parsing and serialization). Specifically, - it lets callers read the request up to just before the body, - and let callers write the request up to just before the body. However, - making use of this behavior is up to callers (since Beast is low level). -]] - -[[ - "What about HTTP/2?" -][ - Many reviewers feel that HTTP/2 support is an essential feature of - a HTTP library. The authors agree that HTTP/2 is important but also - feel that the most sensible implementation is one that does not re-use - the same network reading and writing interface for 2 as that for 1.0 - and 1.1. - - The Beast HTTP message model is suitable for HTTP/2 and can be re-used. - The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x - as an explicit goal. A parser can simply emit full headers after - decoding the compressed HTTP/2 headers. The stream ID is not logically - part of the message but rather message metadata and should be - communicated out-of-band (see below). HTTP/2 sessions begin with a - traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket - upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives - to perform this handshake. - - Free functions for HTTP/2 sessions are not possible because of the - requirement to maintain per-session state. For example, to decode the - compressed headers. Or to remember and respect the remote peer's window - settings. The authors propose that a HTTP/2 implementation be written - as a separate class template, similar to the `websocket::stream` but with - additional interfaces to support version 2 features. We feel that - Beast.HTTP offers enough useful functionality to justify inclusion, - so that developers can take advantage of it right away instead of - waiting. -]] - -] - -[endsect] - - - -[section:websocket WebSocket FAQ] - -[variablelist - -[[ - What about message compression? -][ - Beast WebSocket supports the permessage-deflate extension described in - [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. - The library comes with a header-only, C++11 port of ZLib's "deflate" codec - used in the implementation of the permessage-deflate extension. -]] - -[[ - Where is the TLS/SSL interface? -][ - The `websocket::stream` wraps the socket or stream that you provide - (for example, a `boost::asio::ip::tcp::socket` or a - `boost::asio::ssl::stream`). You establish your TLS connection using the - interface on `ssl::stream` like shown in all of the Asio examples, then - construct your `websocket::stream` around it. It works perfectly fine; - Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of - interface paint on the `ssl::stream`. - - The WebSocket implementation [*does] provide support for shutting down - the TLS connection through the use of the ADL compile-time virtual functions - [link beast.ref.websocket__teardown `teardown`] and - [link beast.ref.websocket__async_teardown `async_teardown`]. These will - properly close the connection as per rfc6455 and overloads are available - for TLS streams. Callers may provide their own overloads of these functions - for user-defined next layer types. -]] - -] - -[endsect] - - - -[section:websocketpp Comparison to Zaphoyd Studios WebSocket++] - -[variablelist - -[[ - How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], - an alternate header-only WebSocket implementation? -][ - [variablelist - - [[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: - - [table - [ - [[@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][ - - 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__. - - 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); - ... - 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); - ```] - [ - // - ] - ]]]] - - [[10. Flow Control][ - - 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. - - 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. - - [table - [ - [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][ - - 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::request_v1 const& request); - ```] - [ - // - ] - ]]]] - - ] -]] - -] - -[endsect] +[include design/http_message.qbk] +[include design/http_comparison.qbk] +[include design/websocket_zaphoyd.qbk] +[include design/review.qbk] [endsect] diff --git a/doc/design/http_comparison.qbk b/doc/design/http_comparison.qbk new file mode 100644 index 00000000..f063bf4f --- /dev/null +++ b/doc/design/http_comparison.qbk @@ -0,0 +1,454 @@ +[/ + 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:http_comparison HTTP Comparison to Other Libraries] + +There are a few C++ published libraries which implement some of the HTTP +protocol. We analyze the message model chosen by those libraries and discuss +the advantages and disadvantages relative to Beast. + +The general strategy used by the author to evaluate external libraries is +as follows: + +* Review the message model. Can it represent a complete request or + response? What level of allocator support is present? How much + customization is possible? + +* Review the stream abstraction. This is the type of object, such as + a socket, which may be used to parse or serialize (i.e. read and write). + Can user defined types be specified? What's the level of conformance to + to Asio or Networking-TS concepts? + +* Check treatment of buffers. Does the library manage the buffers or + or can users provide their own buffers? + +* How does the library handle corner cases such as trailers, + Expect: 100-continue, or deferred commitment of the body type? + +[note + Declarations examples from external libraries have been edited: + portions have been removed for simplification. +] + + + +[heading cpp-netlib] + +[@https://github.com/cpp-netlib/cpp-netlib/tree/092cd570fb179d029d1865aade9f25aae90d97b9 [*cpp-netlib]] +is a network programming library previously intended for Boost but not +having gone through formal review. As of this writing it still uses the +Boost name, namespace, and directory structure although the project states +that Boost acceptance is no longer a goal. The library is based on Boost.Asio +and bills itself as ['"a collection of network related routines/implementations +geared towards providing a robust cross-platform networking library"]. It +cites ['"Common Message Type"] as a feature. As of the branch previous +linked, it uses these declarations: +``` +template +struct basic_message { + public: + typedef Tag tag; + + typedef typename headers_container::type headers_container_type; + typedef typename headers_container_type::value_type header_type; + typedef typename string::type string_type; + + headers_container_type& headers() { return headers_; } + headers_container_type const& headers() const { return headers_; } + + string_type& body() { return body_; } + string_type const& body() const { return body_; } + + string_type& source() { return source_; } + string_type const& source() const { return source_; } + + string_type& destination() { return destination_; } + string_type const& destination() const { return destination_; } + + private: + friend struct detail::directive_base; + friend struct detail::wrapper_base >; + + mutable headers_container_type headers_; + mutable string_type body_; + mutable string_type source_; + mutable string_type destination_; +}; +``` + +This container is the base class template used to represent HTTP messages. +It uses a "tag" type style specializations for a variety of trait classes, +allowing for customization of the various parts of the message. For example, +a user specializes `headers_container` to determine what container type +holds the header fields. We note some problems with the container declaration: + +* The header and body containers may only be default-constructed. + +* No stateful allocator support. + +* There is no way to defer the commitment of the type for `body_` to + after the headers are read in. + +* The message model includes a "source" and "destination." This is + extraneous metadata associated with the connection which is not part + of the HTTP protocol specification and belongs elsewhere. + +* The use of `string_type` (a customization point) for source, + destination, and body suggests that `string_type` models a + [*ForwardRange] whose `value_type` is `char`. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward + range destroys information conveyed by the __ConstBufferSequence__ + and __MutableBufferSequence__ used in dynamic buffers. The consequence + is that cpp-netlib implementations will be less efficient than an + equivalent __N4588__ conforming implementation. + +* The library uses specializatons of `string` to change the type + of string used everywhere, including the body, field name and value + pairs, and extraneous metadata such as source and destination. The + user may only choose a single type: field name, field values, and + the body container will all use the same string type. This limits + utility of the customization point. The library's use of the string + trait is limited to selecting between `std::string` and `std::wstring`. + We do not find this use-case compelling given the limitations. + +* The specialized trait classes generate a proliferation of small + additional framework types. To specialize traits, users need to exit + their namespace and intrude into the `boost::network::http` namespace. + The way the traits are used in the library limits the usefulness + of the traits to trivial purpose. + +* The `string customization point constrains user defined body types + to few possible strategies. There is no way to represent an HTTP message + body as a filename with accompanying algorithms to store or retrieve data + from the file system. + +The design of the message container in this library is cumbersome +with its system of customization using trait specializations. The +use of these customizations is extremely limited due to the way they +are used in the container declaration, making the design overly +complex without corresponding benefit. + + + +[heading Boost.HTTP] + +[@https://github.com/BoostGSoC14/boost.http/tree/45fc1aa828a9e3810b8d87e669b7f60ec100bff4 [*boost.http]] +is a library resulting from the 2014 Google Summer of Code. It was submitted +for a Boost formal review and rejected in 2015. It is based on Boost.Asio, +and development on the library has continued to the present. As of the branch +previously linked, it uses these message declarations: +``` +template +struct basic_message +{ + typedef Headers headers_type; + typedef Body body_type; + + headers_type &headers(); + + const headers_type &headers() const; + + body_type &body(); + + const body_type &body() const; + + headers_type &trailers(); + + const headers_type &trailers() const; + +private: + headers_type headers_; + body_type body_; + headers_type trailers_; +}; + +typedef basic_message> message; + +template +struct is_message>: public std::true_type {}; +``` + +* This container cannot model a complete message. The ['start-line] items + (method and target for requests, reason-phrase for responses) are + communicated out of band, as is the ['http-version]. A function that + operates on the message including the start line requires additional + parameters. This is evident in one of the + [@https://github.com/BoostGSoC14/boost.http/blob/45fc1aa828a9e3810b8d87e669b7f60ec100bff4/example/basic_router.cpp#L81 example programs]. + The `500` and `"OK"` arguments represent the response ['status-code] and + ['reason-phrase] respectively: + ``` + ... + http::message reply; + ... + self->socket.async_write_response(500, string_ref("OK"), reply, yield); + ``` + +* `headers_`, `body_`, and `trailers_` may only be default-constructed, + since there are no explicitly declared constructors. + +* There is no way to defer the commitment of the [*Body] type to after + the headers are read in. This is related to the previous limitation + on default-construction. + +* No stateful allocator support. This follows from the previous limitation + on default-construction. Buffers for start-line strings must be + managed externally from the message object since they are not members. + +* The trailers are stored in a separate object. Aside from the combinatorial + explosion of the number of additional constructors necessary to fully + support arbitrary forwarded parameter lists for each of the headers, body, + and trailers members, the requirement to know in advance whether a + particular HTTP field will be located in the headers or the trailers + poses an unnecessary complication for general purpose functions that + operate on messages. + +* The declarations imply that `std::vector` is a model of [*Body]. + More formally, that a body is represented by the [*ForwardRange] + concept whose `value_type` is an 8-bit integer. This representation + is less than ideal, considering that the library is built on + Boost.Asio. Adapting a __DynamicBuffer__ to the required forward range + destroys information conveyed by the __ConstBufferSequence__ and + __MutableBufferSequence__ used in dynamic buffers. The consequence is + that Boost.HTTP implementations will be less efficient when dealing + with body containers than an equivalent __N4588__ conforming + implementation. + +* The [*Body] customization point constrains user defined types to + very limited implementation strategies. For example, there is no way + to represent an HTTP message body as a filename with accompanying + algorithms to store or retrieve data from the file system. + +This representation addresses a narrow range of use cases. It has +limited potential for customization and performance. It is more difficult +to use because it excludes the start line fields from the model. + + + +[heading C++ REST SDK (cpprestsdk)] + +[@https://github.com/Microsoft/cpprestsdk/tree/381f5aa92d0dfb59e37c0c47b4d3771d8024e09a [*cpprestsdk]] +is a Microsoft project which ['"...aims to help C++ developers connect to and +interact with services"]. It offers the most functionality of the libraries +reviewed here, including support for Websocket services using its websocket++ +dependency. It can use native APIs such as HTTP.SYS when building Windows +based applications, and it can use Boost.Asio. The WebSocket module uses +Boost.Asio exclusively. + +As cpprestsdk is developed by a large corporation, it contains quite a bit +of functionality and necessarily has more interfaces. We will break down +the interfaces used to model messages into more manageable pieces. This +is the container used to store the HTTP header fields: +``` +class http_headers +{ +public: + ... + +private: + std::map m_headers; +}; +``` + +This declaration is quite bare-bones. We note the typical problems of +most field containers: + +* The container may only be default-constructed. + +* No support for allocators, stateful or otherwise. + +* There are no customization points at all. + +Now we analyze the structure of +the larger message container. The library uses a handle/body idiom. There +are two public message container interfaces, one for requests (`http_request`) +and one for responses (`http_response`). Each interface maintains a private +shared pointer to an implementation class. Public member function calls +are routed to the internal implementation. This is the first implementation +class, which forms the base class for both the request and response +implementations: +``` +namespace details { + +class http_msg_base +{ +public: + http_headers &headers() { return m_headers; } + + _ASYNCRTIMP void set_body(const concurrency::streams::istream &instream, const utf8string &contentType); + + /// Set the stream through which the message body could be read + void set_instream(const concurrency::streams::istream &instream) { m_inStream = instream; } + + /// Set the stream through which the message body could be written + void set_outstream(const concurrency::streams::ostream &outstream, bool is_default) { m_outStream = outstream; m_default_outstream = is_default; } + + const pplx::task_completion_event & _get_data_available() const { return m_data_available; } + +protected: + /// Stream to read the message body. + concurrency::streams::istream m_inStream; + + /// stream to write the msg body + concurrency::streams::ostream m_outStream; + + http_headers m_headers; + bool m_default_outstream; + + /// The TCE is used to signal the availability of the message body. + pplx::task_completion_event m_data_available; +}; +``` + +To understand these declarations we need to first understand that cpprestsdk +uses the asynchronous model defined by Microsoft's +[@https://msdn.microsoft.com/en-us/library/dd504870.aspx [*Concurrency Runtime]]. +Identifiers from the [@https://msdn.microsoft.com/en-us/library/jj987780.aspx [*`pplx` namespace]] +define common asynchronous patterns such as tasks and events. The +`concurrency::streams::istream` parameter and `m_data_available` data member +indicates a lack of separation of concerns. The representation of HTTP messages +should not be conflated with the asynchronous model used to serialize or +parse those messages in the message declarations. + +The next declaration forms the complete implementation class referenced by the +handle in the public interface (which follows after): +``` +/// Internal representation of an HTTP request message. +class _http_request final : public http::details::http_msg_base, public std::enable_shared_from_this<_http_request> +{ +public: + _ASYNCRTIMP _http_request(http::method mtd); + + _ASYNCRTIMP _http_request(std::unique_ptr server_context); + + http::method &method() { return m_method; } + + const pplx::cancellation_token &cancellation_token() const { return m_cancellationToken; } + + _ASYNCRTIMP pplx::task reply(const http_response &response); + +private: + + // Actual initiates sending the response, without checking if a response has already been sent. + pplx::task _reply_impl(http_response response); + + http::method m_method; + + std::shared_ptr m_progress_handler; +}; + +} // namespace details +``` + +As before, we note that the implementation class for HTTP requests concerns +itself more with the mechanics of sending the message asynchronously than +it does with actually modeling the HTTP message as described in __rfc7230__: + +* The constructor accepting `std::unique_ptrmethod(); } + + void set_method(const http::method &method) const { _m_impl->method() = method; } + + /// Extract the body of the request message as a string value, checking that the content type is a MIME text type. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_string(bool ignore_content_type = false) + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->extract_string(ignore_content_type); }); + } + + /// Extracts the body of the request message into a json value, checking that the content type is application/json. + /// A body can only be extracted once because in some cases an optimization is made where the data is 'moved' out. + pplx::task extract_json(bool ignore_content_type = false) const + { + auto impl = _m_impl; + return pplx::create_task(_m_impl->_get_data_available()).then([impl, ignore_content_type](utility::size64_t) { return impl->_extract_json(ignore_content_type); }); + } + + /// Sets the body of the message to the contents of a byte vector. If the 'Content-Type' + void set_body(const std::vector &body_data); + + /// Defines a stream that will be relied on to provide the body of the HTTP message when it is + /// sent. + void set_body(const concurrency::streams::istream &stream, const utility::string_t &content_type = _XPLATSTR("application/octet-stream")); + + /// Defines a stream that will be relied on to hold the body of the HTTP response message that + /// results from the request. + void set_response_stream(const concurrency::streams::ostream &stream); + { + return _m_impl->set_response_stream(stream); + } + + /// Defines a callback function that will be invoked for every chunk of data uploaded or downloaded + /// as part of the request. + void set_progress_handler(const progress_handler &handler); + +private: + friend class http::details::_http_request; + friend class http::client::http_client; + + std::shared_ptr _m_impl; +}; +``` + +It is clear from this declaration that the goal of the message model in +this library is driven by its use-case (interacting with REST servers) +and not to model HTTP messages generally. We note problems similar to +the other declarations: + +* There are no compile-time customization points at all. The only + customization is in the `concurrency::streams::istream` and + `concurrency::streams::ostream` reference parameters. Presumably, + these are abstract interfaces which may be subclassed by users + to achieve custom behaviors. + +* The extraction of the body is conflated with the asynchronous model. + +* No way to define an allocator for the container used when extracting + the body. + +* A body can only be extracted once, limiting the use of this container + when using a functional programming style. + +* Setting the body requires either a vector or a `concurrency::streams::istream`. + No user defined types are possible. + +* The HTTP request container conflates HTTP response behavior (see the + `set_response_stream` member). Again this is likely purpose-driven but + the lack of separation of concerns limits this library to only the + uses explicitly envisioned by the authors. + +The general theme of the HTTP message model in cpprestsdk is "no user +definable customizations". There is no allocator support, and no +separation of concerns. It is designed to perform a specific set of +behaviors. In other words, it does not follow the open/closed principle. + +Tasks in the Concurrency Runtime operate in a fashion similar to +`std::future`, but with some improvements such as continuations which +are not yet in the C++ standard. The costs of using a task based +asynchronous interface instead of completion handlers is well +documented: synchronization points along the call chain of composed +task operations which cannot be optimized away. See: +[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3747.pdf +[*A Universal Model for Asynchronous Operations]] (Kohlhoff). + +[endsect] diff --git a/doc/design/http_message.qbk b/doc/design/http_message.qbk new file mode 100644 index 00000000..d7e5226d --- /dev/null +++ b/doc/design/http_message.qbk @@ -0,0 +1,284 @@ +[/ + 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:http_message HTTP Message Container] + +In this section we describe the problem of modeling HTTP messages and explain +how the library arrived at its solution, with a discussion of the benefits +and drawbacks of the design choices. The goal for creating a message model +is to create a container with value semantics, possibly movable and/or +copyable, that completely describes the message. More formally, the container +has all of the information necessary for the serialization algorithms to +represent the entire message as a series of octets, and that the parsing +algorithms store all of the captured information about an entire message +in the container. + +In addition to capturing the entirety of a message, we would like the message +container to contain enough customization points to permit the following: +allocator support, user-defined containers to represent header fields, and +user-defined containers to represent the body. And finally, because requests +and responses have slightly different information in the ['start-line], we +would like the containers for requests and responses to be represented by +different types. + +Here is our first attempt at declaring some message containers: + +[table +[[ +``` +/// An HTTP request +template +struct request +{ + int version; + std::string method; + std::string target; + Fields fields; + typename Body::value_type body; +}; +``` +][ +``` +/// An HTTP response +template +struct response +{ + int version; + int status; + std::string reason; + Fields fields; + typename Body::value_type body; +}; +``` +]] +] + +Here we have accomplished a few things. Request and response objects are +different types. The user can choose the container used to represent the +fields. And the user can choose the [*Body] type, which is a concept +defining not only the type of `body` member but also the algorithms used +to transfer information in and out of that member when performing +serialization and parsing. + +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 +ovious 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. We can avoid those complexities +by renaming the containers and making them partial specializations of a +single class, like this: + +``` +/// An HTTP message +template +struct message; + +/// An HTTP request +template +struct message +{ + int version; + std::string method; + std::string target; + Fields fields; + typename Body::value_type body; +}; + +/// An HTTP response +template +struct response +{ + int version; + int status; + std::string reason; + Fields fields; + typename Body::value_type body; +}; +``` + +Now we can declare a function which takes any message as a parameter: +``` +template +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`. + +Often, in non-trivial HTTP applications, we want to read the HTTP header +and examine its contents before choosing a top for [*Body]. To accomplish +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: +``` +/// An HTTP message header +template +struct header; + +/// An HTTP request header +template +struct header +{ + int version; + std::string method; + std::string target; + Fields fields; +}; + +/// An HTTP response header +template +struct header +{ + int version; + int status; + std::string reason; + Fields fields; +}; + +/// An HTTP message +template +struct message : header +{ + typename Body::value_type body; + + /// Construct from a `header` + message(header&& h); +}; + +``` + +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: +``` +template +void f(header& msg); +``` + +Until now we have not given significant consideration to the constructors +of the `message` class. But to achieve all our goals we will need to make +sure that there are enough constructor overloads to not only provide for +the special copy and move members if the instantiated types support it, +but also allow the fields container and body container to be constructed +with arbitrary variadic lists of parameters. This allows those members +to properly support allocators. + +The solution used in the library is to treat the message like a `std::pair` +for the purposes of construction, except that instead of `first` and `last` +we have `fields` and `body`. This means that single-argument constructors +for those fields should be accessible as they are with `std::pair`, and +that a mechanism identical to the pair's use of `std::piecewise_construct` +should be provided. Those constructors are too complex to repeat here, but +interested readers can view the declarations in the corresponding header +file. + +There is now significant progress with our message container but a stumbling +block remains. The presence of `std::string` members is an obstacle to our +goal of making messages allocator-aware. One obvious solution is to add an +allocator to the template parameter list of the header and message classes, +and allow the string based members to construct with instances of those +allocators. This is unsatisfying because of the combinatorial explosion of +constructor variations needed to 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 make allocators workable, we make a simple change to the interface and +then engineer a clever concession. First, the interface change: +``` +/// An HTTP request header +template +struct header +{ + int version; + string_view method() const; + void method(string_view); + string_view target(); const; + void target(string_view); + Fields fields; +}; + +/// An HTTP response header +template +struct header +{ + int version; + int status; + string_view reason() const; + void reason(string_view); + Fields fields; +}; +``` + +This approach replaces public data members with traditional accessors +using non-owning references to string buffers. 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): +``` +/// An HTTP response header +template +struct header +{ + int version; + int status; + + auto + reason() const -> decltype(std::declval().reason()) const + { + return fields.reason(); + } + + template + void + reason(Value&& value) + { + fields.reason(std::forward(value)); + } + + Fields fields; +``` + +An advantage of this technique is that user-provided implementations of +[*Fields] may use arbitrary representation strategies. For example, instead +of explicitly storing the method as a string, store it as an enumerated +integer with a lookup table of static strings for known message types. + +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: +``` +/// An HTTP header +template +struct header; + +/// An HTTP message +template +struct message; +``` + +This container is also capable of representing complete HTTP/2 messages. +Not because it was explicitly designed for, but because the IETF wanted to +preserve message compatibility with HTTP/1. Aside from version specific +fields such as Connection, the contents of HTTP/1 and HTTP/2 messages are +identical even though their serialized representation is considerably +different. The message model presented in this library is ready for HTTP/2. + +In conclusion, this representation for the message container is well thought +out, provides comprehensive flexibility, and avoids the necessity of defining +additional traits classes. User declarations of functions that accept headers +or messages as parameters are easy to write in a variety of ways to accomplish +different results, without forcing cumbersome SFINAE declarations everywhere. + +[endsect] diff --git a/doc/review.qbk b/doc/design/review.qbk similarity index 51% rename from doc/review.qbk rename to doc/design/review.qbk index 652835e1..bc234493 100644 --- a/doc/review.qbk +++ b/doc/design/review.qbk @@ -5,7 +5,7 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:review For Reviewers] +[section:review 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 @@ -58,6 +58,12 @@ about Beast and other HTTP libraries that have gone through formal review. [[ "There's no HTTP/2 support yet!" ][ + Many reviewers feel that HTTP/2 support is an essential feature of + a HTTP library. The authors agree that HTTP/2 is important but also + feel that the most sensible implementation is one that does not re-use + the same network reading and writing interface for 2 as that for 1.0 + and 1.1. + The Beast HTTP message model was designed with the new protocol in mind and should be evaluated in that context. There are plans to add HTTP/2 in the future, but there is no rush to do so. @@ -66,6 +72,16 @@ about Beast and other HTTP libraries that have gone through formal review. It is the author's position that there is sufficient value in Beast's HTTP/1-only implementation that the lack of HTTP/2 should not be a barrier to acceptance. + + The Beast HTTP message model is suitable for HTTP/2 and can be re-used. + The IETF HTTP Working Group adopted message compatiblity with HTTP/1.x + as an explicit goal. A parser can simply emit full headers after + decoding the compressed HTTP/2 headers. The stream ID is not logically + part of the message but rather message metadata and should be + communicated out-of-band (see below). HTTP/2 sessions begin with a + traditional HTTP/1.1 Upgrade similar in fashion to the WebSocket + upgrade. An HTTP/2 implementation can use existing Beast.HTTP primitives + to perform this handshake. ]] [[ "This should work with standalone-Asio!" @@ -106,7 +122,7 @@ about Beast and other HTTP libraries that have gone through formal review. is lifted from another extremely popular project which has performance as a design goal (see https://github.com/h2o/picohttpparser). - From: http://www.boost.org/development/requirements.html + From: [@http://www.boost.org/development/requirements.html] "Aim first for clarity and correctness; optimization should be only a secondary concern in most Boost libraries." @@ -143,6 +159,121 @@ about Beast and other HTTP libraries that have gone through formal review. +[[ + "Some more advanced examples, e.g. including TLS with client/server + certificates would help." +][ + The HTTP interface doesn't try to reinvent the wheel, it just uses + the `boost::asio::ip::tcp::socket` or `boost::asio::ssl::stream` that + you set up beforehand. Callers use the interfaces already existing + on those objects to make outgoing connections, accept incoming connections, + or establish TLS sessions with certificates. We find the available Asio + examples for performing these tasks sufficient. +]] + +[[ + "A built-in HTTP router?" +][ + We presume this means a facility to match expressions against the URI + in HTTP requests, and dispatch them to calling code. The authors feel + that this is a responsibility of higher level code. Beast does + not try to offer a web server. +]] + +[[ + "HTTP Cookies? Forms/File Uploads?" +][ + Cookies, or managing these types of HTTP headers in general, is the + responsibility of higher levels. Beast.HTTP just tries to get complete + messages to and from the calling code. It deals in the HTTP headers just + enough to process the message body and leaves the rest to callers. However, + for forms and file uploads the symmetric interface of the message class + allows HTTP requests to include arbitrary body types including those needed + to upload a file or fill out a form. +]] + +[[ + "...supporting TLS (is this a feature? If not this would be a show-stopper), + etc." +][ + Beast does not provide direct facilities for implementing TLS connections; + however, the interfaces already existing on the `boost::asio::ssl::stream` + are available and can be used to establish secure connections. Then, + functions like `http::read` or `http::async_write` can work with those + encrypted connections with no problem. +]] + +[[ + "There should also be more examples of how to integrate the http service + with getting files from the file system, generating responses CGI-style" +][ + The design goal for the library is to not try to invent a web server. + We feel that there is a strong need for a basic implementation that + models the HTTP message and provides functions to send and receive them + over Asio. Such an implementation should serve as a building block upon + which higher abstractions such as the aforementioned HTTP service or + cgi-gateway can be built. + + One of the example programs implements a simple HTTP server that + delivers files from the filesystem. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + The Beast interface supporst this functionality (by allowing this + special case of "split" message parsing and serialization). Specifically, + it lets callers read the request up to just before the body, + and let callers write the request up to just before the body. However, + making use of this behavior is up to callers (since Beast is low level). +]] + + + +[[ + "I would also like to see instances of this library being used + in production. That would give some evidence that the design + works in practice." +][ + Beast.HTTP and Beast.WebSocket are production ready and currently + running on public servers receiving traffic and handling millions of + dollars worth of financial transactions daily. The servers run [*rippled], + open source software ([@https://github.com/ripple/rippled repository]) + implementing the + [@https://ripple.com/files/ripple_consensus_whitepaper.pdf [*Ripple Consensus Protocol]], + technology provided by [@http://ripple.com Ripple]. +]] + + + +[[ + What about WebSocket message compression? +][ + Beast WebSocket supports the permessage-deflate extension described in + [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00]. + The library comes with a header-only, C++11 port of ZLib's "deflate" codec + used in the implementation of the permessage-deflate extension. +]] +[[ + Where is the WebSocket TLS/SSL interface? +][ + The `websocket::stream` wraps the socket or stream that you provide + (for example, a `boost::asio::ip::tcp::socket` or a + `boost::asio::ssl::stream`). You establish your TLS connection using the + interface on `ssl::stream` like shown in all of the Asio examples, then + construct your `websocket::stream` around it. It works perfectly fine; + Beast.WebSocket doesn't try to reinvent the wheel or put a fresh coat of + interface paint on the `ssl::stream`. + + The WebSocket implementation [*does] provide support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.websocket__teardown `teardown`] and + [link beast.ref.websocket__async_teardown `async_teardown`]. These will + properly close the connection as per rfc6455 and overloads are available + for TLS streams. Callers may provide their own overloads of these functions + for user-defined next layer types. +]] + ] [endsect] diff --git a/doc/design/websocket_zaphoyd.qbk b/doc/design/websocket_zaphoyd.qbk new file mode 100644 index 00000000..5868651e --- /dev/null +++ b/doc/design/websocket_zaphoyd.qbk @@ -0,0 +1,431 @@ +[/ + 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_zaphoyd Comparison to Zaphoyd Studios WebSocket++] + +[variablelist + +[[ + How does this compare to [@https://www.zaphoyd.com/websocketpp websocketpp], + an alternate header-only WebSocket implementation? +][ + [variablelist + + [[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: + + [table + [ + [[@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][ + + 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__. + + 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); + ... + 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); + ```] + [ + // + ] + ]]]] + + [[10. Flow Control][ + + 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. + + 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. + + [table + [ + [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][ + + 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::request_v1 const& request); + ```] + [ + // + ] + ]]]] + + ] +]] + +] + +[endsect] diff --git a/doc/master.qbk b/doc/master.qbk index e564998f..6d3bcfef 100644 --- a/doc/master.qbk +++ b/doc/master.qbk @@ -88,13 +88,8 @@ provides implementations of the HTTP and WebSocket protocols. [[ [link beast.design Design] ][ - Design rationale, answers to questions, and - other library comparisons. - ]] - [[ - [link beast.review For Reviewers] - ][ - Participants in Beast's formal review should read this first. + Design rationale, answers to questions, library comparisons, + and special considerations for Boost Formal Review participants. ]] [[ [link beast.ref Reference] @@ -114,7 +109,6 @@ provides implementations of the HTTP and WebSocket protocols. [include websocket.qbk] [include examples.qbk] [include design.qbk] -[include review.qbk] [section:ref Reference] [xinclude quickref.xml]