From 54f6f0ceba6e928930a306a6b062cf6b820f0ec3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 25 Feb 2016 16:17:19 -0500 Subject: [PATCH] Beast.WebSocket: Beast.WebSocket provides developers with a robust WebSocket implementation built on Boost.Asio with a consistent asynchronous model using a modern C++ approach. --- Jamroot | 18 +- TODO.txt | 8 + beast/asio/buffers_debug.h | 1 - beast/unity/beast_wsproto_unity.cpp | 21 + beast/wsproto.h | 30 + beast/wsproto/README.md | 232 +++ beast/wsproto/detail/debug.h | 88 + beast/wsproto/detail/decorator.h | 112 ++ beast/wsproto/detail/error.h | 104 + beast/wsproto/detail/frame.h | 375 ++++ beast/wsproto/detail/hybi13.h | 66 + beast/wsproto/detail/invokable.h | 168 ++ beast/wsproto/detail/mask.h | 389 ++++ beast/wsproto/detail/socket_base.h | 141 ++ beast/wsproto/detail/utf8_checker.h | 184 ++ beast/wsproto/error.h | 72 + beast/wsproto/impl/accept_op.ipp | 158 ++ beast/wsproto/impl/close_op.ipp | 198 ++ beast/wsproto/impl/error.ipp | 39 + beast/wsproto/impl/handshake_op.ipp | 170 ++ beast/wsproto/impl/read_frame_op.ipp | 519 +++++ beast/wsproto/impl/read_op.ipp | 144 ++ beast/wsproto/impl/response_op.ipp | 147 ++ beast/wsproto/impl/socket.ipp | 815 ++++++++ beast/wsproto/impl/ssl.ipp | 170 ++ beast/wsproto/impl/teardown.ipp | 183 ++ beast/wsproto/impl/write_frame_op.ipp | 277 +++ beast/wsproto/impl/write_op.ipp | 150 ++ beast/wsproto/option.h | 305 +++ beast/wsproto/rfc6455.h | 163 ++ beast/wsproto/socket.h | 1190 ++++++++++++ beast/wsproto/src/test/async_echo_peer.h | 285 +++ .../src/test/beast_wsproto_ws_echo_test.cpp | 94 + .../src/test/beast_wsproto_ws_test.cpp | 362 ++++ beast/wsproto/src/test/sync_echo_peer.h | 182 ++ beast/wsproto/ssl.h | 86 + beast/wsproto/static_string.h | 337 ++++ beast/wsproto/teardown.h | 161 ++ doc/.gitignore | 4 + doc/Jamfile | 83 + doc/beast.dox | 363 ++++ doc/beast.qbk | 177 ++ doc/boostbook.dtd | 439 +++++ doc/design.qbk | 213 ++ doc/http.qbk | 215 ++ doc/images/beast.png | Bin 0 -> 42936 bytes doc/images/beast.psd | Bin 0 -> 166000 bytes doc/images/body.png | Bin 0 -> 6505 bytes doc/images/body.psd | Bin 0 -> 161406 bytes doc/images/message.png | Bin 0 -> 9532 bytes doc/images/message.psd | Bin 0 -> 204871 bytes doc/index.xml | 13 + doc/makeqbk.sh | 13 + doc/quickref.xml | 163 ++ doc/reference.xsl | 1725 +++++++++++++++++ doc/types.qbk | 446 +++++ doc/wsproto.qbk | 391 ++++ examples/Jamfile | 5 + examples/wsproto_async_echo_peer.h | 267 +++ examples/wsproto_echo.cpp | 53 + examples/wsproto_sync_echo_peer.h | 179 ++ test/{asio => }/Jamfile | 6 +- test/{asio => }/append_buffers.cpp | 0 test/{asio => }/asio.cpp | 0 test/{asio => }/async_completion.cpp | 0 test/{asio => }/basic_streambuf.cpp | 0 test/beast_wsproto_ws_test.cpp | 358 ++++ test/{asio => }/bind_handler.cpp | 0 test/{asio => }/buffers_adapter.cpp | 0 test/{asio => }/buffers_debug.cpp | 0 test/{asio => }/consuming_buffers.cpp | 0 test/{asio => }/handler_alloc.cpp | 0 test/{asio => }/placeholders.cpp | 0 test/{asio => }/prepare_buffers.cpp | 0 test/ssl_error.cpp | 43 + test/{asio => }/static_streambuf.cpp | 0 test/{asio => }/streambuf.cpp | 9 +- test/{asio => }/streambuf_readstream.cpp | 0 test/{asio => }/temp_buffer.cpp | 0 test/{asio => }/type_check.cpp | 0 80 files changed, 13296 insertions(+), 13 deletions(-) create mode 100644 TODO.txt create mode 100644 beast/unity/beast_wsproto_unity.cpp create mode 100644 beast/wsproto.h create mode 100644 beast/wsproto/README.md create mode 100644 beast/wsproto/detail/debug.h create mode 100644 beast/wsproto/detail/decorator.h create mode 100644 beast/wsproto/detail/error.h create mode 100644 beast/wsproto/detail/frame.h create mode 100644 beast/wsproto/detail/hybi13.h create mode 100644 beast/wsproto/detail/invokable.h create mode 100644 beast/wsproto/detail/mask.h create mode 100644 beast/wsproto/detail/socket_base.h create mode 100644 beast/wsproto/detail/utf8_checker.h create mode 100644 beast/wsproto/error.h create mode 100644 beast/wsproto/impl/accept_op.ipp create mode 100644 beast/wsproto/impl/close_op.ipp create mode 100644 beast/wsproto/impl/error.ipp create mode 100644 beast/wsproto/impl/handshake_op.ipp create mode 100644 beast/wsproto/impl/read_frame_op.ipp create mode 100644 beast/wsproto/impl/read_op.ipp create mode 100644 beast/wsproto/impl/response_op.ipp create mode 100644 beast/wsproto/impl/socket.ipp create mode 100644 beast/wsproto/impl/ssl.ipp create mode 100644 beast/wsproto/impl/teardown.ipp create mode 100644 beast/wsproto/impl/write_frame_op.ipp create mode 100644 beast/wsproto/impl/write_op.ipp create mode 100644 beast/wsproto/option.h create mode 100644 beast/wsproto/rfc6455.h create mode 100644 beast/wsproto/socket.h create mode 100644 beast/wsproto/src/test/async_echo_peer.h create mode 100644 beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp create mode 100644 beast/wsproto/src/test/beast_wsproto_ws_test.cpp create mode 100644 beast/wsproto/src/test/sync_echo_peer.h create mode 100644 beast/wsproto/ssl.h create mode 100644 beast/wsproto/static_string.h create mode 100644 beast/wsproto/teardown.h create mode 100644 doc/.gitignore create mode 100644 doc/Jamfile create mode 100644 doc/beast.dox create mode 100644 doc/beast.qbk create mode 100644 doc/boostbook.dtd create mode 100644 doc/design.qbk create mode 100644 doc/http.qbk create mode 100644 doc/images/beast.png create mode 100644 doc/images/beast.psd create mode 100644 doc/images/body.png create mode 100644 doc/images/body.psd create mode 100644 doc/images/message.png create mode 100644 doc/images/message.psd create mode 100644 doc/index.xml create mode 100644 doc/makeqbk.sh create mode 100644 doc/quickref.xml create mode 100644 doc/reference.xsl create mode 100644 doc/types.qbk create mode 100644 doc/wsproto.qbk create mode 100644 examples/wsproto_async_echo_peer.h create mode 100644 examples/wsproto_echo.cpp create mode 100644 examples/wsproto_sync_echo_peer.h rename test/{asio => }/Jamfile (85%) rename test/{asio => }/append_buffers.cpp (100%) rename test/{asio => }/asio.cpp (100%) rename test/{asio => }/async_completion.cpp (100%) rename test/{asio => }/basic_streambuf.cpp (100%) create mode 100644 test/beast_wsproto_ws_test.cpp rename test/{asio => }/bind_handler.cpp (100%) rename test/{asio => }/buffers_adapter.cpp (100%) rename test/{asio => }/buffers_debug.cpp (100%) rename test/{asio => }/consuming_buffers.cpp (100%) rename test/{asio => }/handler_alloc.cpp (100%) rename test/{asio => }/placeholders.cpp (100%) rename test/{asio => }/prepare_buffers.cpp (100%) create mode 100644 test/ssl_error.cpp rename test/{asio => }/static_streambuf.cpp (100%) rename test/{asio => }/streambuf.cpp (97%) rename test/{asio => }/streambuf_readstream.cpp (100%) rename test/{asio => }/temp_buffer.cpp (100%) rename test/{asio => }/type_check.cpp (100%) diff --git a/Jamroot b/Jamroot index e18ad6b7..aed04ce2 100644 --- a/Jamroot +++ b/Jamroot @@ -34,7 +34,16 @@ else if [ os.name ] = HAIKU lib network ; } -build-project test/asio ; +if [ os.name ] = NT +{ + lib ssl : : ssleay32 ; + lib crypto : : libeay32 ; +} +else +{ + lib ssl ; + lib crypto ; +} project beast : requirements @@ -49,6 +58,8 @@ project beast multi static static + gcc:-std=c++14 + clang:-std=c++14 LINUX:_XOPEN_SOURCE=600 LINUX:_GNU_SOURCE=1 SOLARIS:_XOPEN_SOURCE=500 @@ -68,7 +79,10 @@ project beast msvc:_SCL_SECURE_NO_WARNINGS=1 msvc:_CRT_SECURE_NO_WARNINGS=1 : usage-requirements - . + . : build-dir bin ; + +build-project test ; +build-project examples ; diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 00000000..d5767cb8 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,8 @@ +* kick out non-beast code + +* redo directory structure + +* Change build options to C++11 only + +* Replace Jamroot with Jamfile + diff --git a/beast/asio/buffers_debug.h b/beast/asio/buffers_debug.h index 19689c37..e5361799 100644 --- a/beast/asio/buffers_debug.h +++ b/beast/asio/buffers_debug.h @@ -27,7 +27,6 @@ namespace beast { namespace debug { template -static std::string buffers_to_string(Buffers const& bs) { diff --git a/beast/unity/beast_wsproto_unity.cpp b/beast/unity/beast_wsproto_unity.cpp new file mode 100644 index 00000000..c41bd2ac --- /dev/null +++ b/beast/unity/beast_wsproto_unity.cpp @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include diff --git a/beast/wsproto.h b/beast/wsproto.h new file mode 100644 index 00000000..542b308a --- /dev/null +++ b/beast/wsproto.h @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_H_INCLUDED +#define BEAST_WSPROTO_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/beast/wsproto/README.md b/beast/wsproto/README.md new file mode 100644 index 00000000..22fc6a51 --- /dev/null +++ b/beast/wsproto/README.md @@ -0,0 +1,232 @@ +# Beast.WSProto + +-------------------------------------------------------------------------------- + +Beast.WSProto provides developers with a robust WebSocket implementation +built on Boost.Asio with a consistent asynchronous model using a modern +C++ approach. + +## Introduction + +Today's web applications increasingly rely on alternatives to standard HTTP +to achieve performance and/or responsiveness. While WebSocket implementations +are widely available in common web development languages such as Javascript, +good implementations in C++ are scarce. A survey of existing C++ WebSocket +solutions reveals interfaces which have performance limitations, place +unecessary restrictions on callers, exhibit excess complexity, and fail to +take advantage of C++ features or the underlying network transport. + +Beast.WSProto is built on Boost.Asio, a robust cross platform networking +framework that is part of Boost and also offered as a standalone library. +A proposal to add networking functionality to the C++ standard library, +based on Boost.Asio, is under consideration by the standards committee. +Since the final approved networking interface for the C++ standard library +will likely closely resemble the current interface of Boost.Asio, it is +logical for Beast.WSProto to use Boost.Asio as its network transport. + +Beast.WSProto addresses the following goals: + +* **Ease of Use.** WSProto offers only one socket object, whose interface +resembles that of Boost.Asio socket as closely as possible. Users familiar +with Boost.Asio will be immediately comfortable using a `wsproto::socket`. + +* **Flexibility.** Library interfaces should provide callers with maximum +flexibility in implementation; Important decisions such as how to manage +buffers or be notified of completed asynchronous operations should be made +by callers not the library. + +* **Performance.** The implementation should achieve the highest level +of performance possible, with no penalty for using abstractions. + +* **Scalability.** The library should facilitate the development of +network applications that scale to thousands of concurrent connections. + +* **Efficiency.** The library should support techniques such as +scatter-gather I/O, and allow programs to minimise data copying. + +* **Basis for further abstraction.** The library should permit the +development of other libraries that provide higher levels of abstraction. + +Beast.WSProto takes advantage of Boost.Asio's universal Asynchronous +model, handler allocation, and handler invocation hooks. Calls to wsproto +asynchronous initiation functions allow callers the choice of using a +completion handler, stackful or stackless coroutines, futures, or user +defined customizations (for example, Boost.Fiber). The implementation +uses handler invocation hooks (`asio_handler_invoke`), providing +execution guarantees on composed operations in a manner identical to +Boost.Asio. The implementation also uses handler allocation hooks +(`asio_handler_allocate`) when allocating memory internally for composed +operations. + +There is no need for inheritance or virtual members in `wsproto::socket`. +All operations are templated and transparent to the compiler, allowing for +maximum inlining and optimization. + +## Usage + +All examples and identifiers mentioned in this document are written as +if the following declarations are in effect: +```C++ +#include +using namespace beast; +using namespace boost::asio; +``` + +### Creating a Socket + +To participate in a WebSocket connection, callers create an instance +of `wsproto::socket` templated on the `Stream` argument, which must meet +the requirements of `AsyncReadStream`, `AsyncWriteStream`, `SyncReadStream`, +and `SyncWriteStream`. Examples of types that meet these requirements are +`ip::tcp::socket` and `ssl::stream<...>`: +```c++ +io_service ios; +wsproto::socket ws1(ios); // owns the socket + +ssl::context ctx(ssl::context::sslv23); +wsproto::socket> wss(ios, ctx); // owns the socket + +ip::tcp::socket sock(ios); +wsproto::socket ws2(sock); // does not own the socket +``` + +### Connection Establishment + +Callers are responsible for performing tasks such as connection establishment +before attempting websocket activities. +```c++ +io_service ios; +wsproto::socket ws(ios); +ws.next_layer().connect(ip::tcp::endpoint( + ip::tcp::address::from_string("127.0.0.1"), 80)); +``` + +### WebSocket Handshake + +After the connection is established, the socket may be used to initiate +or accept a WebSocket Update request. + +```c++ +// send a WebSocket Upgrade request. +ws.handshake(); +``` + +### Sending and Receiving Messages + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface: +```c++ +void echo(wsproto::socket& ws) +{ + streambuf sb; + wsproto::opcode op; + wsproto::read(ws, op, sb); + wsproto::write(ws, op, sb.data()); + sb.consume(sb.size()); +} +``` + +Alternatively, callers may process incoming message data +incrementally: +```c++ +void echo(wsproto::socket& ws) +{ + streambuf sb; + wsproto::msg_info mi{}; + for(;;) + { + ws.read_some(mi, sb); + if(mi.fin) + break; + } + wsproto::write(ws, op, sb.data()); +} +``` + +### Asynchronous Completions, Coroutines, and Futures + +Asynchronous versions are available for all functions: +```c++ +wsproto::async_read(ws, sb, std::bind( + &on_read, beast::asio::placeholders::error)); +``` + +Calls to WSProto asynchronous initiation functions support +asio-style completion handlers, and other completion tokens +such as support for coroutines or futures: +```c++ +void echo(wsproto::socket& ws, + boost::asio::yield_context yield) +{ + wsproto::async_read(ws, sb, yield); + std::future fut = + wsproto::async_write(ws, sb.data(), boost::use_future); + ... +} +``` + +## Implementation + +### Buffers + +Because calls to read WebSocket data may return a variable amount of bytes, +the interface to calls that read data require an object that meets the +requirements of `Streambuf`. This concept is modeled on +`boost::asio::basic_streambuf`, which meets the requirements of `Streambuf` +defined below. + +The `Streambuf` concept is intended to permit the following implementation +strategies: + +* A single contiguous character array, which is reallocated as necessary to + accommodate changes in the size of the byte sequence. This is the + implementation approach currently used in `boost::asio::basic_streambuf`. +* A sequence of one or more byte arrays, where each array is of the same + size. Additional byte array objects are appended to the sequence to + accommodate changes in the size of the byte sequence. +* A sequence of one or more byte arrays of varying sizes. Additional byte + array objects are appended to the sequence to accommodate changes in the + size of the byte sequence. This is the implementation approach currently + used in `beast::basic_streambuf`. + +#### `Streambuf` requirements: + +In the table below, `X` denotes a class, `a` denotes a value +of type `X`, `n` denotes a value convertible to `std::size_t`, +and `U` and `T` denote unspecified types. + +expression | return | type assertion/note/pre/post-condition +------------------------- | ------------- | -------------------------------------- +`X::const_buffers_type` | `T` | `T` meets the requirements for `ConstBufferSequence`. +`X::mutable_buffers_type` | `U` | `U` meets the requirements for `MutableBufferSequence`. +`a.commit(n)` | | Moves bytes from the output sequence to the input sequence. +`a.consume(n)` | | Removes bytes from the input sequence. +`a.data()` | `T` | Returns a list of buffers that represents the input sequence. +`a.prepare(n)` | `U` | Returns a list of buffers that represents the output sequence, with the given size. +`a.size()` | `std::size_t` | Returns the size of the input sequence. +`a.max_size()` | `std::size_t` | Returns the maximum size of the `Streambuf`. + +### Thread Safety + +Like a regular asio socket, a `wsproto::socket` is not thread safe. Callers are +responsible for synchronizing operations on the socket using an implicit or +explicit strand, as per the Asio documentation. A `wsproto::socket` supports +one active read and one active write at the same time (caller initiated close, +ping, and pong operations count as a write). + +### Buffering + +The implementation does not perform queueing or buffering of messages. If desired, +these features should be implemented by callers. The impact of this design is +that the caller is in full control of the allocation strategy used to store +data and the back-pressure applied on the read and write side of the underlying +TCP/IP connection. + +### The `io_service` + +The creation and operation of the `boost::asio::io_service` associated with the +Stream object underlying the `wsproto::socket` is completely left up to the +user of the library, permitting any implementation strategy including one that +does not require threads for environments where threads are unavailable. +Beast.WSProto itself does not use or require threads. diff --git a/beast/wsproto/detail/debug.h b/beast/wsproto/detail/debug.h new file mode 100644 index 00000000..a3696c9f --- /dev/null +++ b/beast/wsproto/detail/debug.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DEBUG_H_INCLUDED +#define BEAST_WSPROTO_DEBUG_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +std::string +to_hex(boost::asio::const_buffer b) +{ + using namespace boost::asio; + std::stringstream ss; + auto p = buffer_cast(b); + auto n = buffer_size(b); + while(n--) + { + ss << + std::setfill('0') << + std::setw(2) << + std::hex << int(*p++) << " "; + } + return ss.str(); +} + +template +std::string +to_hex(Buffers const& bs) +{ + std::string s; + for(auto const& b : bs) + s.append(to_hex(boost::asio::const_buffer(b))); + return s; +} + +template +std::string +buffers_to_string(Buffers const& bs) +{ + using namespace boost::asio; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; +} + +template +std::string +format(std::string s) +{ + auto const w = 84; + for(int n = w*(s.size()/w); n>0; n-=w) + s.insert(n, 1, '\n'); + return s; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/decorator.h b/beast/wsproto/detail/decorator.h new file mode 100644 index 00000000..7078849c --- /dev/null +++ b/beast/wsproto/detail/decorator.h @@ -0,0 +1,112 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DECORATOR_H_INCLUDED +#define BEAST_WSPROTO_DECORATOR_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +using request_type = http::request; + +using response_type = http::response; + +struct abstract_decorator +{ + virtual + ~abstract_decorator() = default; + + virtual + void + operator()(request_type& req) = 0; + + virtual + void + operator()(response_type& resp) = 0; +}; + +template +class decorator : public abstract_decorator +{ + T t_; + +public: + decorator() = default; + + decorator(T&& t) + : t_(std::move(t)) + { + } + + decorator(T const& t) + : t_(t) + { + } + + void + operator()(request_type& req) override + { + t_(req); + } + + void + operator()(response_type& resp) override + { + t_(resp); + } +}; + +struct default_decorator +{ + static + char const* + version() + { + return "Beast.WSProto/1.0"; + } + + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", version()); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", version()); + } +}; + +using decorator_type = + std::unique_ptr; + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/error.h b/beast/wsproto/detail/error.h new file mode 100644 index 00000000..f19e1bc0 --- /dev/null +++ b/beast/wsproto/detail/error.h @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DETAIL_ERROR_H_INCLUDED +#define BEAST_WSPROTO_DETAIL_ERROR_H_INCLUDED + +#include + +namespace boost { +namespace system { +template<> +struct is_error_code_enum +{ + static bool const value = true; +}; +} // system +} // boost + +namespace beast { +namespace wsproto { +namespace detail { + +class error_category : public boost::system::error_category +{ +public: + const char* + name() const noexcept override + { + return "wsproto"; + } + + std::string + message(int ev) const override + { + switch(static_cast(ev)) + { + case error::closed: return "WebSocket connection closed normally"; + case error::failed: return "WebSocket connection failed due to a protocol violation"; + case error::handshake_failed: return "WebSocket Upgrade handshake failed"; + case error::keep_alive: return "WebSocket Upgrade handshake failed but connection is still open"; + + case error::response_malformed: return "malformed HTTP response"; + case error::response_failed: return "upgrade request failed"; + case error::response_denied: return "upgrade request denied"; + case error::request_malformed: return "malformed HTTP request"; + case error::request_invalid: return "upgrade request invalid"; + case error::request_denied: return "upgrade request denied"; + default: + return "wsproto.error"; + } + } + + boost::system::error_condition + default_error_condition(int ev) const noexcept override + { + return boost::system::error_condition(ev, *this); + } + + bool + equivalent(int ev, + boost::system::error_condition const& condition + ) const noexcept override + { + return condition.value() == ev && + &condition.category() == this; + } + + bool + equivalent(error_code const& error, int ev) const noexcept override + { + return error.value() == ev && + &error.category() == this; + } +}; + +inline +boost::system::error_category const& +get_error_category() +{ + static detail::error_category const cat{}; + return cat; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/frame.h b/beast/wsproto/detail/frame.h new file mode 100644 index 00000000..cd9db078 --- /dev/null +++ b/beast/wsproto/detail/frame.h @@ -0,0 +1,375 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_FRAME_H_INCLUDED +#define BEAST_WSPROTO_FRAME_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// Contents of a WebSocket frame header +struct frame_header +{ + opcode op; + bool fin; + bool mask; + bool rsv1; + bool rsv2; + bool rsv3; + std::uint64_t len; + std::uint32_t key; +}; + +// holds the largest possible frame header +using fh_streambuf = + static_streambuf_n<14>; + +// holds the largest possible control frame +using frame_streambuf = + static_streambuf_n< 2 + 8 + 4 + 125 >; + +inline +bool constexpr +is_reserved(opcode op) +{ + return + (op >= opcode::rsv3 && op <= opcode::rsv7) || + (op >= opcode::crsvb && op <= opcode::crsvf); +} + +inline +bool constexpr +is_valid(opcode op) +{ + return op <= opcode::crsvf; +} + +inline +bool constexpr +is_control(opcode op) +{ + return op >= opcode::close; +} + +// Returns `true` if a close code is valid +inline +bool +is_valid(close_code code) +{ + auto const v = static_cast< + std::uint16_t>(code); + switch(v) + { + case 1000: + case 1001: + case 1002: + case 1003: + case 1007: + case 1008: + case 1009: + case 1010: + case 1011: + case 1012: + case 1013: + return true; + + // explicitly reserved + case 1004: + case 1005: + case 1006: + case 1014: + case 1015: + return false; + } + // reserved + if(v >= 1016 && v <= 2999) + return false; + // not used + if(v >= 0 && v <= 999) + return false; + return true; +} + +//------------------------------------------------------------------------------ + +// Write frame header to streambuf +// +template +void +write(Streambuf& sb, frame_header const& fh) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using namespace boost::endian; + std::size_t n; + std::uint8_t b[14]; + b[0] = (fh.fin ? 0x80 : 0x00) | static_cast(fh.op); + b[1] = fh.mask ? 0x80 : 0x00; + if (fh.len <= 125) + { + b[1] |= fh.len; + n = 2; + } + else if (fh.len <= 65535) + { + b[1] |= 126; + ::new(&b[2]) big_uint16_buf_t{ + (std::uint16_t)fh.len}; + n = 4; + } + else + { + b[1] |= 127; + ::new(&b[2]) big_uint64_buf_t{fh.len}; + n = 10; + } + if(fh.mask) + { + little_uint32_buf_t key(fh.key); + std::copy(key.data(), + key.data() + 4, &b[n]); + n += 4; + } + sb.commit(buffer_copy( + sb.prepare(n), buffer(b))); +} + +// Read fixed frame header +// Requires at least 2 bytes +// +template +std::size_t +read_fh1(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + std::size_t need; + fh.len = b[1] & 0x7f; + switch(fh.len) + { + case 126: need = 2; break; + case 127: need = 8; break; + default: + need = 0; + } + if((fh.mask = (b[1] & 0x80) != 0)) + need += 4; + fh.op = static_cast(b[0] & 0x0f); + fh.fin = (b[0] & 0x80) != 0; + fh.rsv1 = (b[0] & 0x40) != 0; + fh.rsv2 = (b[0] & 0x20) != 0; + fh.rsv3 = (b[0] & 0x10) != 0; + // invalid length for control message + if(is_control(fh.op) && fh.len > 125) + { + code = close_code::protocol_error; + return 0; + } + // reserved bits not cleared + if(fh.rsv1 || fh.rsv2 || fh.rsv3) + { + code = close_code::protocol_error; + return 0; + } + // reserved opcode + if(is_reserved(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // invalid opcode + // (only in locally generated headers) + if(! is_valid(fh.op)) + { + code = close_code::protocol_error; + return 0; + } + // fragmented control message + if(is_control(fh.op) && ! fh.fin) + { + code = close_code::protocol_error; + return 0; + } + // unmasked frame from client + if(role == role_type::server && ! fh.mask) + { + code = close_code::protocol_error; + return 0; + } + // masked frame from server + if(role == role_type::client && fh.mask) + { + code = close_code::protocol_error; + return 0; + } + code = close_code::none; + return need; +} + +// Decode variable frame header from stream +// +template +void +read_fh2(frame_header& fh, Streambuf& sb, + role_type role, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + switch(fh.len) + { + case 126: + { + std::uint8_t b[2]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.len = reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value(); + // length not canonical + if(fh.len < 126) + { + code = close_code::protocol_error; + return; + } + break; + } + case 127: + { + std::uint8_t b[8]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.len = reinterpret_cast< + big_uint64_buf_t const*>(&b[0])->value(); + // length not canonical + if(fh.len < 65536) + { + code = close_code::protocol_error; + return; + } + break; + } + } + if(fh.mask) + { + std::uint8_t b[4]; + assert(buffer_size(sb.data()) >= sizeof(b)); + sb.consume(buffer_copy(buffer(b), sb.data())); + fh.key = reinterpret_cast< + little_uint32_buf_t const*>(&b[0])->value(); + } + code = close_code::none; +} + +// Read data from buffers +// This is for ping and pong payloads +// +template +void +read(ping_payload_type& data, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + assert(buffer_size(bs) <= data.max_size()); + data.resize(buffer_size(bs)); + buffer_copy(mutable_buffers_1{ + data.data(), data.size()}, bs); +} + +// Read close_reason, return true on success +// This is for the close payload +// +template +void +read(close_reason& cr, + Buffers const& bs, close_code& code) +{ + using boost::asio::buffer; + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using namespace boost::endian; + auto n = buffer_size(bs); + assert(n <= 125); + if(n == 0) + { + cr = close_reason{}; + code = close_code::none; + return; + } + if(n == 1) + { + code = close_code::protocol_error; + return; + } + consuming_buffers cb(bs); + { + std::uint8_t b[2]; + buffer_copy(buffer(b), cb); + cr.code = static_cast( + reinterpret_cast< + big_uint16_buf_t const*>(&b[0])->value()); + cb.consume(2); + n -= 2; + if(! is_valid(cr.code)) + { + code = close_code::protocol_error; + return; + } + } + if(n > 0) + { + cr.reason.resize(n); + buffer_copy(buffer(&cr.reason[0], n), cb); + if(! detail::check_utf8( + cr.reason.data(), cr.reason.size())) + { + code = close_code::protocol_error; + return; + } + } + else + { + cr.reason = ""; + } + code = close_code::none; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/hybi13.h b/beast/wsproto/detail/hybi13.h new file mode 100644 index 00000000..ec7d5655 --- /dev/null +++ b/beast/wsproto/detail/hybi13.h @@ -0,0 +1,66 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_HYBI13_H_INCLUDED +#define BEAST_WSPROTO_HYBI13_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +std::string +make_sec_ws_key(Gen& g) +{ + union U + { + std::array a4; + std::array a16; + }; + U u; + for(int i = 0; i < 4; ++i) + u.a4[i] = g(); + return base64_encode(u.a16.data(), u.a16.size()); +} + +template +std::string +make_sec_ws_accept(boost::string_ref const& key) +{ + std::string s(key.data(), key.size()); + s += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + beast::sha_hasher h; + h(s.data(), s.size()); + auto const digest = static_cast< + beast::sha_hasher::result_type>(h); + return base64_encode(digest.data(), digest.size()); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/invokable.h b/beast/wsproto/detail/invokable.h new file mode 100644 index 00000000..14024689 --- /dev/null +++ b/beast/wsproto/detail/invokable.h @@ -0,0 +1,168 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_INVOKABLE_H_INCLUDED +#define BEAST_WSPROTO_INVOKABLE_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// "Parks" a composed operation, to invoke later +// +class invokable +{ + struct base + { + base() = default; + base(base &&) = default; + virtual ~base() = default; + virtual void move(void* p) = 0; + virtual void operator()() = 0; + }; + + template + struct holder : base + { + F f; + + holder(holder&&) = default; + + template + explicit + holder(U&& u) + : f(std::forward(u)) + { + } + + void + move(void* p) override + { + ::new(p) holder(std::move(*this)); + } + + void + operator()() override + { + F f_(std::move(f)); + this->~holder(); + // invocation of f_() can + // assign a new invokable. + f_(); + } + }; + + struct exemplar + { + std::shared_ptr _; + void operator()(){} + }; + + using buf_type = std::uint8_t[ + sizeof(holder)]; + + bool b_ = false; + alignas(holder) buf_type buf_; + +public: +#ifndef NDEBUG + ~invokable() + { + // Engaged invokables must be invoked before + // destruction otherwise the io_service + // invariants are broken w.r.t completions. + assert(! b_); + } +#endif + + invokable() = default; + invokable(invokable const&) = delete; + invokable& operator=(invokable const&) = delete; + + invokable(invokable&& other) + : b_(other.b_) + { + if(other.b_) + { + other.get().move(buf_); + other.b_ = false; + } + } + + invokable& + operator=(invokable&& other) + { + // Engaged invokables must be invoked before + // assignment otherwise the io_service + // invariants are broken w.r.t completions. + assert(! b_); + + if(other.b_) + { + b_ = true; + other.get().move(buf_); + other.b_ = false; + } + return *this; + } + + template + void + emplace(F&& f); + + void + maybe_invoke() + { + if(b_) + { + b_ = false; + get()(); + } + } + +private: + base& + get() + { + return *reinterpret_cast(buf_); + } +}; + +template +void +invokable::emplace(F&& f) +{ + static_assert(sizeof(buf_type) >= sizeof(holder), + "buffer too small"); + assert(! b_); + ::new(buf_) holder(std::forward(f)); + b_ = true; +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/mask.h b/beast/wsproto/detail/mask.h new file mode 100644 index 00000000..22026866 --- /dev/null +++ b/beast/wsproto/detail/mask.h @@ -0,0 +1,389 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_DETAIL_MASKGEN_H_INCLUDED +#define BEAST_WSPROTO_DETAIL_MASKGEN_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +// Pseudo-random source of mask keys +// +template +class maskgen_t +{ + std::mt19937 g_; + +public: + using result_type = typename std::mt19937::result_type; + + maskgen_t(maskgen_t const&) = delete; + maskgen_t& operator=(maskgen_t const&) = delete; + + maskgen_t(); + + result_type + operator()() noexcept; + + void + rekey(); +}; + +template +maskgen_t<_>::maskgen_t() +{ + rekey(); +} + +template +auto +maskgen_t<_>::operator()() noexcept -> + result_type +{ + for(;;) + if(auto key = g_()) + return key; +} + +template +void +maskgen_t<_>::rekey() +{ + std::random_device rng; + std::array e; + for(auto& i : e) + i = rng(); + std::seed_seq ss(e.begin(), e.end()); + g_.seed(ss); +} + +using maskgen = maskgen_t<>; + +//------------------------------------------------------------------------------ + +//using prepared_key_type = std::size_t; +using prepared_key_type = std::uint32_t; +//using prepared_key_type = std::uint64_t; + +inline +void +prepare_key(std::uint32_t& prepared, std::uint32_t key) +{ + prepared = key; +} + +inline +void +prepare_key(std::uint64_t& prepared, std::uint32_t key) +{ + prepared = + (static_cast(key) << 32) | key; +} + +template +inline +std::enable_if_t::value, T> +rol(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast((t << n) | + (static_cast>(t) >> (bits - n))); +} + +template +inline +std::enable_if_t::value, T> +ror(T t, unsigned n = 1) +{ + auto constexpr bits = + static_cast( + sizeof(T) * CHAR_BIT); + n &= bits-1; + return static_cast((t << (bits - n)) | + (static_cast>(t) >> n)); +} + +// 32-bit Uuoptimized +// +template +void +mask_inplace_safe( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit unoptimized +// +template +void +mask_inplace_safe( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *p ^= key ; ++p; + *p ^= (key >> 8); ++p; + *p ^= (key >>16); ++p; + *p ^= (key >>24); ++p; + *p ^= (key >>32); ++p; + *p ^= (key >>40); ++p; + *p ^= (key >>48); ++p; + *p ^= (key >>56); ++p; + } + n %= sizeof(key); + switch(n) + { + case 7: p[6] ^= (key >>16); + case 6: p[5] ^= (key >> 8); + case 5: p[4] ^= key; + case 4: p[3] ^= (key >>24); + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 32-bit optimized +template +void +mask_inplace_32( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + auto m = reinterpret_cast< + uintptr_t>(p) % sizeof(key); + switch(m) + { + case 1: *p ^= key ; ++p; --n; + case 2: *p ^= (key >> 8); ++p; --n; + case 3: *p ^= (key >>16); ++p; --n; + key = ror(key, m * 8); + case 0: + break; + } + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint32_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit optimized +// +template +void +mask_inplace_64( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + auto m = reinterpret_cast< + uintptr_t>(p) % sizeof(key); + switch(m) + { + case 1: *p ^= key ; ++p; --n; + case 2: *p ^= (key >> 8); ++p; --n; + case 3: *p ^= (key >>16); ++p; --n; + case 4: *p ^= (key >>24); ++p; --n; + case 5: *p ^= (key >>32); ++p; --n; + case 6: *p ^= (key >>40); ++p; --n; + case 7: *p ^= (key >>48); ++p; --n; + key = ror(key, m * 8); + case 0: + break; + } + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint64_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 32-bit x86 optimized +// +template +void +mask_inplace_x86( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint32_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +// 64-bit amd64 optimized +// +template +void +mask_inplace_amd( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + auto n = buffer_size(b); + auto p = buffer_cast(b); + for(auto i = n / sizeof(key); i; --i) + { + *reinterpret_cast< + std::uint64_t*>(p) ^= key; + p += sizeof(key); + } + n %= sizeof(key); + switch(n) + { + case 7: p[6] ^= (key >>16); + case 6: p[5] ^= (key >> 8); + case 5: p[4] ^= key; + case 4: p[3] ^= (key >>24); + case 3: p[2] ^= (key >>16); + case 2: p[1] ^= (key >> 8); + case 1: p[0] ^= key; + key = ror(key, n*8); + default: + break; + } +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint32_t& key) +{ + mask_inplace_safe(b, key); + //mask_inplace_32(b, key); + //mask_inplace_x86(b, key); +} + +inline +void +mask_inplace( + boost::asio::mutable_buffer const& b, + std::uint64_t& key) +{ + mask_inplace_safe(b, key); + //mask_inplace_64(b, key); + //mask_inplace_amd(b, key); +} + +// Apply mask in place +// +template +void +mask_inplace( + MutableBuffers const& bs, KeyType& key) +{ + for(auto const& b : bs) + mask_inplace(b, key); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/socket_base.h b/beast/wsproto/detail/socket_base.h new file mode 100644 index 00000000..11abd8b5 --- /dev/null +++ b/beast/wsproto/detail/socket_base.h @@ -0,0 +1,141 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SOCKET_BASE_H_INCLUDED +#define BEAST_WSPROTO_SOCKET_BASE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace detail { + +template +inline +void +maybe_throw(error_code const& ec, String const&) +{ + if(ec) + throw boost::system::system_error{ec}; +} + +template +static +std::size_t +clamp(UInt x) +{ + if(x >= std::numeric_limits::max()) + return std::numeric_limits::max(); + return static_cast(x); +} + +template +static +std::size_t +clamp(UInt x, std::size_t limit) +{ + if(x >= limit) + return limit; + return static_cast(x); +} + +//------------------------------------------------------------------------------ + +struct socket_base +{ +protected: + struct op {}; + + detail::maskgen maskgen_; // source of mask keys + decorator_type d_; // adorns http messages + bool keep_alive_ = false; // close on failed upgrade + role_type role_; // server or client + bool error_ = false; // non-zero ec was delivered + + std::size_t rd_msg_max_ = + 16 * 1024 * 1024; // max message size + detail::frame_header rd_fh_; // current frame header + detail::prepared_key_type rd_key_; // prepared masking key + detail::utf8_checker rd_utf8_check_;// for current text msg + std::uint64_t rd_size_; // size of the current message so far + std::uint64_t rd_need_ = 0; // bytes left in msg frame payload + opcode rd_opcode_; // opcode of current msg + bool rd_cont_ = false; // expecting a continuation frame + bool rd_close_ = false; // got close frame + op* rd_block_ = nullptr; // op currently reading + + std::size_t + wr_frag_size_ = 16 * 1024; // size of auto-fragments + std::size_t wr_buf_size_ = 4096; // write buffer size + opcode wr_opcode_ = opcode::text; // outgoing message type + bool wr_close_ = false; // sent close frame + bool wr_cont_ = false; // next write is continuation frame + op* wr_block_ = nullptr; // op currenly writing + + invokable rd_op_; // invoked after write completes + invokable wr_op_; // invoked after read completes + close_reason cr_; // set from received close frame + + socket_base() + : d_(std::make_unique< + decorator>()) + { + } + + socket_base(socket_base&&) = default; + socket_base(socket_base const&) = delete; + socket_base& operator=(socket_base&&) = default; + socket_base& operator=(socket_base const&) = delete; + + template + void + prepare_fh(close_code& code); + + template + void + write_close(Streambuf& sb, + close_reason const& rc); + + template + void + write_ping(Streambuf& sb, opcode op, + ping_payload_type const& data); +}; + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/detail/utf8_checker.h b/beast/wsproto/detail/utf8_checker.h new file mode 100644 index 00000000..7d35134c --- /dev/null +++ b/beast/wsproto/detail/utf8_checker.h @@ -0,0 +1,184 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_UTF8_CHECKER_H_INCLUDED +#define BEAST_WSPROTO_UTF8_CHECKER_H_INCLUDED + +#include +#include +#include // DEPRECATED + +namespace beast { +namespace wsproto { +namespace detail { + +// Code adapted from +// http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +/* + Copyright (c) 2008-2009 Bjoern Hoehrmann + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject + to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * +*/ +template +class utf8_checker_t +{ + // Table for the UTF8 decode state machine + using lut_type = std::uint8_t[400]; + static + lut_type const& + lut() + { + // 400 elements + static std::uint8_t constexpr tab[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df + 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef + 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff + 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 + 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 + 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 + 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1 // s7..s8 + }; + return tab; + } + + std::uint32_t state_ = 0; + std::uint32_t codepoint_ = 0; + +public: + utf8_checker_t() = default; + utf8_checker_t(utf8_checker_t&&) = default; + utf8_checker_t(utf8_checker_t const&) = default; + utf8_checker_t& operator=(utf8_checker_t&&) = default; + utf8_checker_t& operator=(utf8_checker_t const&) = default; + + void + reset(); + + // Returns `true` on success + bool + write(void const* buffer, std::size_t size); + + // Returns `true` on success + template + bool + write(BufferSequence const& bs); + + // Returns `true` on success + bool + finish(); +}; + +template +void +utf8_checker_t<_>::reset() +{ + state_ = 0; + codepoint_ = 0; +} + +template +bool +utf8_checker_t<_>::write(void const* buffer, std::size_t size) +{ + auto p = static_cast(buffer); + auto plut = &lut()[0]; + while(size) + { + auto const byte = *p; + auto const type = plut[byte]; + if(state_) + codepoint_ = (byte & 0x3fu) | (codepoint_ << 6); + else + codepoint_ = (0xff >> type) & byte; + state_ = plut[256 + state_ * 16 + type]; + if(state_ == 1) + { + reset(); + return false; + } + ++p; + --size; + } + return true; +} + +template +template +bool +utf8_checker_t<_>::write(BufferSequence const& bs) +{ + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + for (auto const& b : bs) + if(! write(buffer_cast(b), + buffer_size(b))) + return false; + return true; +} + +template +bool +utf8_checker_t<_>::finish() +{ + auto const success = state_ == 0; + reset(); + return success; +} + +using utf8_checker = utf8_checker_t<>; + +template +bool +check_utf8(char const* p, std::size_t n) +{ + utf8_checker c; + if(! c.write(p, n)) + return false; + return c.finish(); +} + +} // detail +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/error.h b/beast/wsproto/error.h new file mode 100644 index 00000000..1ccb658a --- /dev/null +++ b/beast/wsproto/error.h @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ERROR_H_INCLUDED +#define BEAST_WSPROTO_ERROR_H_INCLUDED + +#include + +namespace beast { +namespace wsproto { + +using error_code = boost::system::error_code; + +/// Error values +enum class error +{ + /// Both sides performed a WebSocket close + closed = 1, + + /// WebSocket connection failed, protocol violation + failed, + + /// Upgrade request failed, connection is closed + handshake_failed, + + /// Upgrade request failed, but connection is still open + keep_alive, + + /// HTTP response is malformed + response_malformed, + + /// HTTP response failed the upgrade + response_failed, + + /// Upgrade request denied for invalid fields. + response_denied, + + /// Upgrade request is malformed + request_malformed, + + /// Upgrade request fields incorrect + request_invalid, + + /// Upgrade request denied + request_denied +}; + +error_code +make_error_code(error e); + +} // wsproto +} // beast + +#include + +#endif diff --git a/beast/wsproto/impl/accept_op.ipp b/beast/wsproto/impl/accept_op.ipp new file mode 100644 index 00000000..c82e24cd --- /dev/null +++ b/beast/wsproto/impl/accept_op.ipp @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ACCEPT_OP_H_INCLUDED +#define BEAST_WSPROTO_ACCEPT_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// read and respond to an upgrade request +// +template +template +class socket::accept_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + http::request req; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + Buffers const& buffers) + : ws(ws_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + ws.stream_.buffer().commit(buffer_copy( + ws.stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + } + }; + + std::shared_ptr d_; + +public: + accept_op(accept_op&&) = default; + accept_op(accept_op const&) = default; + + template + accept_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code const& ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, accept_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(accept_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, accept_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::accept_op:: +operator()(error_code const& ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read message + d.state = 1; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.req, + std::move(*this)); + return; + + // got message + case 1: + // respond to request + response_op{ + std::move(d.h), d.ws, d.req, true}; + return; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/close_op.ipp b/beast/wsproto/impl/close_op.ipp new file mode 100644 index 00000000..f17e050e --- /dev/null +++ b/beast/wsproto/impl/close_op.ipp @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_CLOSE_OP_H_INCLUDED +#define BEAST_WSPROTO_CLOSE_OP_H_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +// send the close message and wait for the response +// +template +template +class socket::close_op +{ + using alloc_type = + handler_alloc; + using fb_type = + detail::frame_streambuf; + using fmb_type = + typename fb_type::mutable_buffers_type; + + struct data : op + { + socket& ws; + close_reason cr; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + close_reason const& cr_) + : ws(ws_) + , cr(cr_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + ws.template write_close< + static_streambuf>(fb, cr); + } + }; + + std::shared_ptr d_; + +public: + close_op(close_op&&) = default; + close_op(close_op const&) = default; + + template + close_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void + operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, close_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(close_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, close_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::close_op::operator()( + error_code ec, std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.rd_op_.template emplace< + close_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + d.state = 2; + break; + + // resume + case 1: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + d.state = 2; + break; + + case 2: + // send close + d.state = 99; + assert(! d.ws.wr_close_); + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/error.ipp b/beast/wsproto/impl/error.ipp new file mode 100644 index 00000000..85a7fe95 --- /dev/null +++ b/beast/wsproto/impl/error.ipp @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ERROR_IPP_H_INCLUDED +#define BEAST_WSPROTO_ERROR_IPP_H_INCLUDED + +#include + +namespace beast { +namespace wsproto { + +inline +error_code +make_error_code(error e) +{ + return error_code( + static_cast(e), detail::get_error_category()); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/handshake_op.ipp b/beast/wsproto/impl/handshake_op.ipp new file mode 100644 index 00000000..2f6538fb --- /dev/null +++ b/beast/wsproto/impl/handshake_op.ipp @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_HANDSHAKE_OP_H_INCLUDED +#define BEAST_WSPROTO_HANDSHAKE_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// send the upgrade request and process the response +// +template +template +class socket::handshake_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + Handler h; + std::string key; + http::request req; + http::response resp; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + boost::string_ref const& host, + boost::string_ref const& resource) + : ws(ws_) + , h(std::forward(h_)) + , req(ws.build_request(host, resource, key)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + handshake_op(handshake_op&&) = default; + handshake_op(handshake_op const&) = default; + + template + handshake_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, handshake_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(handshake_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, handshake_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::handshake_op< + Handler>::operator()(error_code ec, + std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + // send http upgrade + d.state = 1; + // VFALCO Do we need the ability to move + // a message on the async_write? + http::async_write(d.ws.stream_, + d.req, std::move(*this)); + return; + } + + // sent upgrade + case 1: + // read http response + d.state = 2; + http::async_read(d.ws.next_layer_, + d.ws.stream_.buffer(), d.resp, + std::move(*this)); + return; + + // got response + case 2: + { + d.ws.do_response(d.resp, d.key, ec); + // call handler + d.state = 99; + break; + } + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/read_frame_op.ipp b/beast/wsproto/impl/read_frame_op.ipp new file mode 100644 index 00000000..9c3d1f01 --- /dev/null +++ b/beast/wsproto/impl/read_frame_op.ipp @@ -0,0 +1,519 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_READ_FRAME_OP_H_INCLUDED +#define BEAST_WSPROTO_READ_FRAME_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Reads a single message frame, +// processes any received control frames. +// +template +template +class socket::read_frame_op +{ + using alloc_type = + handler_alloc; + + using fb_type = + detail::frame_streambuf; + + using fmb_type = + typename fb_type::mutable_buffers_type; + + using smb_type = + typename Streambuf::mutable_buffers_type; + + struct data : op + { + socket& ws; + frame_info& fi; + Streambuf& sb; + smb_type smb; + Handler h; + fb_type fb; + fmb_type fmb; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + frame_info& fi_, Streambuf& sb_) + : ws(ws_) + , fi(fi_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_frame_op(read_frame_op&&) = default; + read_frame_op(read_frame_op const&) = default; + + template + read_frame_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code const& ec) + { + (*this)(ec, 0); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_frame_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::read_frame_op:: +operator()(error_code ec,std::size_t bytes_transferred, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + close_code code; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + if(d.ws.rd_need_ > 0) + { + d.state = 1; + break; + } + d.state = 2; + break; + + case 1: + // read payload + d.state = 3; + d.smb = d.sb.prepare( + detail::clamp(d.ws.rd_need_)); + d.ws.stream_.async_read_some( + d.smb, std::move(*this)); + return; + + case 2: + // read fixed header + d.state = 5; + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(2), std::move(*this)); + return; + + // got payload + case 3: + { + d.ws.rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, d.smb); + if(d.ws.rd_fh_.mask) + detail::mask_inplace(pb, d.ws.rd_key_); + if(d.ws.rd_opcode_ == opcode::text) + { + if(! d.ws.rd_utf8_check_.write(pb) || + (d.ws.rd_need_ == 0 && d.ws.rd_fh_.fin && + ! d.ws.rd_utf8_check_.finish())) + { + // invalid utf8 + d.state = 16; + code = close_code::bad_payload; + break; + } + } + d.sb.commit(bytes_transferred); + d.state = 4; + break; + } + + // call handler + case 4: + d.state = 99; + d.fi.op = d.ws.rd_opcode_; + d.fi.fin = d.ws.rd_fh_.fin && + d.ws.rd_need_ == 0; + break; + + // got fixed header + case 5: + { + d.fb.commit(bytes_transferred); + code = close_code::none; + auto const n = detail::read_fh1( + d.ws.rd_fh_, d.fb, d.ws.role_, code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.state = 6; + if (n == 0) + { + bytes_transferred = 0; + break; + } + // read variable header + boost::asio::async_read(d.ws.stream_, + d.fb.prepare(n), std::move(*this)); + return; + } + + // got variable header + case 6: + d.fb.commit(bytes_transferred); + code = close_code::none; + detail::read_fh2(d.ws.rd_fh_, + d.fb, d.ws.role_, code); + if(code == close_code::none) + d.ws.prepare_fh(code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + if(detail::is_control(d.ws.rd_fh_.op)) + { + if(d.ws.rd_fh_.len > 0) + { + // read control payload + d.state = 7; + d.fmb = d.fb.prepare(static_cast< + std::size_t>(d.ws.rd_fh_.len)); + boost::asio::async_read(d.ws.stream_, + d.fmb, std::move(*this)); + return; + } + d.state = 8; + break; + } + if(d.ws.rd_need_ > 0) + { + d.state = 1; + break; + } + if(! d.ws.rd_fh_.fin) + { + d.state = 2; + break; + } + // empty frame with fin + d.state = 4; + break; + + // got control payload + case 7: + if(d.ws.rd_fh_.mask) + detail::mask_inplace( + d.fmb, d.ws.rd_key_); + d.fb.commit(bytes_transferred); + d.state = 8; + break; + + // do control + case 8: + if(d.ws.rd_fh_.op == opcode::ping) + { + code = close_code::none; + ping_payload_type data; + detail::read(data, d.fb.data(), code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.fb.reset(); + if(d.ws.wr_close_) + { + d.state = 2; + break; + } + d.ws.template write_ping( + d.fb, opcode::pong, data); + if(d.ws.wr_block_) + { + assert(d.ws.wr_block_ != &d); + // suspend + d.state = 13; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 14; + break; + } + else if(d.ws.rd_fh_.op == opcode::pong) + { + code = close_code::none; + ping_payload_type data; + detail::read(data, d.fb.data(), code); + if(code != close_code::none) + { + // protocol error + d.state = 16; + break; + } + d.fb.reset(); + // VFALCO TODO maybe_invoke an async pong handler + // For now just ignore the pong. + d.state = 2; + break; + } + assert(d.ws.rd_fh_.op == opcode::close); + { + detail::read(d.ws.cr_, d.fb.data(), code); + if(code != close_code::none) + { + d.state = 16; + break; + } + if(! d.ws.wr_close_) + { + auto cr = d.ws.cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, cr); + if(d.ws.wr_block_) + { + // suspend + d.state = 9; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 10; + break; + } + // call handler; + d.state = 99; + ec = error::closed; + break; + } + + // resume + case 9: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + if(d.ws.wr_close_) + { + // call handler + d.state = 99; + ec = error::closed; + break; + } + d.state = 10; + break; + + // send close + case 10: + d.state = 11; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return;; + + // teardown + case 11: + d.state = 12; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + case 12: + // call handler + d.state = 99; + ec = error::closed; + break; + + // resume + case 13: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + if(d.ws.wr_close_) + { + d.fb.reset(); + d.state = 2; + break; + } + d.state = 14; + break; + + case 14: + // write ping/pong + d.state = 15; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + // sent ping/pong + case 15: + d.fb.reset(); + d.state = 2; + d.ws.wr_block_ = nullptr; + break; + + // fail the connection + case 16: + if(! d.ws.wr_close_) + { + d.fb.reset(); + d.ws.template write_close< + static_streambuf>(d.fb, code); + if(d.ws.wr_block_) + { + // suspend + d.state = 17; + d.ws.rd_op_.template emplace< + read_frame_op>(std::move(*this)); + return; + } + d.state = 18; + break; + } + + // resume + case 17: + if(d.ws.wr_close_) + { + d.state = 19; + break; + } + d.state = 18; + break; + + case 18: + // send close + d.state = 19; + d.ws.wr_close_ = true; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + d.fb.data(), std::move(*this)); + return; + + // teardown + case 19: + d.state = 20; + wsproto_helpers::call_async_teardown( + d.ws.next_layer_, std::move(*this)); + return; + + case 20: + // call handler + d.state = 99; + ec = error::failed; + break; + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + d.h(ec); + d.ws.wr_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/read_op.ipp b/beast/wsproto/impl/read_op.ipp new file mode 100644 index 00000000..895a9151 --- /dev/null +++ b/beast/wsproto/impl/read_op.ipp @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_READ_OP_H_INCLUDED +#define BEAST_WSPROTO_READ_OP_H_INCLUDED + +#include +#include + +namespace beast { +namespace wsproto { + +// read an entire message +// +template +template +class socket::read_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + opcode& op; + Streambuf& sb; + Handler h; + frame_info fi; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket& ws_, opcode& op_, + Streambuf& sb_) + : ws(ws_) + , op(op_) + , sb(sb_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + read_op(read_op&&) = default; + read_op(read_op const&) = default; + + template + read_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code const& ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, read_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(read_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, read_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::read_op:: +operator()(error_code const& ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // read payload + d.state = 1; + d.ws.async_read_frame( + d.fi, d.sb, std::move(*this)); + return; + + // got payload + case 1: + d.op = d.fi.op; + d.state = d.fi.fin ? 99 : 0; + break; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/response_op.ipp b/beast/wsproto/impl/response_op.ipp new file mode 100644 index 00000000..ff7e6f7f --- /dev/null +++ b/beast/wsproto/impl/response_op.ipp @@ -0,0 +1,147 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_RESPONSE_OP_H_INCLUDED +#define BEAST_WSPROTO_RESPONSE_OP_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Respond to an upgrade HTTP request +template +template +class socket::response_op +{ + using alloc_type = + handler_alloc; + + struct data + { + socket& ws; + http::response resp; + Handler h; + error_code final_ec; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + http::message const& req, + bool cont_) + : ws(ws_) + , resp(ws_.build_response(req)) + , h(std::forward(h_)) + , cont(cont_) + { + if(resp.status != 101) + final_ec = error::handshake_failed; + } + }; + + std::shared_ptr d_; + +public: + response_op(response_op&&) = default; + response_op(response_op const&) = default; + + template + response_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()( + error_code ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, response_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(response_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, response_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket::response_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + // send response + d.state = 1; + http::async_write(d.ws.next_layer_, + d.resp, std::move(*this)); + return; + + // sent response + case 1: + d.state = 99; + ec = d.final_ec; + if(! ec) + d.ws.role_ = role_type::server; + break; + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/socket.ipp b/beast/wsproto/impl/socket.ipp new file mode 100644 index 00000000..50607f9a --- /dev/null +++ b/beast/wsproto/impl/socket.ipp @@ -0,0 +1,815 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_IMPL_SOCKET_IPP_INCLUDED +#define BEAST_WSPROTO_IMPL_SOCKET_IPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +template +void +socket_base::prepare_fh(close_code& code) +{ + // continuation without an active message + if(! rd_cont_ && rd_fh_.op == opcode::cont) + { + code = close_code::protocol_error; + return; + } + // new data frame when continuation expected + if(rd_cont_ && ! is_control(rd_fh_.op) && + rd_fh_.op != opcode::cont) + { + code = close_code::protocol_error; + return; + } + if(rd_fh_.mask) + prepare_key(rd_key_, rd_fh_.key); + if(! is_control(rd_fh_.op)) + { + if(rd_fh_.op != opcode::cont) + { + rd_size_ = rd_fh_.len; + rd_opcode_ = rd_fh_.op; + } + else + { + if(rd_size_ > std::numeric_limits< + std::uint64_t>::max() - rd_fh_.len) + { + code = close_code::too_big; + return; + } + rd_size_ += rd_fh_.len; + } + if(rd_size_ > rd_msg_max_) + { + code = close_code::too_big; + return; + } + rd_need_ = rd_fh_.len; + rd_cont_ = ! rd_fh_.fin; + } +} + +template +void +socket_base::write_close( + Streambuf& sb, close_reason const& cr) +{ + using namespace boost::endian; + frame_header fh; + fh.op = opcode::close; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = cr.code == close_code::none ? + 0 : 2 + cr.reason.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(cr.code != close_code::none) + { + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + { + std::uint8_t b[2]; + ::new(&b[0]) big_uint16_buf_t{ + (std::uint16_t)cr.code}; + auto d = sb.prepare(2); + boost::asio::buffer_copy(d, + boost::asio::buffer(b)); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(2); + } + if(! cr.reason.empty()) + { + auto d = sb.prepare(cr.reason.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffer( + cr.reason.data(), cr.reason.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(cr.reason.size()); + } + } +} + +template +void +socket_base::write_ping(Streambuf& sb, + opcode op, ping_payload_type const& data) +{ + frame_header fh; + fh.op = op; + fh.fin = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = data.size(); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::write(sb, fh); + if(data.empty()) + return; + detail::prepared_key_type key; + if(fh.mask) + detail::prepare_key(key, fh.key); + auto d = sb.prepare(data.size()); + boost::asio::buffer_copy(d, + boost::asio::const_buffers_1( + data.data(), data.size())); + if(fh.mask) + detail::mask_inplace(d, key); + sb.commit(data.size()); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +template +socket::socket(Args&&... args) + : next_layer_(std::forward(args)...) + , stream_(next_layer_) +{ + static_assert(is_Stream::value, + "Stream requirements not met"); +} + +template +void +socket::accept(error_code& ec) +{ + accept(boost::asio::null_buffers{}, ec); +} + +template +template +auto +socket::async_accept(AcceptHandler&& handler) +{ + return async_accept(boost::asio::null_buffers{}, + std::forward(handler)); +} + +template +template +void +socket::accept( + ConstBufferSequence const& buffers) +{ + static_assert(is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + error_code ec; + accept(buffers, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +socket::accept( + ConstBufferSequence const& buffers, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + stream_.buffer().commit(buffer_copy( + stream_.buffer().prepare( + buffer_size(buffers)), buffers)); + http::request m; + http::read(next_layer_, stream_.buffer(), m, ec); + if(ec) + return; + accept(m, ec); +} + +template +template +auto +socket::async_accept( + ConstBufferSequence const& bs, AcceptHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + accept_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +socket::accept( + http::message const& request) +{ + error_code ec; + accept(request, ec); + detail::maybe_throw(ec, "accept"); +} + +template +template +void +socket::accept( + http::message const& req, + error_code& ec) +{ + auto resp = build_response(req); + http::write(stream_, resp, ec); + if(resp.status != 101) + { + ec = error::handshake_failed; + // VFALCO TODO Respect keep alive setting, perform + // teardown if Connection: close. + return; + } + role_ = role_type::server; +} + +template +template +auto +socket::async_accept( + http::message const& req, + AcceptHandler&& handler) +{ + beast::async_completion< + AcceptHandler, void(error_code) + > completion(handler); + response_op{ + completion.handler, *this, req, + boost_asio_handler_cont_helpers:: + is_continuation(completion.handler)}; + return completion.result.get(); +} + +template +void +socket::handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec) +{ + std::string key; + http::write(stream_, + build_request(host, resource, key), ec); + if(ec) + return; + http::response resp; + http::read(next_layer_, stream_.buffer(), resp, ec); + if(ec) + return; + do_response(resp, key, ec); +} + +template +template +auto +socket::async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& handler) +{ + beast::async_completion< + HandshakeHandler, void(error_code) + > completion(handler); + handshake_op{ + completion.handler, *this, host, resource}; + return completion.result.get(); +} + +template +void +socket::close( + close_reason const& cr, error_code& ec) +{ + assert(! wr_close_); + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + error_ = ec != 0; +} + +template +template +auto +socket::async_close( + close_reason const& cr, CloseHandler&& handler) +{ + beast::async_completion< + CloseHandler, void(error_code) + > completion(handler); + close_op{ + completion.handler, *this, cr}; + return completion.result.get(); +} + +template +template +void +socket:: +read(opcode& op, Streambuf& streambuf, error_code& ec) +{ + frame_info fi; + for(;;) + { + read_frame(fi, streambuf, ec); + if(ec) + break; + op = fi.op; + if(fi.fin) + break; + } +} + +template +template +auto +socket:: +async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code) + > completion(handler); + read_op{ + completion.handler, *this, op, streambuf}; + return completion.result.get(); +} + +template +template +void +socket::read_frame(frame_info& fi, + Streambuf& streambuf, error_code& ec) +{ + close_code code{}; + for(;;) + { + if(rd_need_ == 0) + { + // read header + detail::frame_streambuf fb; + do_read_fh(fb, code, ec); + if((error_ = ec != 0)) + return; + if(code != close_code::none) + break; + if(detail::is_control(rd_fh_.op)) + { + // read control payload + if(rd_fh_.len > 0) + { + auto const mb = fb.prepare( + static_cast(rd_fh_.len)); + fb.commit(boost::asio::read(stream_, mb, ec)); + if((error_ = ec != 0)) + return; + if(rd_fh_.mask) + detail::mask_inplace(mb, rd_key_); + fb.commit(static_cast(rd_fh_.len)); + } + if(rd_fh_.op == opcode::ping) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if(code != close_code::none) + break; + fb.reset(); + write_ping( + fb, opcode::pong, data); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + continue; + } + else if(rd_fh_.op == opcode::pong) + { + ping_payload_type data; + detail::read(data, fb.data(), code); + if((error_ = ec != 0)) + break; + // VFALCO How to notify callers using + // the synchronous interface? + continue; + } + assert(rd_fh_.op == opcode::close); + { + detail::read(cr_, fb.data(), code); + if(code != close_code::none) + break; + if(! wr_close_) + { + auto cr = cr_; + if(cr.code == close_code::none) + cr.code = close_code::normal; + cr.reason = ""; + fb.reset(); + wr_close_ = true; + write_close(fb, cr); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + break; + } + } + if(rd_need_ == 0 && ! rd_fh_.fin) + { + // empty frame + continue; + } + } + // read payload + auto smb = streambuf.prepare( + detail::clamp(rd_need_)); + auto const bytes_transferred = + stream_.read_some(smb, ec); + if((error_ = ec != 0)) + return; + rd_need_ -= bytes_transferred; + auto const pb = prepare_buffers( + bytes_transferred, smb); + if(rd_fh_.mask) + detail::mask_inplace(pb, rd_key_); + if(rd_opcode_ == opcode::text) + { + if(! rd_utf8_check_.write(pb) || + (rd_need_ == 0 && rd_fh_.fin && + ! rd_utf8_check_.finish())) + { + code = close_code::bad_payload; + break; + } + } + streambuf.commit(bytes_transferred); + fi.op = rd_opcode_; + fi.fin = rd_fh_.fin && rd_need_ == 0; + return; + } + if(code != close_code::none) + { + // Fail the connection (per rfc6455) + if(! wr_close_) + { + wr_close_ = true; + detail::frame_streambuf fb; + write_close(fb, code); + boost::asio::write(stream_, fb.data(), ec); + if((error_ = ec != 0)) + return; + } + wsproto_helpers::call_teardown(next_layer_, ec); + if((error_ = ec != 0)) + return; + ec = error::failed; + error_ = true; + return; + } + if(! ec) + wsproto_helpers::call_teardown(next_layer_, ec); + if(! ec) + ec = error::closed; + error_ = ec != 0; +} + +template +template +auto +socket::async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler) +{ + static_assert(beast::is_Streambuf::value, + "Streambuf requirements not met"); + beast::async_completion< + ReadHandler, void(error_code)> completion(handler); + read_frame_op{ + completion.handler, *this, fi, streambuf}; + return completion.result.get(); +} + +template +template +void +socket::write( + ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_size; + consuming_buffers cb(bs); + auto remain = buffer_size(cb); + for(;;) + { + auto const n = + detail::clamp(remain, wr_frag_size_); + remain -= n; + auto const fin = remain <= 0; + write_frame(fin, prepare_buffers(n, cb), ec); + cb.consume(n); + if(ec) + return; + if(fin) + break; + } +} + +template +template +auto +socket::async_write( + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code)> completion(handler); + write_op{ + completion.handler, *this, bs}; + return completion.result.get(); +} + +template +template +void +socket::write_frame(bool fin, + ConstBufferSequence const& bs, error_code& ec) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + using boost::asio::mutable_buffers_1; + detail::frame_header fh; + fh.op = wr_cont_ ? opcode::cont : wr_opcode_; + wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = buffer_size(bs); + if((fh.mask = (role_ == role_type::client))) + fh.key = maskgen_(); + detail::fh_streambuf fh_buf; + detail::write(fh_buf, fh); + if(! fh.mask) + { + // send header and payload + boost::asio::write(stream_, + append_buffers(fh_buf.data(), bs), ec); + error_ = ec != 0; + return; + } + detail::prepared_key_type key; + detail::prepare_key(key, fh.key); + auto const tmp_size = detail::clamp( + fh.len, wr_buf_size_); + std::unique_ptr up( + new std::uint8_t[tmp_size]); + auto const tmp = up.get(); + std::uint64_t remain = fh.len; + consuming_buffers cb(bs); + { + auto const n = + detail::clamp(remain, tmp_size); + mutable_buffers_1 mb{tmp, n}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send header and payload + boost::asio::write(stream_, + append_buffers(fh_buf.data(), mb), ec); + if(ec) + { + error_ = ec != 0; + return; + } + } + while(remain > 0) + { + auto const n = + detail::clamp(remain, tmp_size); + mutable_buffers_1 mb{tmp, n}; + buffer_copy(mb, cb); + cb.consume(n); + remain -= n; + detail::mask_inplace(mb, key); + // send payload + boost::asio::write(stream_, mb, ec); + if(ec) + { + error_ = ec != 0; + return; + } + } +} + +template +template +auto +socket::async_write_frame(bool fin, + ConstBufferSequence const& bs, WriteHandler&& handler) +{ + static_assert(beast::is_ConstBufferSequence< + ConstBufferSequence>::value, + "ConstBufferSequence requirements not met"); + beast::async_completion< + WriteHandler, void(error_code) + > completion(handler); + write_frame_op{completion.handler, + *this, fin, bs}; + return completion.result.get(); +} + +//------------------------------------------------------------------------------ + +template +http::request +socket::build_request(boost::string_ref const& host, + boost::string_ref const& resource, std::string& key) +{ + http::request req; + req.url = "/"; + req.version = 11; + req.method = http::method_t::http_get; + req.headers.insert("Host", host); + req.headers.insert("Connection", "upgrade"); + req.headers.insert("Upgrade", "websocket"); + key = detail::make_sec_ws_key(maskgen_); + req.headers.insert("Sec-WebSocket-Key", key); + req.headers.insert("Sec-WebSocket-Version", "13"); + (*d_)(req); + return req; +} + +template +template +http::response +socket::build_response( + http::message const& req) +{ + auto err = + [&](auto const& text) + { + http::response resp( + {400, http::reason_string(400), req.version}); + resp.body = text; + // VFALCO TODO respect keep-alive here + return resp; + }; + if(req.version < 11) + return err("HTTP version 1.1 required"); + if(req.method != http::method_t::http_get) + return err("Wrong method"); + if(! is_upgrade(req)) + return err("Expected Upgrade request"); + if(! req.headers.exists("Host")) + return err("Missing Host"); + if(! req.headers.exists("Sec-WebSocket-Key")) + return err("Missing Sec-WebSocket-Key"); + { + auto const version = + req.headers["Sec-WebSocket-Version"]; + if(version.empty()) + return err("Missing Sec-WebSocket-Version"); + if(version != "13") + return err("Unsupported Sec-WebSocket-Version"); + } + if(! rfc2616::token_in_list( + req.headers["Upgrade"], "websocket")) + return err("Missing websocket Upgrade token"); + http::response resp( + {101, http::reason_string(101), req.version}); + resp.headers.insert("Upgrade", "websocket"); + resp.headers.insert("Connection", "upgrade"); + { + auto const key = + req.headers["Sec-WebSocket-Key"]; + resp.headers.insert("Sec-WebSocket-Key", key); + resp.headers.insert("Sec-WebSocket-Accept", + detail::make_sec_ws_accept(key)); + } + resp.headers.replace("Server", "Beast.WSProto"); + (*d_)(resp); + return resp; +} + +template +template +void +socket::do_response( + http::message const& resp, + boost::string_ref const& key, error_code& ec) +{ + // VFALCO Review these error codes + auto fail = [&]{ ec = error::response_failed; }; + if(resp.status != 101) + return fail(); + if(! is_upgrade(resp)) + return fail(); + if(! rfc2616::ci_equal( + resp.headers["Upgrade"], "websocket")) + return fail(); + if(! resp.headers.exists("Sec-WebSocket-Accept")) + return fail(); + if(resp.headers["Sec-WebSocket-Accept"] != + detail::make_sec_ws_accept(key)) + return fail(); + role_ = role_type::client; +} + +template +void +socket::do_read_fh( + detail::frame_streambuf& fb, + close_code& code, error_code& ec) +{ + fb.commit(boost::asio::read( + stream_, fb.prepare(2), ec)); + if(ec) + return; + auto const n = detail::read_fh1( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + if(n > 0) + { + fb.commit(boost::asio::read( + stream_, fb.prepare(n), ec)); + if(ec) + return; + } + detail::read_fh2( + rd_fh_, fb, role_, code); + if(code != close_code::none) + return; + prepare_fh(code); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/ssl.ipp b/beast/wsproto/impl/ssl.ipp new file mode 100644 index 00000000..ec465db6 --- /dev/null +++ b/beast/wsproto/impl/ssl.ipp @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SSL_IPP_INCLUDED +#define BEAST_WSPROTO_SSL_IPP_INCLUDED + +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +/* + +See +http://stackoverflow.com/questions/32046034/what-is-the-proper-way-to-securely-disconnect-an-asio-ssl-socket/32054476#32054476 + +Behavior of ssl::stream regarding close_ + + If the remote host calls async_shutdown then the + local host's async_read will complete with eof. + + If both hosts call async_shutdown then the calls + to async_shutdown will complete with eof. + +*/ +template +class teardown_ssl_op +{ + using stream_type = + boost::asio::ssl::stream; + + struct data + { + stream_type& stream; + Handler h; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + stream_type& stream_) + : stream(stream_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + explicit + teardown_ssl_op( + DeducedHandler&& h, + stream_type& stream) + : d_(std::make_shared( + std::forward(h), + stream)) + { + (*this)(error_code{}, false); + } + + void + operator()(error_code ec, bool again = true); + + friend + auto asio_handler_allocate(std::size_t size, + teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate(void* p, + std::size_t size, teardown_ssl_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation( + teardown_ssl_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, + teardown_ssl_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_ssl_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(!ec && d.state != 99) + { + switch(d.state) + { + case 0: + d.state = 99; + d.stream.async_shutdown(*this); + return; + } + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec) +{ + stream.shutdown(ec); +} + +template +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_ssl_op>{std::forward( + handler), stream}; +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/teardown.ipp b/beast/wsproto/impl/teardown.ipp new file mode 100644 index 00000000..92e50fd0 --- /dev/null +++ b/beast/wsproto/impl/teardown.ipp @@ -0,0 +1,183 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_TEARDOWN_IPP_INCLUDED +#define BEAST_WSPROTO_TEARDOWN_IPP_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +namespace detail { + +template +class teardown_tcp_op +{ + using socket_type = + boost::asio::ip::tcp::socket; + + struct data + { + socket_type& socket; + Handler h; + char buf[8192]; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket_type& socket_) + : socket(socket_) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + template + teardown_tcp_op( + DeducedHandler&& h, + socket_type& socket) + : d_(std::make_shared( + std::forward(h), + socket)) + { + (*this)(error_code{}, 0, false); + } + + void + operator()( + error_code ec, std::size_t, bool again = true); + + friend + auto asio_handler_allocate(std::size_t size, + teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate(void* p, + std::size_t size, teardown_tcp_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(teardown_tcp_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, + teardown_tcp_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +void +teardown_tcp_op:: +operator()(error_code ec, std::size_t, bool again) +{ + using boost::asio::buffer; + auto& d = *d_; + d.cont = d.cont || again; + while(! ec) + { + switch(d.state) + { + case 0: + d.state = 1; + d.socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + break; + + case 1: + d.socket.async_read_some( + buffer(d.buf), std::move(*this)); + return; + } + } + if(ec == boost::asio::error::eof) + { + d.socket.close(ec); + ec = error_code{}; + } + d.h(ec); +} + +} // detail + +//------------------------------------------------------------------------------ + +inline +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec) +{ + using boost::asio::buffer; + socket.shutdown( + boost::asio::ip::tcp::socket::shutdown_send, ec); + while(! ec) + { + char buf[8192]; + auto const n = socket.read_some( + buffer(buf), ec); + if(! n) + break; + } + if(ec == boost::asio::error::eof) + ec = error_code{}; + socket.close(ec); +} + +template +inline +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler) +{ + static_assert(beast::is_Handler< + TeardownHandler, void(error_code)>::value, + "TeardownHandler requirements not met"); + detail::teardown_tcp_op>{std::forward< + TeardownHandler>(handler), socket}; +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/write_frame_op.ipp b/beast/wsproto/impl/write_frame_op.ipp new file mode 100644 index 00000000..a3741570 --- /dev/null +++ b/beast/wsproto/impl/write_frame_op.ipp @@ -0,0 +1,277 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_WRITE_FRAME_OP_H_INCLUDED +#define BEAST_WSPROTO_WRITE_FRAME_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// write a frame +// +template +template +class socket::write_frame_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + socket& ws; + consuming_buffers cb; + Handler h; + detail::frame_header fh; + detail::fh_streambuf fh_buf; + detail::prepared_key_type key; + void* tmp; + std::size_t tmp_size; + std::uint64_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, socket& ws_, + bool fin, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + fh.op = ws.wr_cont_ ? + opcode::cont : ws.wr_opcode_; + ws.wr_cont_ = ! fin; + fh.fin = fin; + fh.rsv1 = 0; + fh.rsv2 = 0; + fh.rsv3 = 0; + fh.len = boost::asio::buffer_size(cb); + if((fh.mask = (ws.role_ == role_type::client))) + { + fh.key = ws.maskgen_(); + detail::prepare_key(key, fh.key); + tmp_size = detail::clamp( + fh.len, ws.wr_buf_size_); + tmp = boost_asio_handler_alloc_helpers:: + allocate(tmp_size, h); + remain = fh.len; + } + else + { + tmp = nullptr; + } + detail::write(fh_buf, fh); + } + + ~data() + { + if(tmp) + boost_asio_handler_alloc_helpers:: + deallocate(tmp, tmp_size, h); + } + }; + + std::shared_ptr d_; + +public: + write_frame_op(write_frame_op&&) = default; + write_frame_op(write_frame_op const&) = default; + + template + write_frame_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::make_shared( + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, 0, false); + } + + void operator()() + { + auto& d = *d_; + d.cont = false; + (*this)(error_code{}, 0, false); + } + + void operator()(error_code ec, + std::size_t bytes_transferred, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_frame_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_frame_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_frame_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket:: +write_frame_op:: +operator()( + error_code ec, std::size_t bytes_transferred, bool again) +{ + using boost::asio::buffer_copy; + using boost::asio::mutable_buffers_1; + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + if(d.ws.wr_block_) + { + // suspend + d.state = 1; + d.ws.wr_op_.template emplace< + write_frame_op>(std::move(*this)); + return; + } + if(d.ws.error_) + { + // call handler + d.state = 99; + d.ws.get_io_service().post( + bind_handler(std::move(*this), + boost::asio::error::operation_aborted, 0)); + return; + } + assert(! d.ws.wr_close_); + d.state = 2; + break; + + // resume + case 1: + if(d.ws.error_) + { + // call handler + d.state = 99; + ec = boost::asio::error::operation_aborted; + break; + } + d.state = 2; + break; + + case 2: + { + if(! d.fh.mask) + { + // send header and payload + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + append_buffers(d.fh_buf.data(), d.cb), + std::move(*this)); + return; + } + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, n}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send header and payload + d.state = d.remain > 0 ? 3 : 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write(d.ws.stream_, + append_buffers(d.fh_buf.data(), + mb), std::move(*this)); + return; + } + + // sent masked payload + case 3: + { + auto const n = + detail::clamp(d.remain, d.tmp_size); + mutable_buffers_1 mb{d.tmp, + static_cast(n)}; + buffer_copy(mb, d.cb); + d.cb.consume(n); + d.remain -= n; + detail::mask_inplace(mb, d.key); + // send payload + if(d.remain == 0) + d.state = 99; + assert(! d.ws.wr_block_); + d.ws.wr_block_ = &d; + boost::asio::async_write( + d.ws.stream_, mb, std::move(*this)); + return; + } + } + } + if(ec) + d.ws.error_ = true; + if(d.ws.wr_block_ == &d) + d.ws.wr_block_ = nullptr; + if(d.tmp) + { + boost_asio_handler_alloc_helpers:: + deallocate(d.tmp, d.tmp_size, d.h); + d.tmp = nullptr; + } + d.h(ec); + d.ws.rd_op_.maybe_invoke(); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/impl/write_op.ipp b/beast/wsproto/impl/write_op.ipp new file mode 100644 index 00000000..24e48dd4 --- /dev/null +++ b/beast/wsproto/impl/write_op.ipp @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_WRITE_OP_H_INCLUDED +#define BEAST_WSPROTO_WRITE_OP_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// write a message +// +template +template +class socket::write_op +{ + using alloc_type = + handler_alloc; + + struct data : op + { + socket& ws; + consuming_buffers cb; + Handler h; + std::size_t remain; + bool cont; + int state = 0; + + template + data(DeducedHandler&& h_, + socket& ws_, Buffers const& bs) + : ws(ws_) + , cb(bs) + , h(std::forward(h_)) + , remain(boost::asio::buffer_size(cb)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + +public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + explicit + write_op(DeducedHandler&& h, + socket& ws, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), ws, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void operator()(error_code ec, bool again = true); + + friend + auto asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + auto asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + auto asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + auto asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } +}; + +template +template +void +socket:: +write_op:: +operator()(error_code ec, bool again) +{ + auto& d = *d_; + d.cont = d.cont || again; + while(! ec && d.state != 99) + { + switch(d.state) + { + case 0: + { + auto const n = std::min( + d.remain, d.ws.wr_frag_size_); + d.remain -= n; + auto const fin = d.remain <= 0; + if(fin) + d.state = 99; + d.ws.async_write_frame(fin, + prepare_buffers(n, d.cb), std::move(*this)); + d.cb.consume(n); + return; + } + } + } + d.h(ec); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/option.h b/beast/wsproto/option.h new file mode 100644 index 00000000..f2fbb7b1 --- /dev/null +++ b/beast/wsproto/option.h @@ -0,0 +1,305 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_OPTION_H_INCLUDED +#define BEAST_WSPROTO_OPTION_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Automatic fragmentation size option. + + Sets the maximum size of fragments generated when sending + messages on a WebSocket socket. + + When the automatic fragmentation size is non-zero, messages + exceeding the size will be split into multiple frames no + larger than the size. This setting does not affect frames + send explicitly using `write_frame` or `async_write_frame`. + + The default setting is to fragment messages into 16KB frames. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the automatic fragmentation size option: + @code + ... + wsproto::socket stream(ios); + stream.set_option(auto_fragment_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using auto_fragment_size = implementation_defined; +#else +struct auto_fragment_size +{ + std::size_t value; + + auto_fragment_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** HTTP decorator option. + + The decorator transforms the HTTP requests and responses used + when requesting or responding to the WebSocket Upgrade. This may + be used to set or change header fields. For example to set the + Server or User-Agent fields. The default setting applies no + transformation to the HTTP message. + + For synchronous operations, the implementation will call the + decorator before the function call to perform the operation + returns. + + For asynchronous operations, the implementation guarantees that + calls to the decorator will be made from the same implicit or + explicit strand used to call the asynchronous initiation + function. + + The default setting is no decorator. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the decorator. + @code + struct identity + { + template + void + operator()(http::message& m) + { + if(isRequest) + m.headers.replace("User-Agent", "MyClient"); + else + m.headers.replace("Server", "MyServer"); + } + }; + ... + websocket::stream ws(ios); + ws.set_option(decorate(identity{})); + @endcode +*/ +#if GENERATING_DOCS +using decorate = implementation_defined; +#else +template +inline +auto +decorate(Decorator&& d) +{ + return std::make_unique>>( + std::forward(d)); +} +#endif + +/** Keep-alive option. + + Determines if the connection is closed after a failed upgrade + request. + + This setting only affects the behavior of HTTP requests that + implicitly or explicitly ask for a keepalive. For HTTP requests + that indicate the connection should be closed, the connection is + closed as per rfc2616. + + The default setting is to close connections after a failed + upgrade request. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the keep alive option. + @code + ... + websocket::stream ws(ios); + ws.set_option(keep_alive{8192}); + @endcode +*/ +#if GENERATING_DOCS +using keep_alive = implementation_defined; +#else +struct keep_alive +{ + bool value; + + keep_alive(bool v) + : value(v) + { + } +}; +#endif + +/** Message type option. + + This controls the opcode set for outgoing messages. Valid + choices are opcode::binary or opcode::text. The setting is + only applied at the start when a caller begins a new message. + Changing the opcode after a message is started will only + take effect after the current message being sent is complete. + + The default setting is opcode::text. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the message type to binary. + @code + ... + websocket::stream ws(ios); + ws.set_option(message_type{opcode::binary}); + @endcode +*/ +#if GENERATING_DOCS +using message_type = implementation_defined; +#else +struct message_type +{ + opcode value; + + explicit + message_type(opcode op) + { + if(op != opcode::binary && op != opcode::text) + throw std::domain_error("bad opcode"); + value = op; + } +}; +#endif + +/** Read buffer size option. + + Sets the number of bytes allocated to the socket's read buffer. + If this is zero, then reads are not buffered. Setting this + higher can improve performance when expecting to receive + many small frames. + + The default is no buffering. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the read buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_buffer_size{16 * 1024}); + @endcode +*/ +#if GENERATING_DOCS +using read_buffer_size = implementation_defined; +#else +struct read_buffer_size +{ + std::size_t value; + + explicit + read_buffer_size(std::size_t n) + : value(n) + { + } +}; +#endif + +/** Maximum incoming message size option. + + Sets the largest permissible incoming message size. Message + frame headers indicating a size that would bring the total + message size over this limit will cause a protocol failure. + + The default setting is 16 megabytes. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the maximum read message size. + @code + ... + websocket::stream ws(ios); + ws.set_option(read_message_max{65536}); + @endcode +*/ +#if GENERATING_DOCS +using read_message_max = implementation_defined; +#else +struct read_message_max +{ + std::size_t value; + + explicit + read_message_max(std::size_t n) + : value(n) + { + } +}; +#endif + +/** Write buffer size option. + + Sets the number of bytes allocated to the socket's write buffer. + This buffer is used to hold masked frame payload data. Lowering + the size of the buffer can decrease the memory requirements for + each connection, at the cost of an increased number of calls to + perform socket writes. + + This setting does not affect connections operating in the server + role, since servers do not apply a masking key to frame payloads. + + The default setting is 4096. The minimum value is 1024. + + @note Objects of this type are passed to socket::set_option. + + @par Example + Setting the write buffer size. + @code + ... + websocket::stream ws(ios); + ws.set_option(write_buffer_size{8192}); + @endcode +*/ +#if GENERATING_DOCS +using write_buffer_size = implementation_defined; +#else +struct write_buffer_size +{ + std::size_t value; + + explicit + write_buffer_size(std::size_t n) + : value(n) + { + } +}; +#endif + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/rfc6455.h b/beast/wsproto/rfc6455.h new file mode 100644 index 00000000..84700f42 --- /dev/null +++ b/beast/wsproto/rfc6455.h @@ -0,0 +1,163 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_RFC6455_H_INCLUDED +#define BEAST_WSPROTO_RFC6455_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** WebSocket frame header opcodes. */ +enum class opcode : std::uint8_t +{ + cont = 0, + text = 1, + binary = 2, + rsv3 = 3, + rsv4 = 4, + rsv5 = 5, + rsv6 = 6, + rsv7 = 7, + close = 8, + ping = 9, + pong = 10, + crsvb = 11, + crsvc = 12, + crsvd = 13, + crsve = 14, + crsvf = 15 +}; + +/** Close status codes. + + These codes accompany close frames. + + @see RFC 6455 7.4.1 Defined Status Codes + https://tools.ietf.org/html/rfc6455#section-7.4.1 +*/ +enum class close_code : std::uint16_t +{ + // used internally to mean "no error" + none = 0, + + normal = 1000, + going_away = 1001, + protocol_error = 1002, + unknown_data = 1003, + bad_payload = 1007, + policy_error = 1008, + too_big = 1009, + needs_extension = 1010, + internal_error = 1011, + + service_restart = 1012, + try_again_later = 1013, + + reserved1 = 1004, + no_status = 1005, // illegal on wire + abnormal = 1006, // illegal on wire + reserved2 = 1015, + + last = 5000 // satisfy warnings +}; + +#if ! GENERATING_DOCS + +using reason_string_type = + static_string<123, char>; + +/// Payload type for pings and pongs +using ping_payload_type = + static_string<125, char>; + +#endif + +/** Description of the close reason. + + This object stores the close code (if any) and the optional + utf-8 encoded implementation defined reason string. +*/ +struct close_reason +{ + /// The close code. + close_code code = close_code::none; + + /// The optional utf8-encoded reason string. + reason_string_type reason; + + /** Default constructor. + + The code will be none. Default constructed objects + will explicitly convert to bool as `false`. + */ + close_reason() = default; + + /// Construct from a code. + close_reason(close_code code_) + : code(code_) + { + } + + /// Construct from a reason. code is close_code::normal. + template + close_reason(CharT const* reason_) + : code(close_code::normal) + , reason(reason_) + { + } + + /// Construct from a code and reason. + template + close_reason(close_code code_, + CharT const* reason_) + : code(code_) + , reason(reason_) + { + } + + /// Returns `true` if a code was specified + operator bool() const + { + return code != close_code::none; + } +}; + +#if ! GENERATING_DOCS + +/// Identifies the role of a WebSockets stream. +enum class role_type +{ + /// Stream is operating as a client. + client, + + /// Stream is operating as a server. + server +}; + +#endif + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/socket.h b/beast/wsproto/socket.h new file mode 100644 index 00000000..d6f83ea7 --- /dev/null +++ b/beast/wsproto/socket.h @@ -0,0 +1,1190 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SOCKET_H_INCLUDED +#define BEAST_WSPROTO_SOCKET_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Information about a WebSocket frame. + + This information is provided to callers during frame + read operations. +*/ +struct frame_info +{ + /// Indicates the type of message (binary or text). + opcode op; + + /// `true` if this is the last frame in the current message. + bool fin; +}; + +//-------------------------------------------------------------------- + +/** Provides message-oriented functionality using WebSocket. + + The socket class template provides asynchronous and blocking + message-oriented functionality necessary for clients and servers + to utilize the WebSocket protocol. + + @par Thread Safety + @e Distinct @e objects: Safe.@n + @e Shared @e objects: Unsafe. The application must ensure that + all asynchronous operations are performed within the same + implicit or explicit strand. + + @par Example + + To use the WebSocket socket template with an + ip::tcp::socket, you would write: + + @code + wsproto::socket ws(io_service); + @endcode + Alternatively, you can write: + @code + ip::tcp::socket sock(io_service); + wsproto::socket ws(sock); + @endcode + + @note A socket object must not be destroyed while there are + pending asynchronous operations associated with it. + + @par Concepts + AsyncReadStream, AsyncWriteStream, + Decorator, Streambuf, SyncReadStream, SyncWriteStream. +*/ +template +class socket : public detail::socket_base +{ + friend class ws_test; + + Stream next_layer_; + streambuf_readstream stream_; + +public: + /// The type of the next layer. + using next_layer_type = + std::remove_reference_t; + + /// The type of the lowest layer. + using lowest_layer_type = + typename next_layer_type::lowest_layer_type; + + /// The type of endpoint of the lowest layer. + using endpoint_type = + typename lowest_layer_type::endpoint_type; + + /// The protocol of the next layer. + using protocol_type = + typename lowest_layer_type::protocol_type; + + /// The type of resolver of the next layer. + using resolver_type = + typename protocol_type::resolver; + + /// Move constructor. + socket(socket&&) = default; + + /** Move assignment. + + Undefined behavior if operations are active or pending. + */ + socket& operator=(socket&&) = default; + + /** Construct a websocket. + + This constructor creates a websocket and initialises the + underlying stream object. + + @throws Any exceptions thrown by the Stream constructor. + + @param args The arguments to be passed to initialise the + underlying stream object. The arguments are forwarded + to the stream's constructor. + */ + template + explicit + socket(Args&&... args); + + /** Destructor. + + A stream object must not be destroyed while there are + pending asynchronous operations associated with it. + */ + ~socket() = default; + + /** Set options on the socket. + + The application must ensure that calls to set options + are performed within the same implicit or explicit strand. + + @param args One or more socket options to set. + */ +#if GENERATING_DOCS + template + void + set_option(Args&&... args) +#else + template + void + set_option(A1&& a1, A2&& a2, An&&... an) +#endif + { + set_option(std::forward(a1)); + set_option(std::forward(a2), + std::forward(an)...); + } + + void + set_option(auto_fragment_size const& o) + { + if(o.value <= 0) + wr_frag_size_ = + std::numeric_limits::max(); + else + wr_frag_size_ = o.value; + } + + void + set_option(detail::decorator_type o) + { + d_ = std::move(o); + } + + void + set_option(keep_alive const& o) + { + keep_alive_ = o.value; + } + + void + set_option(message_type const& o) + { + wr_opcode_ = o.value; + } + + void + set_option(read_buffer_size const& o) + { + stream_.reserve(o.value); + } + + void + set_option(read_message_max const& o) + { + rd_msg_max_ = o.value; + } + + void + set_option(write_buffer_size const& o) + { + wr_buf_size_ = std::max(o.value, 1024); + stream_.reserve(o.value); + } + + /** Get the io_service associated with the socket. + + This function may be used to obtain the io_service object + that the socket uses to dispatch handlers for asynchronous + operations. + + @return A reference to the io_service object that the socket + will use to dispatch handlers. Ownership is not transferred + to the caller. + */ + boost::asio::io_service& + get_io_service() + { + return next_layer_.lowest_layer().get_io_service(); + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer + in a stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type& + next_layer() + { + return next_layer_; + } + + /** Get a reference to the next layer. + + This function returns a reference to the next layer in a + stack of stream layers. + + @return A reference to the next layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + next_layer_type const& + next_layer() const + { + return next_layer_; + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type& + lowest_layer() + { + return next_layer_.lowest_layer(); + } + + /** Get a reference to the lowest layer. + + This function returns a reference to the lowest layer + in a stack of stream layers. + + @return A reference to the lowest layer in the stack of + stream layers. Ownership is not transferred to the caller. + */ + lowest_layer_type const& + lowest_layer() const + { + return next_layer_.lowest_layer(); + } + + /** Returns the close reason received from the peer. + + This is only valid after a read completes with error::closed. + */ + close_reason const& + reason() const + { + return cr_; + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"), and an appropriate + exception will be thrown. + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @throws boost::system::system_error Thrown on failure. + */ + void + accept() + { + error_code ec; + accept(boost::asio::null_buffers{}, ec); + detail::maybe_throw(ec, "accept"); + } + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param ec Set to indicate what error occurred, if any. + */ + void + accept(error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + */ + template + auto + async_accept(AcceptHandler&& handler); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + accept(ConstBufferSequence const& buffers); + + /** Read and respond to a WebSocket HTTP Upgrade request. + + This function is used to synchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(ConstBufferSequence const& buffers, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param buffers Caller provide data that has already been + received on the socket. This may be used for implementations + allowing multiple protocols on the same socket. The + buffered data will first be applied to the handshake, and + then to received WebSocket frames. The implementation will + copy the caller provided data before the function returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + */ + template + auto + async_accept(ConstBufferSequence const& buffers, + AcceptHandler&& handler); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @throws boost::system::system_error Thrown on failure. + */ + // VFALCO TODO This should also take a streambuf with any leftover bytes. + template + void + accept(http::message const& request); + + /** Respond to a WebSocket HTTP Upgrade request + + This function is used to synchronously send the HTTP response + to a HTTP WebSocket Upgrade request. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + The call blocks until one of the following conditions is true: + + @li An error occurs on the socket. + + @li The entire HTTP response has been sent. + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + accept(http::message const& request, error_code& ec); + + /** Start reading and responding to a WebSocket HTTP Upgrade request. + + This function is used to asynchronously read a HTTP WebSocket + Upgrade request and send the HTTP response. The function call + always returns immediately. + + If the contents of the request are valid, the HTTP response + indicates a successful upgrade and the socket is then ready + to send and receive WebSocket protocol frames and messages. + + If the WebSocket HTTP Upgrade request cannot be satisfied, + a HTTP response is sent indicating the reason and status + code (typically 400, "Bad Request"). + + @param request An object containing the HTTP Upgrade request. The + implementation will make copies as necessary before this + function call returns. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + */ + template + auto + async_accept(http::message const& request, AcceptHandler&& handler); + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the socket + + @li A complete HTTP response with the result of the upgrade + request is received. + + @throws boost::system::system_error Thrown on failure. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @par Example + @code + wsproto::socket ws(io_service); + ... + try + { + ws.upgrade("localhost", "/"); + } + catch(...) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource) + { + error_code ec; + handshake(host, resource, ec); + detail::maybe_throw(ec, "upgrade"); + } + + /** Send a WebSocket Upgrade request. + + This function is used to synchronously send the WebSocket + upgrade HTTP request. The call blocks until one of the + following conditions is true: + + @li An error occurs on the socket + + @li A complete HTTP response with the result of the upgrade + request is received. + + @param host The name of the remote host, required by + the HTTP protocol. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. + + @param ec Set to indicate what error occurred, if any. + + @par Example + @code + wsproto::socket ws(io_service); + ... + error_code ec; + ws.upgrade(host, resource, ec); + if(ec) + { + // An error occurred. + } + @endcode + */ + void + handshake(boost::string_ref const& host, + boost::string_ref const& resource, error_code& ec); + + /** Asynchronously send a WebSocket Upgrade request. + + This function is used to asynchronously send the WebSocket + upgrade HTTP request. This function call always returns + immediately. + + @param host The name of the remote host, required by + the HTTP protocol. Copies may be made as needed. + + @param resource The requesting URI, which may not be empty, + required by the HTTP protocol. Copies may be made as + needed. + + @param h The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + */ + template + auto + async_handshake(boost::string_ref const& host, + boost::string_ref const& resource, HandshakeHandler&& h); + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + */ + void + close(close_reason const& cr) + { + error_code ec; + close(cr, ec); + detail::maybe_throw(ec, "close"); + } + + /** Perform a WebSocket close. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param ec Set to indicate what error occurred, if any. + */ + void + close(close_reason const& cr, error_code& ec); + + /** Start an asychronous WebSocket close operation. + + This function initiates the WebSocket close procedure. + + If the close reason specifies a close code other than + close_code::none, the close frame is sent with the close code + and optional reason string. Otherwise, the close frame + is sent with no payload. + + Callers should not attempt to write WebSocket data after + initiating the close. Instead, callers should continue + reading until an error occurs. A read returning error::closed + indicates a successful connection closure. + + @param cr The reason for the close. + + @param handler The handler to be called when the close operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + 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(). + */ + template + auto + async_close(close_reason const& cr, CloseHandler&& handler); + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read(opcode& op, Streambuf& streambuf) + { + error_code ec; + read(op, streambuf, ec); + detail::maybe_throw(ec, "read"); + } + + /** Read a message. + + This function is used to read a message from the + websocket. The function call will block until the message + has been read successfully, or until an error occurs. + + On success op is set to reflect the message type, binary + or text. + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read(opcode& op, + Streambuf& streambuf, error_code& ec); + + /** Start reading a message asynchronously. + + This function is used to asychronously read a message from + the websocket. The function call always returns immediately. + + Upon a successful completion, op is set to either binary or + text depending on the message type, and the input area of the + streambuf will hold all the message payload bytes (which may + be zero in length). + + @param op A value to receive the message type. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data. + This object must remain valid until the handler is called. + + @param handler The handler to be called when the read operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + 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(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + auto + #endif + async_read(opcode& op, + Streambuf& streambuf, ReadHandler&& handler); + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @throws boost::system::system_error Thrown on failure. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf) + { + error_code ec; + read_frame(fi, streambuf, ec); + detail::maybe_throw(ec, "read_some"); + } + + /** Read a message frame. + + This function is used to read a single message frame from + the websocket. The function call will block until one message + frame has been read successfully, or an error occurs. + + On success, fi is filled out to reflect the message payload + contents. op is set to binary or text, and the fin flag + indicates if all the message data has been read in. To read the + entire message, callers should repeat the read_frame operation + until fi.fin is true. A message with no payload will have + fi.fin == true, and zero bytes placed into the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + + @param streambuf A stream buffer to hold the message data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + read_frame(frame_info& fi, Streambuf& streambuf, error_code& ec); + + /** Start reading a message frame asynchronously. + + This function is used to asychronously read a single message + frame from the websocket. The function call always returns + immediately. + + Upon a successful completion, fi is filled out to reflect the + message payload contents. op is set to binary or text, and the + fin flag indicates if all the message data has been read in. + To read the entire message, callers should repeat the + read_frame operation until fi.fin is true. A message with no + payload will have fi.fin == true, and zero bytes placed into + the stream buffer. + + If a control frame is received while attempting to read a + message frame, the control frame is handled automatically. + If a ping control frame is received, a pong is sent immediately. + If a close control frame is received, a close is sent in + response and the read operation will return with `error::closed`. + + @param fi An object to store metadata about the message. + This object must remain valid until the handler is called. + + @param streambuf A stream buffer to hold the message data after + any masking or decompression has been applied. This object must + remain valid until the handler is called. + + @param handler The handler to be called when the read operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + 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(). + */ + template + auto + async_read_frame(frame_info& fi, + Streambuf& streambuf, ReadHandler&& handler); + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @throws boost::system::system_error Thrown on failure. + + @note This function always sends an entire message. To + send a message in fragments, use `socket::write`. + */ + template + void + write(ConstBufferSequence const& buffers) + { + error_code ec; + write(buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a message. + + This function is used to write a message to the websocket. + The call blocks until one of the following conditions is met: + + @li The entire message is sent. + + @li An error occurs. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param ec Set to indicate what error occurred, if any. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @note This function always sends an entire message. To + send a message in fragments, use `socket::write`. + */ + template + void + write(ConstBufferSequence const& buffers, error_code& ec); + + /** Start writing a complete message asynchronously. + + This function is used to asychronously write a message to + the websocket. The function call always returns immediately. + + The message opcode will be set to text or binary as + per the current setting of the `message_type` option. + If automatic fragmenting is enabled, the message will be + split into one or more frames as necessary. + + @param buffers The buffers containing the entire message + payload. The implementation will make copies of this object + as needed, but ownership of the underlying memory is not + transferred. The caller is responsible for ensuring that + the memory locations pointed to by buffers remains valid + until the completion handler is called. + + @param handler The handler to be called when the write operation + completes. Copies will be made of the handler as required. The + function signature of the handler must be: + @code + void handler( + error_code const& error // Result of operation + ); + @endcode + 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(). + */ + template + #if GENERATING_DOCS + void_or_deduced + #else + auto + #endif + async_write(ConstBufferSequence const& buffers, + WriteHandler&& handler); + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @throws boost::system::system_error Thrown on failure. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + */ + template + void + write_frame(bool fin, ConstBufferSequence const& buffers) + { + error_code ec; + write_frame(fin, buffers, ec); + detail::maybe_throw(ec, "write"); + } + + /** Send a frame. + + This function is used to write a frame to a stream. The + call will block until one of the following conditions is true: + + @li All of the data in the supplied buffers has been written. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's write_some function. The actual payload sent + may be transformed as per the WebSocket protocol settings. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + @param fin `true` if this is the last frame in the message. + + @param buffers One or more buffers containing the frame's + payload data. + + @param ec Set to indicate what error occurred, if any. + */ + template + void + write_frame(bool fin, + ConstBufferSequence const& buffers, error_code& ec); + + /** Start sending a frame asynchronously. + + This function is used to asynchronously write a WebSocket + frame on the stream. This function call always returns + immediately. + + If this is the beginning of a new message, the message opcode + will be set to text or binary as per the current setting of + the `message_type` option. + + This operation is implemented in terms of one or more calls + to the stream's async_write_some function. The actual payload + sent may be transformed as per the WebSocket protocol settings. + + @param fin A bool indicating whether or not the frame is the + last frame in the corresponding WebSockets message. + + @param buffers A object meeting the requirements of + ConstBufferSequence which holds the payload data before any + masking or compression. Although the buffers object may be copied + as necessary, ownership of the underlying buffers is retained by + the caller, which must guarantee that they remain valid until + the handler is called. + + @param handler The handler to be called when the write completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + boost::system::error_code const& error // result of operation + ); @endcode + */ + template + auto + async_write_frame(bool fin, + ConstBufferSequence const& buffers, WriteHandler&& handler); + +private: + template class accept_op; + template class close_op; + template class handshake_op; + template class response_op; + template class read_op; + template class read_frame_op; + template class write_op; + template class write_frame_op; + + http::request + build_request(boost::string_ref const& host, + boost::string_ref const& resource, + std::string& key); + + template + http::response + build_response(http::message const& req); + + template + void + do_response(http::message const& resp, + boost::string_ref const& key, error_code& ec); + + void + do_read_fh(detail::frame_streambuf& fb, + close_code& code, error_code& ec); +}; + +} // wsproto +} // beast + +#include + +#endif diff --git a/beast/wsproto/src/test/async_echo_peer.h b/beast/wsproto/src/test/async_echo_peer.h new file mode 100644 index 00000000..b81fc823 --- /dev/null +++ b/beast/wsproto/src/test/async_echo_peer.h @@ -0,0 +1,285 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +// Asynchronous WebSocket echo client/server +// +class async_echo_peer +{ +public: + static std::size_t constexpr autobahnCycles = 520; + + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + std::size_t n_ = 0; + +public: + async_echo_peer(bool server, + endpoint_type const& ep, unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + if(server) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{std::move(sock_), ep, suite_}; + } + auto const n = 1; + thread_.reserve(n); + for(int i = 0; i < n; ++i) + thread_.emplace_back(suite_, + [&] { ios_.run(); }); + } + + ~async_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class Peer + { + struct data + { + int state = 0; + unit_test::suite& suite; + boost::optional ep; + wsproto::socket ws; + wsproto::opcode op; + beast::streambuf sb; + int id; + + data(socket_type&& sock_, + unit_test::suite& suite_) + : suite(suite_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + + data(socket_type&& sock_, endpoint_type const& ep_, + unit_test::suite& suite_) + : suite(suite_) + , ep(ep_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + }; + + std::shared_ptr d_; + + public: + Peer(Peer&&) = default; + Peer(Peer const&) = default; + Peer& operator=(Peer&&) = delete; + Peer& operator=(Peer const&) = delete; + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "async_echo_server"); + } + }; + + template + explicit + Peer(socket_type&& sock, Args&&... args) + : d_(std::make_shared( + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.ws.set_option(decorate(identity{})); + //d.ws.set_option(auto_fragment_size(0)); + d.ws.set_option(read_message_max(64 * 1024 * 1024)); + run(); + } + + void run() + { + auto& d = *d_; + if(! d.ep) + { + d.ws.async_accept(std::move(*this)); + } + else + { + d.state = 4; + d.ws.next_layer().async_connect( + *d.ep, std::move(*this)); + } + } + + void operator()(error_code ec) + { + auto& d = *d_; + switch(d_->state) + { + // did accept + case 0: + if(ec) + return fail(ec, "async_accept"); + + // start + case 1: + if(ec) + return fail(ec, "async_handshake"); + d.sb.consume(d.sb.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.sb, std::move(*this)); + return; + + // got message + case 2: + if(ec == wsproto::error::closed) + return; + if(ec) + return fail(ec, "async_read"); + // write message + d.state = 1; + d.ws.set_option(wsproto::message_type(d.op)); + d.ws.async_write(d.sb.data(), std::move(*this)); + return; + + // connected + case 4: + if(ec) + return fail(ec, "async_connect"); + d.state = 1; + d.ws.async_handshake( + d.ep->address().to_string() + ":" + + std::to_string(d.ep->port()), + "/", std::move(*this)); + return; + } + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != wsproto::error::closed) + { + d_->suite.log << + "#" << std::to_string(d_->id) << " " << + what << ": " << ec.message(); + } + } + }; + + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + if(n_ < autobahnCycles) + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + Peer{std::move(sock), suite_}; + } +}; + +} // test +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp b/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp new file mode 100644 index 00000000..188a8200 --- /dev/null +++ b/beast/wsproto/src/test/beast_wsproto_ws_echo_test.cpp @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +class ws_echo_test : public unit_test::suite +{ +public: + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + void + run() override + { + async_echo_peer s1(true, endpoint_type{ + address_type::from_string("127.0.0.1"), + 6000 }, *this); + + sync_echo_peer s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), + 6001 }, *this); + + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + std::mutex m; + bool stop = false; + std::condition_variable cv; + signals.async_wait( + [&](boost::system::error_code const& ec, + int signal_number) + { + std::lock_guard lock(m); + stop = true; + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&]{ return stop; }); + } +}; + +class ws_client_test : public unit_test::suite +{ +public: + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + void + run() override + { + pass(); + { + async_echo_peer s1(false, endpoint_type{ + address_type::from_string("127.0.0.1"), + 9001 }, *this); + } + { + sync_echo_peer s2(false, endpoint_type{ + address_type::from_string("127.0.0.1"), + 9001 }, *this); + } + } +}; + +BEAST_DEFINE_TESTSUITE_MANUAL(ws_echo, asio, beast); +BEAST_DEFINE_TESTSUITE_MANUAL(ws_client, asio, beast); + +} // test +} // wsproto +} // beast diff --git a/beast/wsproto/src/test/beast_wsproto_ws_test.cpp b/beast/wsproto/src/test/beast_wsproto_ws_test.cpp new file mode 100644 index 00000000..05176ce8 --- /dev/null +++ b/beast/wsproto/src/test/beast_wsproto_ws_test.cpp @@ -0,0 +1,362 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +class ws_test : public unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using yield_context = boost::asio::yield_context; + + endpoint_type ep_; + + //-------------------------------------------------------------------------- + + // opcodes for creating the test plans + + // concurrent read and write + struct case_1{}; + + // write a bad frame and shut down + struct case_2{}; + + //-------------------------------------------------------------------------- + + class coro_peer + { + error_code ec_; + boost::asio::io_service ios_; + boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; + socket ws_; + opcode op_; + beast::streambuf rb_; + beast::streambuf wb_; + yield_context* yield_; + int state_ = 0; + //unit_test::suite& test_; + + public: + coro_peer(coro_peer&&) = default; + coro_peer(coro_peer const&) = delete; + coro_peer& operator=(coro_peer&&) = delete; + coro_peer& operator=(coro_peer const&) = delete; + + template + coro_peer(bool server, endpoint_type ep, + unit_test::suite& test, Ops const&... ops) + : acceptor_(ios_) + , sock_(ios_) + , ws_(sock_) + //, test_(test) + { + if(server) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 10; + acceptor_.async_accept(sock_, (*yield_)[ec_]); + if(ec_) + return this->fail("accept"); + state_ = 20; + ws_.async_accept((*yield_)[ec_]); + if(ec_) + return this->fail("ws.accept"); + this->invoke(ops...); + state_ = -1; + }); + } + else + { + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 30; + sock_.async_connect(ep, (*yield_)[ec_]); + if(ec_) + return this->fail("connect"); + state_ = 40; + ws_.async_handshake(ep.address().to_string() + + std::to_string(ep.port()), "/", (*yield_)[ec_]); + if(ec_) + return this->fail("handshake"); + this->invoke(ops...); + state_ = -1; + }); + } + } + + ~coro_peer() + { + } + + int + state() const + { + return state_; + } + + void + run_one() + { + ios_.run_one(); + } + + void + step_to(int to = 0) + { + while(state_ != to) + ios_.run_one(); + } + + private: + template + void fail(String const& s) + { + } + + void invoke_1(case_1) + { + ws_.async_read(op_, rb_, + [&](auto ec) + { + if(ec) + return this->fail(ec); + rb_.consume(rb_.size()); + }); + state_ = 100; + ws_.async_write( + boost::asio::null_buffers{}, (*yield_)[ec_]); + if(ec_) + return fail("write"); + } + + void invoke_1(case_2) + { + detail::frame_header fh; + fh.op = opcode::rsv5; // bad opcode + fh.fin = true; + fh.mask = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = 0; + fh.key = 0; + detail::write(wb_, fh); + state_ = 200; + boost::asio::async_write( + ws_.next_layer(), wb_.data(), + (*yield_)[ec_]); + if(ec_) + return fail("write"); + ws_.next_layer().shutdown( + socket_type::shutdown_both, ec_); + if(ec_) + return fail("shutdown"); + } + + inline + void + invoke() + { + } + + template + inline + void + invoke(Op op, Ops const&... ops) + { + invoke_1(op); + invoke(ops...); + } + }; + + void + testInvokable() + { + endpoint_type const ep{ + address_type::from_string( + "127.0.0.1"), 6000}; + coro_peer server(true, ep, *this, case_1{}); + coro_peer client(false, ep, *this, case_2{}); + server.step_to(10); // async_accept + client.step_to(30); // async_connect + server.step_to(20); // async_accept(ws) + client.step_to(40); // async_handshake + server.step_to(100); // case_1 + client.step_to(200); // case_2 + client.step_to(-1); + server.step_to(-1); + } + + //-------------------------------------------------------------------------- + + bool + maybe_fail(error_code const& ec, std::string const& what) + { + return expect(! ec, what + ": " + ec.message()); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + maybe_fail(ec, what); + throw ec; + } + } + + template + static + std::string + buffers_to_string(Buffers const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + for(auto i = s.size(); i-- > 0;) + if(s[i] == '\r') + s.replace(i, 1, "\\r"); + else if(s[i] == '\n') + s.replace(i, 1, "\\n\n"); + return s; + } + + int + makeRequest(endpoint_type ep, std::string const& s) + { + using boost::asio::buffer; + boost::asio::io_service ios; + boost::asio::ip::tcp::socket sock(ios); + sock.connect(ep); + write(sock, append_buffers( + buffer(s), buffer("\r\n"))); + + using namespace http; + response resp; + streambuf sb; + read(sock, sb, resp); + return resp.status; + } + + void + expectStatus(endpoint_type ep, + int status, std::string const& s) + { + expect(makeRequest(ep, s) == status); + } + + void + testHandshake(endpoint_type ep) + { + expectStatus(ep, 400, "GET / HTTP/1.0\r\n"); + } + + void + syncEchoClient(endpoint_type ep) + { + using boost::asio::buffer; + error_code ec; + boost::asio::io_service ios; + wsproto::socket ws(ios); + ws.next_layer().connect(ep, ec); + if(! maybe_fail(ec, "connect")) + return; + ws.handshake(ep.address().to_string(), "/", ec); + if(! maybe_fail(ec, "upgrade")) + return; + std::string s(65535, '*'); + ws.write_frame(true, buffer(s), ec); + if(! maybe_fail(ec, "write")) + return; + boost::asio::streambuf sb; + wsproto::opcode op; + ws.read(op, sb, ec); + if(! maybe_fail(ec, "read")) + return; + if(! ec) + expect(op == wsproto::opcode::text); + expect(buffers_to_string(sb.data()) == s); + sb.consume(sb.size()); + ws.close({}, ec); + if(! maybe_fail(ec, "close")) + return; + while(! ec) + { + ws.read(op, sb, ec); + if(! ec) + sb.consume(sb.size()); + } + if(ec != error::closed) + maybe_fail(ec, "teardown"); + } + + void + run() override + { + //testInvokable(); + + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + testcase("Echo Server"); + test::sync_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6001}; + testcase("Async Echo Server"); + test::async_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ws,asio,beast); + +} // wsproto +} // beast diff --git a/beast/wsproto/src/test/sync_echo_peer.h b/beast/wsproto/src/test/sync_echo_peer.h new file mode 100644 index 00000000..1e8e4377 --- /dev/null +++ b/beast/wsproto/src/test/sync_echo_peer.h @@ -0,0 +1,182 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { +namespace test { + +// Synchronous WebSocket echo client/server +// +class sync_echo_peer +{ +public: + static std::size_t constexpr autobahnCycles = 520; + + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + unit_test::suite& suite_; + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + unit_test::thread thread_; + std::size_t n_ = 0; + +public: + sync_echo_peer(bool server, + endpoint_type ep, unit_test::suite& suite) + : suite_(suite) + , sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + thread_ = unit_test::thread(suite_, + [&] + { + ios_.run(); + }); + } + + ~sync_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + void + fail(error_code ec, std::string what) + { + suite_.log << + what << ": " << ec.message(); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + ++n_; + std::thread{ + [ + this, + sock = std::move(sock_), + work = boost::asio::io_service::work{ios_} + ]() mutable + { + do_peer(std::move(sock)); + }}.detach(); + if(n_ < autobahnCycles) + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "sync_echo_server"); + } + }; + + void + do_peer(socket_type&& sock) + { + wsproto::socket ws(std::move(sock)); + ws.set_option(decorate(identity{})); + ws.set_option(read_message_max(64 * 1024 * 1024)); + //ws.set_option(auto_fragment_size(0)); + error_code ec; + ws.accept(ec); + if(ec) + { + fail(ec, "accept"); + return; + } + for(;;) + { + wsproto::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + break; + ws.set_option(wsproto::message_type(op)); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != wsproto::error::closed) + { + fail(ec, "read"); + } + } +}; + +} // test +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/ssl.h b/beast/wsproto/ssl.h new file mode 100644 index 00000000..e99faec6 --- /dev/null +++ b/beast/wsproto/ssl.h @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SSL_H_INCLUDED +#define BEAST_WSPROTO_SSL_H_INCLUDED + +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Tear down a `boost::asio::ssl::stream`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The stream to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown( + boost::asio::ssl::stream& stream, + error_code& ec); + +/** Start tearing down a `boost::asio::ssl::stream`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param stream The stream to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + +*/ +template +inline +void +async_teardown( + boost::asio::ssl::stream& stream, + TeardownHandler&& handler); + +} // wsproto +} // beast + +#include + +#endif diff --git a/beast/wsproto/static_string.h b/beast/wsproto/static_string.h new file mode 100644 index 00000000..45eb0858 --- /dev/null +++ b/beast/wsproto/static_string.h @@ -0,0 +1,337 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_STATIC_STRING_H_INCLUDED +#define BEAST_WSPROTO_STATIC_STRING_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** A string with a fixed-size storage area. + + `static_string` objects behave like `std::string` except that + the storage is not dynamically allocated but rather fixed in + size. + + These strings offer performance advantages when a protocol + imposes a natural small upper limit on the size of a value. +*/ +template> +class static_string +{ + std::size_t n_; + std::array s_; + +public: + using traits_type = Traits; + using value_type = typename Traits::char_type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using const_pointer = value_type const*; + using const_reference = value_type const&; + using iterator = value_type*; + using const_iterator = value_type const*; + using reverse_iterator = + std::reverse_iterator; + using const_reverse_iterator = + std::reverse_iterator; + + static_string() + { + resize(0); + } + + static_string(static_string const& s) + : n_(s.n_) + { + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + } + + static_string& + operator=(static_string const& s) + { + n_ = s.n_; + std::copy(&s.s_[0], &s.s_[0]+s.n_+1, &s_[0]); + return *this; + } + + static_string(CharT const* s) + { + assign(s); + } + + static_string& operator=(CharT const* s) + { + assign(s); + return *this; + } + + reference + at(size_type pos) + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + const_reference + at(size_type pos) const + { + if(pos >= n_) + throw std::out_of_range("static_string::at"); + return s_[pos]; + } + + reference + operator[](size_type pos); + + const_reference + operator[](size_type pos) const; + + CharT& + front() + { + return s_[0]; + } + + CharT const& + front() const + { + return s_[0]; + } + + CharT& + back() + { + return s_[n_-1]; + } + + CharT const& + back() const + { + return s_[n_-1]; + } + + CharT* + data() + { + return &s_[0]; + } + + CharT const* + data() const + { + return &s_[0]; + } + + CharT const* + c_str() const + { + s_[n_] = 0; + return &s_[0]; + } + + iterator + begin() + { + return &s_[0]; + } + + const_iterator + begin() const + { + return &s_[0]; + } + + const_iterator + cbegin() const + { + return &s_[0]; + } + + iterator + end() + { + return &s_[n_]; + } + + const_iterator + end() const + { + return &s_[n_]; + } + + const_iterator + cend() const + { + return &s_[n_]; + } + + reverse_iterator + rbegin() + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + rbegin() const + { + return reverse_iterator{end()}; + } + + const_reverse_iterator + crbegin() const + { + return reverse_iterator{cend()}; + } + + reverse_iterator + rend() + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + rend() const + { + return reverse_iterator{begin()}; + } + + const_reverse_iterator + crend() const + { + return reverse_iterator{cbegin()}; + } + + bool + empty() const + { + return n_ == 0; + } + + size_type + size() const + { + return n_; + } + + size_type constexpr + max_size() const + { + return N; + } + + size_type + capacity() const + { + return N; + } + + void + shrink_to_fit() + { + // no-op + } + + void + clear() + { + resize(0); + } + + // Does not perform value-initialization + void + resize(std::size_t n); + + std::basic_string + to_string() const + { + return std::basic_string< + CharT, Traits>{&s_[0], n_}; + } + +private: + void + assign(CharT const* s); +}; + +template +auto +static_string:: +operator[](size_type pos) -> + reference +{ + static CharT null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +auto +static_string:: +operator[](size_type pos) const -> + const_reference +{ + static CharT constexpr null{0}; + if(pos == n_) + return null; + return s_[pos]; +} + +template +void +static_string:: +resize(std::size_t n) +{ + if(n > N) + throw std::length_error( + "static_string overflow"); + n_ = n; + s_[n_] = 0; +} + +template +void +static_string:: +assign(CharT const* s) +{ + size_type n = 0; + for(auto p = s; *p; ++p) + ++n; + if(n > N) + throw std::out_of_range( + "too large"); + std::copy(s, s+n, s_.begin()); +} + +} // wsproto +} // beast + +#endif diff --git a/beast/wsproto/teardown.h b/beast/wsproto/teardown.h new file mode 100644 index 00000000..f184f9e6 --- /dev/null +++ b/beast/wsproto/teardown.h @@ -0,0 +1,161 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_TEARDOWN_H_INCLUDED +#define BEAST_WSPROTO_TEARDOWN_H_INCLUDED + +#include +#include +#include + +namespace beast { +namespace wsproto { + +/** Tear down a connection. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +template +void +teardown(Socket& socket, error_code& ec) = delete; + +/** Start tearing down a connection. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + +*/ +template +void +async_teardown(AsyncSocket& socket, TeardownHandler&& handler) = delete; + +//------------------------------------------------------------------------------ + +/** Tear down a `boost::asio::ip::tcp::socket`. + + This tears down a connection. The implementation will call + the overload of this function based on the `Stream` parameter + used to consruct the socket. When `Stream` is a user defined + type, and not a `boost::asio::ip::tcp::socket` or any + `boost::asio::ssl::stream`, callers are responsible for + providing a suitable overload of this function. + + @param socket The socket to tear down. + + @param ec Set to the error if any occurred. +*/ +void +teardown( + boost::asio::ip::tcp::socket& socket, + error_code& ec); + +/** Start tearing down a `boost::asio::ip::tcp::socket`. + + This begins tearing down a connection asynchronously. + The implementation will call the overload of this function + based on the `Stream` parameter used to consruct the socket. + When `Stream` is a user defined type, and not a + `boost::asio::ip::tcp::socket` or any `boost::asio::ssl::stream`, + callers are responsible for providing a suitable overload + of this function. + + @param socket The socket to tear down. + + @param handler The handler to be called when the request completes. + Copies will be made of the handler as required. The equivalent + function signature of the handler must be: + @code void handler( + error_code const& error // result of operation + ); @endcode + 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(). + +*/ +template +void +async_teardown( + boost::asio::ip::tcp::socket& socket, + TeardownHandler&& handler); + +} // wsproto + +//------------------------------------------------------------------------------ + +namespace wsproto_helpers { + +// Calls to teardown and async_teardown must be made from +// a namespace that does not contain any overloads of these +// functions. The wsproto_helpers namespace is defined here +// for that purpose. + +template +inline +void +call_teardown(Socket& socket, wsproto::error_code& ec) +{ + using wsproto::teardown; + teardown(socket, ec); +} + +template +inline +void +call_async_teardown(Socket& socket, TeardownHandler&& handler) +{ + using wsproto::async_teardown; + async_teardown(socket, + std::forward(handler)); +} + +} // wsproto_helpers + +} // beast + +#include + +#endif diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 00000000..b775bdcf --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,4 @@ +html +temp +reference.qbk +out.txt diff --git a/doc/Jamfile b/doc/Jamfile new file mode 100644 index 00000000..48e72a0a --- /dev/null +++ b/doc/Jamfile @@ -0,0 +1,83 @@ +# +# Copyright (c) 2013-2016 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) +# + +import os ; + +local broot = [ os.environ BOOST_ROOT ] ; + +project beast/doc ; + +using boostbook ; +using quickbook ; +using doxygen ; + +xml beast_boostbook : beast.qbk ; + +path-constant out : . ; + +install stylesheets + : + $(broot)/doc/src/boostbook.css + : + $(out)/html + ; + +explicit stylesheets ; + +install images + : + [ glob $(broot)/doc/src/images/*.png ] + images/beast.png + images/body.png + images/message.png + : + $(out)/html/images + ; + +explicit images ; + +install callouts + : + [ glob $(broot)/doc/src/images/callouts/*.png ] + : + $(out)/html/images/callouts + ; + +explicit callout ; + +boostbook doc + : + beast_boostbook + : + chapter.autolabel=0 + boost.image.src=images/beast.png + boost.image.alt="Beast Logo" + boost.image.w=1007 + boost.image.h=107 + boost.root=$(broot) + chapter.autolabel=0 + chunk.first.sections=1 # Chunk the first top-level section? + chunk.section.depth=8 # Depth to which sections should be chunked + generate.section.toc.level=1 # Control depth of TOC generation in sections + toc.max.depth=2 # How many levels should be created for each TOC? + toc.section.depth=2 # How deep should recursive sections appear in the TOC? + : + temp + stylesheets + images + ; + +#explicit doc ; +# nav.layout=none +# html:location=../bin/doc/html +# generate.toc="chapter nop section nop" +# root.filename=index +# output-root="../bin/html" + + +#[include reference.qbk] +#[xinclude index.xml] diff --git a/doc/beast.dox b/doc/beast.dox new file mode 100644 index 00000000..9529ddde --- /dev/null +++ b/doc/beast.dox @@ -0,0 +1,363 @@ +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "Beast" +PROJECT_NUMBER = +PROJECT_BRIEF = C++ Library +PROJECT_LOGO = images/beast.png +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = NO +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = NO +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = YES +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_FILES = NO +SHOW_NAMESPACES = NO +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = \ + ../beast/asio/append_buffers.h \ + ../beast/asio/async_completion.h \ + ../beast/asio/basic_streambuf.h \ + ../beast/asio/bind_handler.h \ + ../beast/asio/buffers_adapter.h \ + ../beast/asio/consuming_buffers.h \ + ../beast/asio/handler_alloc.h \ + ../beast/asio/prepare_buffers.h \ + ../beast/asio/static_streambuf.h \ + ../beast/asio/streambuf.h \ + ../beast/asio/streambuf_readstream.h \ + ../beast/asio/type_check.h \ + ../beast/http/basic_headers.h \ + ../beast/http/basic_parser.h \ + ../beast/http/chunk_encode.h \ + ../beast/http/empty_body.h \ + ../beast/http/error.h \ + ../beast/http/message.h \ + ../beast/http/parser.h \ + ../beast/http/read.h \ + ../beast/http/resume_context.h \ + ../beast/http/stream.h \ + ../beast/http/streambuf_body.h \ + ../beast/http/string_body.h \ + ../beast/http/type_check.h \ + ../beast/http/write.h \ + ../beast/wsproto/error.h \ + ../beast/wsproto/option.h \ + ../beast/wsproto/rfc6455.h \ + ../beast/wsproto/socket.h \ + ../beast/wsproto/static_string.h \ + ../beast/wsproto/teardown.h \ + +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = ../beast/http/URL.h +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = NO +HTML_OUTPUT = dhtm +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = YES +XML_OUTPUT = ../bin/doc/xml +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = ../ +INCLUDE_FILE_PATTERNS = +PREDEFINED = DOXYGEN \ + GENERATING_DOCS +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/doc/beast.qbk b/doc/beast.qbk new file mode 100644 index 00000000..8f0cbfa8 --- /dev/null +++ b/doc/beast.qbk @@ -0,0 +1,177 @@ +[/ + Copyright (c) 2013-2016 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) +] + +[library Beast + [quickbook 1.6] + [copyright 2013 - 2016 Vinnie Falco] + [purpose C++ Library] + [license + 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]) + ] + [authors [Falco, Vinnie]] + [category template] + [category generic] +] + +[template mdash[] '''— '''] +[template indexterm1[term1] ''''''[term1]''''''] +[template indexterm2[term1 term2] ''''''[term1]''''''[term2]''''''] +[template ticket[number]''''''#[number]''''''] +[def __POSIX__ /POSIX/] +[def __Windows__ /Windows/] +[def __accept__ [@http://www.opengroup.org/onlinepubs/000095399/functions/accept.html `accept()`]] +[def __connect__ [@http://www.opengroup.org/onlinepubs/000095399/functions/connect.html `connect()`]] +[def __getpeername__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getpeername.html `getpeername()`]] +[def __getsockname__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockname.html `getsockname()`]] +[def __getsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/getsockopt.html `getsockopt()`]] +[def __ioctl__ [@http://www.opengroup.org/onlinepubs/000095399/functions/ioctl.html `ioctl()`]] +[def __recvfrom__ [@http://www.opengroup.org/onlinepubs/000095399/functions/recvfrom.html `recvfrom()`]] +[def __sendto__ [@http://www.opengroup.org/onlinepubs/000095399/functions/sendto.html `sendto()`]] +[def __setsockopt__ [@http://www.opengroup.org/onlinepubs/000095399/functions/setsockopt.html `setsockopt()`]] +[def __socket__ [@http://www.opengroup.org/onlinepubs/000095399/functions/socket.html `socket()`]] + + + +[section:intro Introduction] + +Beast is a cross-platform C++ library built on Boost, containing two modules +implementing widely used network protocols. Beast.HTTP offers a universal +model for describing, sending, and receiving HTTP messages while Beast.WSProto +provides a complete implementation of the WebSocket protocol. Their design +achieves these goals: + +* [*Symmetry.] Interfaces are role-agnostic; the same interfaces can be +used to build clients, servers, or both. + +* [*Ease of Use.] HTTP messages are modeled using simple, readily +accessible objects. Functions and classes used to send and receive HTTP +or WebSocket messages are designed to resemble Boost.Asio as closely as +possible. Users familiar with Boost.Asio will be immediately comfortable +using this library. + +* [*Flexibility.] Interfaces do not mandate specific implementation +strategies; important decisions such as buffer or thread management are +left to users of the library. + +* [*Performance.] The implementation performs competitively, making it a +realistic choice for building a high performance network server. + +* [*Scalability.] Development of network applications that scale to thousands +of concurrent connections is possible with the implementation. + +* [*Basis for further abstraction.] The interfaces facilitate the +development of other libraries that provide higher levels of abstraction. + + + +[section:requirements Requirements] + +Beast requires: + +* [*C++11.] A minimum of C++11 is needed. +* [*Boost.] Beast is built on Boost, especially Boost.Asio. +* [*OpenSSL.] If using TLS/Secure sockets (optional). + +[note Tested compilers: msvc-14+, gcc 5+, clang 3.6+] + +Most of the library is header-only; however, the HTTP parser used is written +in C. To link an application that uses Beast, it is necessary to add a single +.cpp file from beast into your project's build script. + +[endsect] + + + +[section:example Examples] + +These usage examples are intended to quickly impress upon readers the +flavor of the library. + +[note + All examples and identifiers mentioned in this document are written as + if the following statements are in effect: + ``` + #include + #include + using namespace beast; + using namespace beast::http; + using namespace boost::asio; + using namespace boost::asio::ip; + ``` +] + +Use HTTP to request the root page from a website and receive the response: +``` + std::string const host = "boost.org"; + io_service ios; + tcp::resolver r(ios); + tcp::socket sock(ios); + connect(sock, r.resolve(tcp::resolver::query{host, "http"})); + + request req(method_t::http_get, "/", 11); + req.headers.replace("Host", host + ":" + std::to_string(sock.remote_endpoint().port())); + req.headers.replace("User-Agent", "Beast"); + write(sock, prepare(req, connection(close))); + + parsed_response resp; + read(sock, resp); + ... +``` + +Establish a WebSocket connection, send a message and receive the reply: +``` + std::string const host = "boost.org"; + io_service ios; + tcp::resolver r(ios); + tcp::socket sock(ios); + connect(sock, r.resolve(tcp::resolver::query{host, "ws"})); + + wsproto::socket ws(sock); + ws.handshake(); + ws.write(ws, buffer("Hello, world!")); + + streambuf sb; + wsproto::opcode op; + ws.read(ws, op, sb); + + ws.close(); // WebSocket protocol close +``` + +[endsect] + + + +[section:credits Credits] + +Beast would not be possible without the considerable time and patience +contributed by David Schwartz, Edward Hennis, Howard Hinnant, Miguel Portilla, +Nikolaos Bougalis, Scott Determan, Scott Schurr, and Ripple Labs for +supporting its development. Thanks also to Christopher Kohloff, whose Asio +C++ library is the inspiration behind which much of the design and +documentation is based. + +[endsect] + + + +[endsect] + + + +[include http.qbk] +[include wsproto.qbk] +[include types.qbk] +[include design.qbk] +[section:quickref Quick Reference] +[xinclude quickref.xml] +[endsect] +[include reference.qbk] +[section:idx Index] +[xinclude index.xml] +[endsect] diff --git a/doc/boostbook.dtd b/doc/boostbook.dtd new file mode 100644 index 00000000..bd4c3f87 --- /dev/null +++ b/doc/boostbook.dtd @@ -0,0 +1,439 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +%DocBook; diff --git a/doc/design.qbk b/doc/design.qbk new file mode 100644 index 00000000..316b83b6 --- /dev/null +++ b/doc/design.qbk @@ -0,0 +1,213 @@ +[/ + Copyright (c) 2013-2016 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:design Design choices] + +The implementations are driven by business needs of cryptocurrency server +applications ([link https://ripple.com Ripple] written in C++. These +needs were not met by existing solutions so new code was written. The new +code tries to avoid design flaws encountered in the already-existing software +libraries: + +* Don't sacrifice performance. + +* Don't do too much, otherwise interfaces become rigid. + +* Symmetric interfaces (client and server the same, or close to it). + +* Emulate Boost.Asio interfaces as much as possible, since Asio is + proven and it is familiar to users. + +* Let library users make the important decisions such as how to + allocate memory or how to leverage flow control. + +Beast formalizes the [link beast.types.Streambuf [*`Streambuf`]] concept, +and relies heavily on the Boost.Asio [*`ConstBufferSequence`] and +[*`MutableBufferSequence`] concepts for passing buffers to functions. +The authors have found the `Streambuf` and buffer sequence interfaces +to be optimal for interacting with Asio, and for other tasks such as +incremental parsing of data in buffers (for example, parsing websocket +frames stored in a [link beast.ref.static_streambuf `static_streambuf`]). + +During the development of Beast the authors have studied other software +packages and in particular the comments left during the Boost Review process +of other packages offering similar functionality. In this section we attempt +to address those issues. + +[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 will be used in [*rippled], an + asynchronous peer to peer server that implements the + [*Ripple Consensus Protocol]. These servers are deployed in multiple + production environments, with banks in many countries running client + applications that connect to [*rippled]. +]] + +] + + +[section:http HTTP] + +For HTTP we to 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, +as a WebSocket session requires a HTTP Upgrade handshake exchange at the +start. Other design goals: + +* Don't try to invent a complete web server or client + +* Have simple free functions to send and receive messages. + +* Allow the message object to be customized, + +[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 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.HTTP does + not try to offer a web server. +]] + +[[ + "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.HTTP 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. +]] + +[[ + "You should send a 100-continue to ask for the rest of the body if required." +][ + These behaviors are best left to the calling software. A future library + can build on Beast.HTTP to provide these behaviors. +]] + +[[ + "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 IEFT 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 logicaly + 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. A 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] + +[variablelist +[[ + What about message compression? +][ + The feature is not currently present in the library, but the choice + of type requirements for buffers passed to the read functions have been + made with compression in mind. There is the plan to add this feature; + however, we feel that even without compression users can begin taking + advantage of the WebSocket protocol immediately with this library. +]] + +[[ + Where is the TLS/SSL interface? +][ + The `websocket::stream` just 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, they 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] provides support for shutting down + the TLS connection through the use of the ADL compile-time virtual functions + [link beast.ref.wsproto__teardown `teardown`] and + [link beast.ref.wsproto__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] + + + + + + + +[endsect] diff --git a/doc/http.qbk b/doc/http.qbk new file mode 100644 index 00000000..5120092f --- /dev/null +++ b/doc/http.qbk @@ -0,0 +1,215 @@ +[/ + Copyright (c) 2013-2016 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 HTTP] + +Beast.HTTP offers programmers simple and performant models of HTTP messages and +their associated operations including synchronous and asynchronous reading and +writing using Boost.Asio. + +The HTTP protocol is described fully in +[@https://tools.ietf.org/html/rfc2616 rfc2616] + + + +[section:motivation Motivation] + +The HTTP protocol is pervasive in network applications. As C++ is a logical +choice for high performance network servers, there is great utility in solid +building blocks for manipulating, sending, and receiving HTTP messages +compliant with the Hypertext Transfer Protocol and the supplements that +follow. Unfortunately reliable implementations or industry standards do not +exist in C++. + +Beast.HTTP is built on Boost.Asio and uses HTTP parser from NodeJS, which is +extensively field tested and exceptionally robust. A proposal to add networking +functionality to the C++ standard library, based on Boost.Asio, is under +consideration by the standards committee. Since the final approved networking +interface for the C++ standard library will likely closely resemble the current +interface of Boost.Asio, it is logical for Beast.HTTP to use Boost.Asio as its +network transport. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the HTTP protocol specification described in +[@https://tools.ietf.org/html/rfc2616 rfc2616] ] + +[endsect] + + + +[section:example Example] + +All examples and identifiers mentioned in this document are written as +if the following declarations are in effect: +``` +#include +using namespace beast::http; +using namespace boost::asio; +``` + +Create a HTTP request: +``` +request req({method_t::http_get, "/", 11}); +req.headers.insert("Host", "127.0.0.1:80"); +req.headers.insert("User-Agent", "Beast.HTTP"); + +``` + +To send a message it must first be prepared through a call to `prepare`. This +customization point transforms the `message` into a `prepared_message`, +filling in some standard HTTP behavior and allowing the Body associated with +the message to perform preparatory steps. For example, a string body may set +the Content-Length and Content-Type appropriately. +``` +void send_request(ip::tcp::socket& sock, + request&& req) +{ + // Send the request on the socket + write(sock, prepare(req, connection(keep_alive)); +} +``` + +Messages can be read from the network and parsed into a `parsed_message` object, +which extends the `message` by adding parse-specific metadata such as the +keep-alive which is context sensitive (depending on the HTTP version for +example). When preparing a response for sending, `prepare` must be called with +an additional parameter, the corresponding parsed request. The implementation +inspects the contents of the request to set dependent fields of the response. +This example reads a message, builds a response, and sends it. +``` +void handle_connection(ip::tcp::socket& sock) +{ + parsed_request req; + read(sock, req); + response resp; + ... + write(sock, prepare(resp, req)); +} +``` + +[endsect] + + + +[section:message Message model] + +A HTTP message (referred to hereafter as "message") contains a request or +response line, a series of zero or more name/value pairs (collectively +termed "headers"), and a series of octets called the message body which may +be zero in length. Applications using HTTP often perform these operations +on messages: + +* [*Parse] a new message from a series of octets. + +* [*Assemble] a new message from scratch or from an existing message. + +* [*Serialize] a message into a series of octets. + +* [*Read] a message from a stream. This can be thought of as a compound +operation; a network read, followed by a [*parse]. + +* [*Write] a message to a stream. This can be thought of as a compound +operation: a [*serialize] followed by a network write. + +The spectrum of hardware and software platforms which perform these typical +HTTP operations is vast, ranging from powerful servers in large datacenters +to tiny resource-limited embedded devices. No single concrete implementation +of a class intended to model messages can efficiently serve all needs. +For example, an object that minimizes resources during parsing may not be +able to edit and change headers dynamically. A message that represents the +message body as a disk file may support sending but not parsing. Many efficient +and correct models of messages exist, supporting some or all of the +operations listed above. + +To support a variety of implementation strategies, we introduce customization +points for the header and body via class template arguments. This illustration +shows the message class template (boilerplate present in the actual +declaration has been removed for clarity): + +[$images/message.png [width 580px] [height 225px]] + +The default constructor, move special members, and copy special members are +all defaulted. A message is movable, copyable, or default constructible based +on the capabilities of its template arguments. + +Messages modeled in this fashion are ['complete], containing all of the +information required to perform the supported set of operations. They are +['first-class types], returnable from functions and composable. HTTP +requests and responses are distinct types, allowing functions to be +overloaded on the type of message. + +[endsect] + + + +[section:body Body parameter] + +The `Body` template argument in the `message` class must meet the +[link beast.types.Body [*`Body`] requirements]. It provides customization +of the data member in the message, the algorithm for parsing, and the +algorithm for serialization: + +[$images/body.png [width 510px] [height 210px]] + +The following types, provided by the library, meet the `Body` requirements: + +* [link beast.ref.http__empty_body [*`empty_body`:]] An empty message body. +Used in GET requests where there is no message body. + +* [link beast.ref.http__string_body [*`string_body`:]] A body with a +`value_type` of `std::string`. Useful for quickly putting together a request +or response with simple text in the message body (such as an error message). +Subject to the performance limit of strings if large amounts of data are +inserted. + +* [link beast.ref.http__basic_streambuf_body [*`basic_streambuf_body`:]] A body with a +`value_type` of `streambuf`. A streambuf is an efficient storage object which +uses multiple octet arrays of varying lengths to represent data. + +Instances of the optional nested types `writer` and `reader` perform +serialization and deserialization of the message body. If either or +both of these types are present, the message becomes serializable, parsable, +or both. They model [link beast.types.Reader [*`Reader`]] and +[link beast.types.Writer [*`Writer`]] respectively. + +For specialized applications, users may implement their own types which +meet the requirements. The examples included with this library provide a +Body implementation used to serve files in a HTTP server. + +[endsect] + + + +[section:headers Headers container] + +The `Headers` type represents the field/value pairs present in every HTTP +message. These types implement the +[link beast.types.FieldSequence [*`FieldSequence`]] +concept. The value type of a field sequence is an object meeting the +requirements of [link beast.types.Field [*`Field`]]. The implementation can +serialize any instance of `Headers` that meets the field sequence requirements. +This example shows a function which returns `true` if the specified field +sequence has a connect field: +``` +template +bool +has_connect(FieldSequence const& fs) +{ + return std::find_if(fs.begin(), fs.end(), + [&](auto const& field) + { + return ci_equal(field.name(), "Connect"); + }); +} +``` + +[endsect] + + + +[endsect] + diff --git a/doc/images/beast.png b/doc/images/beast.png new file mode 100644 index 0000000000000000000000000000000000000000..3444d5814c8787c832b85944a28d2b2768d20330 GIT binary patch literal 42936 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

2aI&T*k8IPv`Xexzb+ody*c;*S`$gGR5}R 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 Co-o}2 literal 0 HcmV?d00001 diff --git a/doc/images/beast.psd b/doc/images/beast.psd new file mode 100644 index 0000000000000000000000000000000000000000..81096530f5bbca260ca6375f962a06980cd64597 GIT binary patch literal 166000 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

&=?K(aaJOd6ZfS>R3F6$!au#GJ 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%0DIeEoa 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 bPzSUTD=Lwj?}VEAWsky|F literal 0 HcmV?d00001 diff --git a/doc/images/message.png b/doc/images/message.png new file mode 100644 index 0000000000000000000000000000000000000000..cb07efc82fb17c79d46203234e71e2be83191183 GIT binary patch literal 9532 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%0Aax5!$Nb0Qx^g+xt6!$d1Y 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 literal 0 HcmV?d00001 diff --git a/doc/index.xml b/doc/index.xml new file mode 100644 index 00000000..0bbf7b76 --- /dev/null +++ b/doc/index.xml @@ -0,0 +1,13 @@ + + + + + +

+ +
diff --git a/doc/makeqbk.sh b/doc/makeqbk.sh new file mode 100644 index 00000000..584cb8a9 --- /dev/null +++ b/doc/makeqbk.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +# Copyright (c) 2013-2016 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) + +mkdir -p ../bin/doc/xml +doxygen beast.dox +cd ../bin/doc/xml +xsltproc combine.xslt index.xml > all.xml +cd ../../../doc +xsltproc reference.xsl ../bin/doc/xml/all.xml > reference.qbk diff --git a/doc/quickref.xml b/doc/quickref.xml new file mode 100644 index 00000000..2f015ecf --- /dev/null +++ b/doc/quickref.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + HTTP + + + WebSocket + + + + + + + Classes + + basic_headers + basic_parser + basic_streambuf_body + empty_body + error_code + message + resume_context + stream + streambuf_body + string_body + + Type Traits + + is_Body + + + + Functions + + async_read + async_write + chunk_encode + chunk_encode_final + read + write + + Concepts + + Body + Field + FieldSequence + Reader + Writer + + + + Classes + + close_reason + socket + static_string + + Options + + auto_fragment_size + decorate + keep_alive + read_buffer_size + read_message_max + write_buffer_size + + + + Functions + + async_teardown + teardown + + Constants + + close_code + opcode + + + + + + + + + + + + + + Core + + + + + + + Classes + + async_completion + basic_streambuf + buffers_adapter + consuming_buffers + handler_alloc + prepared_buffers + static_streambuf + static_streambuf_n + streambuf + streambuf_readstream + + + + Functions + + append_buffers + bind_handler + prepare_buffer + prepare_buffers + + + + Type Traits + + is_AsyncReadStream + is_AsyncWriteStream + is_BufferSequence + is_ConstBufferSequence + is_Handler + is_MutableBufferSequence + is_Stream + is_Streambuf + is_SyncReadStream + is_SyncWriteStream + + + + Concepts + + BufferSequence + Stream + Streambuf + + + + + + diff --git a/doc/reference.xsl b/doc/reference.xsl new file mode 100644 index 00000000..fd2cbb31 --- /dev/null +++ b/doc/reference.xsl @@ -0,0 +1,1725 @@ + + + + + + + + + + + + + + + + + +[/ + Copyright (c) 2013-2016 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:ref Reference] + + + + + + + + + + + + + + + + [endsect] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``[link beast.ref.ConstBufferSequence ['ConstBufferSequence]]`` + + + ``['implementation-defined]`` + + + ``[link beast.ref.asynchronous_operations.return_type_of_an_initiating_function ['void-or-deduced]]`` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [heading + + ] + + + + + + + + + + + + + + `` + + + + + + `` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + + ` + + + + ` + + ` + + + + * + + + + + + + +[*] + +['] + + + + + [heading Parameters] + + + [heading Exceptions] + + + [variablelist + + ] + + + + [[ + + ][ + + ]] + + + + + + + + [heading Return Value] + + + + + + [heading Remarks] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \_ + + + + + + + + + + + + + + + + + \[ + + + + + + + \] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + + + + + + + + + + + + + + [link beast.ref. + + ` + + `] + + + ` + + ` + + + + + + + + + + [heading Requirements] + ['Header: ][^ + + ] + ['Convenience header: ] + + + [^beast/asio.h] + + + [^beast/http.h] + + + [^beast/wsproto.h] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + + ``` + + + + + + : + + + + + + public + + + + + + , + + + + ``` + + + + + + + + + + + + + +[endsect] + + + + + + + [heading Types] + [table [[Name][Description]] + + + [ + + + [[link beast.ref. + + . + + [* + + ]]] [ + + + + ] + + + + + + + + + + + + + + + + + + + [[link beast.ref. + + [* + + ]]] [ + + ] + + + ] + + ] + + + [heading Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Protected Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Private Member Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Protected Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Private Data Members] + [table [[Name][Description]] + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + ] ] + + ] + + + [heading Friends] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref. + . + [* + + ]]] [ + + + + + + + + ] ] + + + ] + + + [heading Related Functions] + [table [[Name][Description]] + + + + + + + + + + + + + + + + + + + + + + + + + [ + [[link beast.ref.. + [*]]] + [ + + + + + + + + + ] + ] + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + :: + + ] + [indexterm2 + + .. + + ] + + ``` + + + + + ``` + + ``` + + + + explicit + friend + static + virtual + + + + + + + + + ``[link beast.ref. + + . + + .overload + + + + ]``( + + ) + + const + + ; + + ``` + + + + + [section: + + + + + overload + + + + + :: + + + ( + + of + + overloads) + + ] + + + + ['Inherited from + + + + .] + + + + [indexterm2 + + .. + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + + + + + ``` using + + = + + + + + + + + + ; ``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ``` + + static + + + + + + + + + ; ``` + + + + + + + ``` + enum + + ``` + + + + [indexterm2 + + .. + + ] + + + [heading Values] + [variablelist + + [[ + + ] [ + + ]] + + ] + + + + + + + + + + + + + + + + + + + + + + + static + virtual + + + + + + ( + + ) + const + ; + + + + + template< + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = + + + + , + + + + + + + + + (& + + ) + + + + + + + & + + + + * + + + + + + + + + + = + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [section: + + + + ] + [indexterm1 + + ] + + + + + + + + + + + ``` + + + + + + + + + + + + ``[link beast.ref. + + .overload + + + + ]``( + + ); + + ``` + + + + + + + + + [section: + + + + [section: + overload + + + + + + ( + + of + + overloads) + + ] + + [indexterm1 + + ] + + + + + + + + + + + + + + + + + + ``` + + ``` + + + + + + + + + + + [endsect] [endsect] + + + [endsect] + + + + + diff --git a/doc/types.qbk b/doc/types.qbk new file mode 100644 index 00000000..535a4b49 --- /dev/null +++ b/doc/types.qbk @@ -0,0 +1,446 @@ +[/ + Copyright (c) 2013-2016 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:types Type Requirements] + + + +[section:Body Body] + +In this table: + +* `X` is a type meeting the requirements of [*`Body`]. + +[table Body requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + The type of the `message::body` member. + If this is not movable or not copyable, the containing message + will be not movable or not copyable. + ] +] +[ + [`X:value_type{}`] + [] + [`DefaultConstructible`] +] +[ + [`Body::reader`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Reader [*`Reader`]]. + Provides an implementation to parse the body. + ] +] +[ + [`Body::writer`] + [] + [ + If present, a type meeting the requirements of + [link beast.types.Writer [*`Writer`]]. + Provides an implementation to serialize the body. + ] +] +] + +[endsect] + + + +[section:BufferSequence BufferSequence] + +A `BufferSequence` meets [*one of] the following requirements: + +* `ConstBufferSequence` +* `MutableBufferSequence` + +[endsect] + + + +[section:Field Field] + +A [*`Field`] represents a single HTTP header field/value pair. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Field`]. +* `a` denotes a value of type `X`. + +[table Field requirements + +[[operation][type][semantics, pre/post-conditions]] +[ + [`a.name()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the case-insensitve field + name, without leading or trailing white space. + ] +] +[ + [`a.value()`] + [`boost::string_ref`] + [ + This function returns a value implicitly convertible to + `boost::string_ref` containing the value for the field. The + value is considered canonical if there is no leading or + trailing whitespace. + ] +] +] + +[endsect] + + + +[section:FieldSequence FieldSequence] + +A [*`FieldSequence`] is an iterable container whose value type meets +the requirements of [link beast.types.Field [*`Field`]]. + +In this table: + +* `X` denotes a type that meets the requirements of [*`FieldSequence`]. + +* `a` is a value of type `X`. + +[table FieldSequence requirements +[[operation][type][semantics, pre/post-conditions]] +[ + [`X::value_type`] + [] + [ + A type that meets the requirements of `Field`. + ] +] +[ + [`X::const_iterator`] + [] + [ + A type that meets the requirements of `ForwardIterator`. + ] +] +[ + [`a.begin()`] + [`X::const_iterator`] + [ + Returns an iterator to the beginning of the field sequence. + ] +] +[ + [`a.end()`] + [`X::const_iterator`] + [ + Returns an iterator to the end of the field sequence. + ] +] +] + +[endsect] + + + +[section:Reader Reader] + +Parser implementations will construct the corresponding `reader` object +during the parse. This customization point allows the Body to determine +the strategy for storing incoming message body data. + +In this table: + +* `X` denotes a type meeting the requirements of [*`Reader`]. + +* `a` denotes a value of type `X`. + +* `p` is any pointer. + +* `n` is a value convertible to `std::size_t`. + +* `ec` is a value of type `error_code&`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true` + + +[table Reader requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X a(m);`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is + guaranteed to end no earlier than after `a` is destroyed. + ] +] +[ + [`a.write(p, n, ec)`] + [`void`] + [ + Deserializes the input sequence into the body. + If `ec` is set, the deserialization is aborted and the error + is returned to the caller. + ] +] +] + +[note Definitions for required `Reader` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +[endsect] + + + +[section:Writer Writer] + +A `Writer` serializes the message body. The implementation creates an instance +of this type when serializing a message, and calls into it zero or more times +to provide buffers containing the data. The interface of `Writer` is intended +to allow serialization in these scenarios: + +* A body that does not entirely fit in memory. +* A body produced incrementally from coroutine output. +* A body represented by zero or more buffers already in memory. +* A body as a series of buffers when the content size is not known ahead of time. +* Body data generated on demand from other threads. +* Body data computed algorithmically. + +In this table: + +* `X` denotes a type meeting the requirements of `Writer`. + +* `a` denotes a value of type `X`. + +* `m` denotes a value of type `message const&` where + `std::is_same:value == true`. + +* `rc` is an object of type [link beast.reference.http__resume_context resume_context]. + +* `ec` is a value of type `error_code&`. + +* `wf` is a [*write function]: a function object of unspecified type provided + by the implementation which accepts any value meeting the requirements + of `ConstBufferSequence` as its single parameter. + +[table Writer requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X a(m);`] + [] + [ + `a` is constructible from `m`. The lifetime of `m` is + guaranteed to end no earlier than after `a` is destroyed. + ] +] +[ + [`a.init(ec)`] + [`void`] + [ + Called immediately after construction. + If `ec` is set, the serialization is aborted and the error + is propagated to the caller. + ] +] +[ + [`a.content_length()`] + [`std::size_t`] + [ + If this member is present, it is called after initialization + and before calls to provide buffers. The serialized message will + have the Content-Length field set to the value returned from + this function. If this member is absent, the serialized message + body will be chunk-encoded for HTTP versions 1.1 and later, else + the serialized message body will be sent unmodified, with the + error `boost::asio::error::eof` returned to the caller, to notify + they should close the connection to indicate the end of the message. + ] +] +[ + [`a(rc, ec, wf)`] + [`boost::tribool`] + [ + Called repeatedly after `init` succeeds. + `wf` is a function object which takes as its single parameter, + any value meeting the requirements of `ConstBufferSequence`. + Buffers provided by the `writer` to this [*write function] must + remain valid until the next member function of `writer` is + invoked (which may be the destructor). This function returns `true` + to indicate all message body data has been written, or `false` + if there is more body data. If the return value is + `boost::indeterminate`, the implementation will suspend the operation + until the writer invokes `rc`. It is the writers responsibility when + returning `boost::indeterminate`, to acquire ownership of the + `resume_context` via move construction and eventually call it or else + undefined behavior results. + ] +] +] + +[note Definitions for required `Writer` member functions should be declared +inline so the generated code becomes part of the implementation. ] + +Exemplar: +``` +struct writer +{ +public: + /** Construct the writer. + + The msg object is guaranteed to exist for the lifetime of the writer. + + Exceptions: + No-throw guarantee. + + @param msg The message whose body is to be written. + */ + template + explicit + writer(message const& msg); + + /** Initialize the writer. + + Called once immediately after construction. + The writer can perform initialization which may fail. + + @param ec Contains the error code if any errors occur. + */ + void + init(error_code& ec); + + /** Returns the content length. + + If this member is present, the implementation will set the + Content-Length field accordingly. If absent, the implementation will + use chunk-encoding or terminate the connection to indicate the end + of the message. + */ + std::size_t + content_length() const; + + /** Write zero or one buffer representing the message body. + + Postconditions: + + If return value is `true`: + * Callee does not take ownership of resume. + * Callee made zero or one calls to `write`. + * There is no more data remaining to write. + + If return value is `false`: + * Callee does not take ownership of resume. + * Callee made one call to `write`. + + If return value is boost::indeterminate: + * Callee takes ownership of `resume`. + * Caller suspends the write operation + until `resume` is invoked. + + When the caller takes ownership of resume, the + asynchronous operation will not complete until the + caller destroys the object. + + @param resume A functor to call to resume the write operation + after the writer has returned boost::indeterminate. + + @param ec Set to indicate an error. This will cause an + asynchronous write operation to complete with the error. + + @param write A functor the writer will call to provide the next + set of buffers. Ownership of the buffers is not transferred, + the writer must guarantee that the buffers remain valid until the + next member function is invoked, which may be the destructor. + + @return `true` if there is data, `false` when done, + boost::indeterminate to suspend. + + @note Undefined behavior if the callee takes ownership + of resume but does not return boost::indeterminate. + */ + template + boost::tribool + operator()(resume_context&&, error_code&, WriteFunction&& write); +}; +``` + +[endsect] + + + +[section:Stream Stream] + +A `Stream` meets the following requirements: + +* `SyncReadStream` +* `SyncWriteStream` +* `AsyncReadStream` +* `AsyncWriteStream` + +[endsect] + + + +[section:Streambuf Streambuf] + +In the table below, `X` denotes a class, `a` denotes a value +of type `X`, `n` denotes a value convertible to `std::size_t`, +and `U` and `T` denote unspecified types. + +[table Streambuf requirements +[[operation] [type] [semantics, pre/post-conditions]] +[ + [`X::const_buffers_type`] + [`T`] + [`T` meets the requirements for `ConstBufferSequence`.] +] +[ + [`X::mutable_buffers_type`] + [`U`] + [`U` meets the requirements for `MutableBufferSequence`.] +] +[ + [`a.commit(n)`] + [`void`] + [Moves bytes from the output sequence to the input sequence.] +] +[ + [`a.consume(n)`] + [`void`] + [Removes bytes from the input sequence.] +] +[ + [`a.data()`] + [`T`] + [Returns a list of buffers that represents the input sequence.] +] +[ + [`a.prepare(n)`] + [`U`] + [Returns a list of buffers that represents the output sequence, with + the given size.] +] +[ + [`a.size()`] + [`std::size_t`] + [Returns the size of the input sequence.] +] +[ + [`a.max_size()`] + [`std::size_t`] + [Returns the maximum size of the `Streambuf`.] +] +] + +[endsect] + + + +[endsect] diff --git a/doc/wsproto.qbk b/doc/wsproto.qbk new file mode 100644 index 00000000..0ed910d0 --- /dev/null +++ b/doc/wsproto.qbk @@ -0,0 +1,391 @@ +[/ + Copyright (c) 2013-2016 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:wsproto WSProto] + +The WebSocket Protocol enables two-way communication between a client +running untrusted code in a controlled environment to a remote host that has +opted-in to communications from that code. The protocol consists of an opening +handshake followed by basic message framing, layered over TCP. The goal of +this technology is to provide a mechanism for browser-based applications that +need two-way communication with servers that does not rely on opening multiple +HTTP connections. + +Beast.WSProto provides developers with a robust WebSocket implementation +built on Boost.Asio with a consistent asynchronous model using a modern +C++ approach. + +The WebSocket protocol is described fully in +[@https://tools.ietf.org/html/rfc6455 rfc6455] + + + +[section:motivation Motivation] + +Today's web applications increasingly rely on alternatives to standard HTTP +to achieve performance and/or responsiveness. While WebSocket implementations +are widely available in common web development languages such as Javascript, +good implementations in C++ are scarce. A survey of existing C++ WebSocket +solutions reveals interfaces which lack symmetry, impose performance penalties, +and needlessly restrict implementation strategies. + +Beast.WSProto is built on Boost.Asio, a robust cross platform networking +framework that is part of Boost and also offered as a standalone library. +A proposal to add networking functionality to the C++ standard library, +based on Boost.Asio, is under consideration by the standards committee. +Since the final approved networking interface for the C++ standard library +will likely closely resemble the current interface of Boost.Asio, it is +logical for Beast.WSProto to use Boost.Asio as its network transport. + +Beast.WSProto takes advantage of Boost.Asio's extensible asynchronous +model, handler allocation, and handler invocation hooks. Calls to +Beast.WSProto asynchronous initiation functions allow callers the choice +of using a completion handler, stackful or stackless coroutines, futures, +or user defined customizations (for example, Boost.Fiber). The +implementation uses handler invocation hooks (`asio_handler_invoke`), +providing execution guarantees on composed operations in a manner +identical to Boost.Asio. The implementation also uses handler allocation +hooks (`asio_handler_allocate`) when allocating memory internally for +composed operations. + +There is no need for inheritance or virtual members in `wsproto::socket`. +All operations are templated and transparent to the compiler, allowing for +maximum inlining and optimization. + +[note The documentation which follows assumes familiarity with +both Boost.Asio and the WebSocket protocol specification described in +[@https://tools.ietf.org/html/rfc6455 rfc6455] ] + +[endsect] + + + +[section:creating Creating the socket] + +The interface to Beast's WebSocket implementation is a single template +class which wraps an object meeting the requirements of `SyncStream` if +synchronous operations are performed, `AsyncStream` if asynchronous +operations are performed, or both. Arguments supplied during construction +are passed to the `Stream` constructor. Here we declare two websockets +which contain the underlying stream: +``` +io_service ios; +wsproto::socket ws(ios); + +ssl::context ctx(ssl::context::sslv23); +wsproto::socket> wss(ios, ctx); +``` + +For servers that can handshake in multiple protocols, it may be desired +to wrap a socket that already exists. The socket can be moved in: +``` + tcp::socket&& sock; + ... + wsproto::socket ws(std::move(sock)); +``` + +Or, the wrapper can be constructed with a non-owning reference. In +this case, the caller is responsible for managing the lifetime of the +underlying socket being wrapped: +``` + tcp::socket sock; + ... + wsproto::socket ws(sock); +``` + +The stream being wrapped can be accessed through the websocket's "next layer", +permitting callers to interact directly with its interface. +``` + ssl::context ctx(ssl::context::sslv23); + wsproto::socket> ws(ios, ctx); + ... + ws.next_layer().shutdown(); // ssl::stream shutdown +``` + +[important Initiating read and write operations on the next layer while +websocket operations are being performed can break invariants, and +result in undefined behavior. ] + +[endsect] + + + +[section:connecting Making connections] + +Connections are established by using the interfaces which already exist for +the wrapped stream. For example, making an outgoing connection: +``` + std::string const host = "mywebapp.com"; + io_service ios; + tcp::resolver r(ios); + wsproto::socket ws(ios); + connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"})); +``` + +Accepting an incoming connection: +``` +void do_accept(tcp::acceptor& acceptor) +{ + wsproto::socket ws(acceptor.get_io_service()); + acceptor.accept(ws.next_layer()); +} +``` + +[note Examples use synchronous interfaces for clarity of exposition. ] + +[endsect] + + + +[section:handshaking Handshaking] + +A WebSocket session begins when one side sends the HTTP Upgrade request +for websocket, and the other side sends an appropriate HTTP response +indicating that the request was accepted and that the connection has +been upgraded. The HTTP Upgrade request must include the Host HTTP field, +and the URI of the resource to request. `wsproto::socket::hanshake` is +used to send the request with the required host and resource strings. +``` + wsproto::socket ws(ios); + ... + ws.set_option(wsproto::keep_alive(true)); + ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices"); +``` + +The `wsproto::socket` automatically handles receiving and processing +the HTTP response to the handshake request. The call to handshake is +successful if a HTTP response is received with the 101 "Switching Protocols" +status code. On failure, an error is returned or an exception is thrown. +Depending on the keep alive setting, the socket may remain open for a +subsequent handshake attempt + +Performing a handshake for an incoming websocket upgrade request operates +similarly. If the handshake fails, an error is returned or exception thrown: +``` + wsproto::socket ws(ios); + ... + ws.accept(); +``` + +Servers that can handshake in multiple protocols may have already read data +on the connection, or might have already received an entire HTTP request +containing the upgrade request. Overloads of `accept` allow callers to +pass in this additional buffered handshake data. +``` +void do_accept(tcp::socket& sock) +{ + boost::asio::streambuf sb; + read_until(sock, sb, "\r\n\r\n"); + ... + wsproto::socket ws(sock); + ws.accept(sb.data()); + ... +} +``` + +Alternatively, the caller can pass an entire HTTP request if it was +obtained elsewhere: +``` +void do_accept(tcp::socket& sock) +{ + boost::asio::streambuf sb; + http::parsed_request request; + http::read(sock, request); + ... + wsproto::socket ws(sock); + ws.accept(request); + ... +} +``` + +[note Identifiers in the `http` namespace are part of Beast.HTTP. ] + +[endsect] + + + +[section:messages Messages] + +After the WebSocket handshake is accomplished, callers may send and receive +messages using the message oriented interface. This interface requires that +all of the buffers representing the message are known ahead of time: +``` +void echo(wsproto::socket& ws) +{ + streambuf sb; + wsproto::opcode::value op; + ws.read(sb); + + ws.set_option(wsproto::message_type(op)); + wsproto::write(ws, sb.data()); + sb.consume(sb.size()); +} +``` + +[important Calls to `wsproto::socket::set_option` must be made from the same +implicit or explicit strand as that used to perform other operations. ] + +[endsect] + + + +[section:frames Frames] + +Some use-cases make it impractical or impossible to buffer the entire +message ahead of time: + +* Streaming multimedia to an endpoint. +* Sending a message that does not fit in memory at once. +* Providing incremental results as they become available. + +For these cases, the frame oriented interface may be used. This +example reads and echoes a complete message using this interface: +``` +void echo(wsproto::socket& ws) +{ + beast::streambuf sb; + wsproto::frame_info fi; + for(;;) + { + ws.read_frame(fi, sb); + if(fi.fin) + break; + } + ws.set_option(wsproto::message_type(fi.op)); + beast::consuming_buffers cb(sb.data()); + for(;;) + { + using boost::asio::buffer_size; + std::size_t size = std::min(buffer_size(cb)); + if(size > 512) + { + ws.write_frame(false, beast::prepare_buffers(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +} +``` + +[endsect] + + + +[section:controlframes Control frames] + +During read operations, the implementation automatically reads and processes +WebSocket control frames such as ping, pong, and close. Pings are replied +to as soon as possible, pongs are noted. The receipt of a close frame +initiates the WebSocket close procedure, eventually resulting in the +error code `wsproto::error::closed` being delivered to the caller in +a subsequent read operation, assuming no other error takes place. + +To ensure timely delivery of control frames, large messages are broken up +into smaller sized frames. The implementation chooses the size and number +of the frames making up the message. The automatic fragment size option +gives callers control over the size of these frames: +``` + ... + ws.set_option(wsproto::auto_fragment_size(8192)); +``` + +The WebSocket protocol defines a procedure and control message for initiating +a close of the session. Handling of close initiated by the remote end of the +connection is performed automatically. To manually initiate a close, use +`wsproto::socket::close`: +``` + ws.close(); +``` + +[note To receive the `wsproto::error::closed` error, a read operation +is required. ] + +[endsect] + + + +[section:buffers Buffers] + +Because calls to read data may return a variable amount of bytes, the +interface to calls that read data require an object that meets the requirements +of `Streambuf`. This concept is modeled on `boost::asio::basic_streambuf`. + +The implementation does not perform queueing or buffering of messages. If +desired, these features should be provided by callers. The impact of this +design is that library users are in full control of the allocation strategy +used to store data and the back-pressure applied on the read and write side +of the underlying TCP/IP connection. + +[endsect] + + +[section:async Asynchronous interface] + +Asynchronous versions are available for all functions: +``` +wsproto::opcode op; +ws.async_read(op, sb, + [](boost::system::error_code const& ec) + { + ... + }); +``` + +Calls to asynchronous initiation functions support the extensible asynchronous +model developed by the Boost.Asio author, allowing for traditional completion +handlers, stackful or stackless coroutines, and even futures: +``` +void echo(wsproto::socket& ws, + boost::asio::yield_context yield) +{ + ws.async_read(sb, yield); + std::future fut = + ws.async_write, sb.data(), boost::use_future); + ... +} +``` + +[endsect] + + + +[section:io_service io_service] + +The creation and operation of the `boost::asio::io_service` associated with +the underlying stream is left to the callers, permitting any implementation +strategy including one that does not require threads for environments where +threads are unavailable. Beast.WSProto itself does not use or require threads. + +[endsect] + + + +[section:safety Thread Safety] + +Like a regular asio socket, a `wsproto::socket` is not thread safe. Callers +are responsible for synchronizing operations on the socket using an implicit +or explicit strand, as per the Asio documentation. The asynchronous interface +supports one active read and one active write simultaneously. Undefined +behavior results if two or more reads or two or more writes are attempted +concurrently. Caller initiated WebSocket ping, pong, and close operations +each count as an active write. + +The implementation uses composed asynchronous operations internally; a high +level read can cause both reads and writes to take place on the underlying +stream. This behavior is transparent to callers. + +[endsect] + + + +[endsect] + +[include quickref.xml] diff --git a/examples/Jamfile b/examples/Jamfile index 327a8182..10edb82b 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -17,3 +17,8 @@ exe http_server : ../beast/http/src/beast_http_nodejs_parser.cpp http_server.cpp ; + +exe wsproto_echo : + ../beast/http/src/beast_http_nodejs_parser.cpp + wsproto_echo.cpp + ; diff --git a/examples/wsproto_async_echo_peer.h b/examples/wsproto_async_echo_peer.h new file mode 100644 index 00000000..89e51cd8 --- /dev/null +++ b/examples/wsproto_async_echo_peer.h @@ -0,0 +1,267 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_ASYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Asynchronous WebSocket echo client/server +// +class async_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector thread_; + +public: + async_echo_peer(bool server, + endpoint_type const& ep, std::size_t threads) + : sock_(ios_) + , acceptor_(ios_) + { + if(server) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + else + { + Peer{std::move(sock_), ep}; + } + thread_.reserve(threads); + for(std::size_t i = 0; i < threads; ++i) + thread_.emplace_back( + [&]{ ios_.run(); }); + } + + ~async_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + for(auto& t : thread_) + t.join(); + } + +private: + class Peer + { + struct data + { + int state = 0; + boost::optional ep; + wsproto::socket ws; + wsproto::opcode op; + beast::streambuf sb; + int id; + + data(socket_type&& sock_) + : ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + + data(socket_type&& sock_, + endpoint_type const& ep_) + : ep(ep_) + , ws(std::move(sock_)) + , id([] + { + static int n = 0; + return ++n; + }()) + { + } + }; + + std::shared_ptr d_; + + public: + Peer(Peer&&) = default; + Peer(Peer const&) = default; + Peer& operator=(Peer&&) = delete; + Peer& operator=(Peer const&) = delete; + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "async_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "async_echo_server"); + } + }; + + template + explicit + Peer(socket_type&& sock, Args&&... args) + : d_(std::make_shared( + std::forward(sock), + std::forward(args)...)) + { + auto& d = *d_; + d.ws.set_option(decorate(identity{})); + d.ws.set_option(read_message_max(64 * 1024 * 1024)); + run(); + } + + void run() + { + auto& d = *d_; + if(! d.ep) + { + d.ws.async_accept(std::move(*this)); + } + else + { + d.state = 4; + d.ws.next_layer().async_connect( + *d.ep, std::move(*this)); + } + } + + void operator()(error_code ec) + { + auto& d = *d_; + switch(d_->state) + { + // did accept + case 0: + if(ec) + return fail(ec, "async_accept"); + + // start + case 1: + if(ec) + return fail(ec, "async_handshake"); + d.sb.consume(d.sb.size()); + // read message + d.state = 2; + d.ws.async_read(d.op, d.sb, std::move(*this)); + return; + + // got message + case 2: + if(ec == wsproto::error::closed) + return; + if(ec) + return fail(ec, "async_read"); + // write message + d.state = 1; + d.ws.set_option(wsproto::message_type(d.op)); + d.ws.async_write(d.sb.data(), std::move(*this)); + return; + + // connected + case 4: + if(ec) + return fail(ec, "async_connect"); + d.state = 1; + d.ws.async_handshake( + d.ep->address().to_string() + ":" + + std::to_string(d.ep->port()), + "/", std::move(*this)); + return; + } + } + + private: + void + fail(error_code ec, std::string what) + { + if(ec != wsproto::error::closed) + std::cerr << "#" << d_->id << " " << + what << ": " << ec.message() << std::endl; + } + }; + + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(! acceptor_.is_open()) + return; + maybe_throw(ec, "accept"); + socket_type sock(std::move(sock_)); + acceptor_.async_accept(sock_, + std::bind(&async_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + Peer{std::move(sock)}; + } +}; + +} // wsproto +} // beast + +#endif diff --git a/examples/wsproto_echo.cpp b/examples/wsproto_echo.cpp new file mode 100644 index 00000000..2aa9da0c --- /dev/null +++ b/examples/wsproto_echo.cpp @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "wsproto_async_echo_peer.h" +#include "wsproto_sync_echo_peer.h" +#include +#include +#include + +int main() +{ + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + + beast::wsproto::async_echo_peer s1(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6000 }, 4); + + beast::wsproto::sync_echo_peer s2(true, endpoint_type{ + address_type::from_string("127.0.0.1"), 6001 }); + + boost::asio::io_service ios; + boost::asio::signal_set signals( + ios, SIGINT, SIGTERM); + std::mutex m; + bool stop = false; + std::condition_variable cv; + signals.async_wait( + [&](boost::system::error_code const& ec, + int signal_number) + { + std::lock_guard lock(m); + stop = true; + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&]{ return stop; }); +} diff --git a/examples/wsproto_sync_echo_peer.h b/examples/wsproto_sync_echo_peer.h new file mode 100644 index 00000000..37aa3414 --- /dev/null +++ b/examples/wsproto_sync_echo_peer.h @@ -0,0 +1,179 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED +#define BEAST_WSPROTO_SYNC_ECHO_PEER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +// Synchronous WebSocket echo client/server +// +class sync_echo_peer +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + +private: + boost::asio::io_service ios_; + socket_type sock_; + boost::asio::ip::tcp::acceptor acceptor_; + std::thread thread_; + +public: + sync_echo_peer(bool server, endpoint_type ep) + : sock_(ios_) + , acceptor_(ios_) + { + error_code ec; + acceptor_.open(ep.protocol(), ec); + maybe_throw(ec, "open"); + acceptor_.bind(ep, ec); + maybe_throw(ec, "bind"); + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + maybe_throw(ec, "listen"); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + thread_ = std::thread{[&]{ ios_.run(); }}; + } + + ~sync_echo_peer() + { + error_code ec; + ios_.dispatch( + [&]{ acceptor_.close(ec); }); + thread_.join(); + } + +private: + static + void + fail(error_code ec, std::string what) + { + std::cerr << + what << ": " << ec.message() << std::endl; + } + + static + void + fail(int id, error_code ec, std::string what) + { + std::cerr << "#" << std::to_string(id) << " " << + what << ": " << ec.message() << std::endl; + } + + static + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + fail(ec, what); + throw ec; + } + } + + void + on_accept(error_code ec) + { + if(ec == boost::asio::error::operation_aborted) + return; + maybe_throw(ec, "accept"); + static int id_ = 0; + std::thread{ + [ + id = ++id_, + this, + sock = std::move(sock_), + work = boost::asio::io_service::work{ios_} + ]() mutable + { + do_peer(id, std::move(sock)); + }}.detach(); + acceptor_.async_accept(sock_, + std::bind(&sync_echo_peer::on_accept, this, + beast::asio::placeholders::error)); + } + + struct identity + { + template + void + operator()(http::message& req) + { + req.headers.replace("User-Agent", "sync_echo_client"); + } + + template + void + operator()(http::message& resp) + { + resp.headers.replace("Server", "sync_echo_server"); + } + }; + + void + do_peer(int id, socket_type&& sock) + { + wsproto::socket ws(std::move(sock)); + ws.set_option(decorate(identity{})); + ws.set_option(read_message_max(64 * 1024 * 1024)); + error_code ec; + ws.accept(ec); + if(ec) + { + fail(id, ec, "accept"); + return; + } + for(;;) + { + wsproto::opcode op; + beast::streambuf sb; + ws.read(op, sb, ec); + if(ec) + break; + ws.set_option(wsproto::message_type(op)); + ws.write(sb.data(), ec); + if(ec) + break; + } + if(ec && ec != wsproto::error::closed) + { + fail(id, ec, "read"); + } + } +}; + +} // wsproto +} // beast + +#endif diff --git a/test/asio/Jamfile b/test/Jamfile similarity index 85% rename from test/asio/Jamfile rename to test/Jamfile index 57ec7cec..e407b93a 100644 --- a/test/asio/Jamfile +++ b/test/Jamfile @@ -7,14 +7,14 @@ import os ; -path-constant main : ../../beast/unit_test/src/main.cpp ; +path-constant test_main : ../beast/unit_test/src/main.cpp ; unit-test all : append_buffers.cpp asio.cpp async_completion.cpp basic_streambuf.cpp - ../basic_headers.cpp + basic_headers.cpp bind_handler.cpp buffers_adapter.cpp buffers_debug.cpp @@ -26,5 +26,5 @@ unit-test all : streambuf.cpp streambuf_readstream.cpp type_check.cpp - $(main) + $(test_main) ; diff --git a/test/asio/append_buffers.cpp b/test/append_buffers.cpp similarity index 100% rename from test/asio/append_buffers.cpp rename to test/append_buffers.cpp diff --git a/test/asio/asio.cpp b/test/asio.cpp similarity index 100% rename from test/asio/asio.cpp rename to test/asio.cpp diff --git a/test/asio/async_completion.cpp b/test/async_completion.cpp similarity index 100% rename from test/asio/async_completion.cpp rename to test/async_completion.cpp diff --git a/test/asio/basic_streambuf.cpp b/test/basic_streambuf.cpp similarity index 100% rename from test/asio/basic_streambuf.cpp rename to test/basic_streambuf.cpp diff --git a/test/beast_wsproto_ws_test.cpp b/test/beast_wsproto_ws_test.cpp new file mode 100644 index 00000000..8bddcd57 --- /dev/null +++ b/test/beast_wsproto_ws_test.cpp @@ -0,0 +1,358 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include + +namespace beast { +namespace wsproto { + +class ws_test : public unit_test::suite +{ +public: + using error_code = boost::system::error_code; + using endpoint_type = boost::asio::ip::tcp::endpoint; + using address_type = boost::asio::ip::address; + using socket_type = boost::asio::ip::tcp::socket; + using yield_context = boost::asio::yield_context; + + endpoint_type ep_; + + //-------------------------------------------------------------------------- + + // opcodes for creating the test plans + + // concurrent read and write + struct case_1{}; + + // write a bad frame and shut down + struct case_2{}; + + //-------------------------------------------------------------------------- + + class coro_peer + { + error_code ec_; + boost::asio::io_service ios_; + boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; + socket ws_; + opcode::value op_; + beast::streambuf rb_; + beast::streambuf wb_; + yield_context* yield_; + int state_ = 0; + //unit_test::suite& test_; + + public: + coro_peer(coro_peer&&) = default; + coro_peer(coro_peer const&) = delete; + coro_peer& operator=(coro_peer&&) = delete; + coro_peer& operator=(coro_peer const&) = delete; + + template + coro_peer(bool server, endpoint_type ep, + unit_test::suite& test, Ops const&... ops) + : acceptor_(ios_) + , sock_(ios_) + , ws_(sock_) + //, test_(test) + { + if(server) + { + acceptor_.open(ep.protocol()); + acceptor_.bind(ep); + acceptor_.listen( + boost::asio::socket_base::max_connections); + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 10; + acceptor_.async_accept(sock_, (*yield_)[ec_]); + if(ec_) + return this->fail("accept"); + state_ = 20; + ws_.async_accept((*yield_)[ec_]); + if(ec_) + return this->fail("ws.accept"); + this->invoke(ops...); + state_ = -1; + }); + } + else + { + boost::asio::spawn(ios_, + [=](auto yield) + { + yield_ = &yield; + state_ = 30; + sock_.async_connect(ep, (*yield_)[ec_]); + if(ec_) + return this->fail("connect"); + state_ = 40; + ws_.async_handshake(ep.address().to_string() + + std::to_string(ep.port()), "/", (*yield_)[ec_]); + if(ec_) + return this->fail("handshake"); + this->invoke(ops...); + state_ = -1; + }); + } + } + + ~coro_peer() + { + } + + int + state() const + { + return state_; + } + + void + run_one() + { + ios_.run_one(); + } + + void + step_to(int to = 0) + { + while(state_ != to) + ios_.run_one(); + } + + private: + template + void fail(String const& s) + { + } + + void invoke_1(case_1) + { + async_read(ws_, op_, rb_, + [&](auto ec) + { + if(ec) + return this->fail(ec); + rb_.consume(rb_.size()); + }); + state_ = 100; + async_write(ws_, opcode::text, + boost::asio::null_buffers{}, (*yield_)[ec_]); + if(ec_) + return fail("write"); + } + + void invoke_1(case_2) + { + detail::frame_header fh; + fh.op = opcode::rsv5; // bad opcode + fh.fin = true; + fh.mask = true; + fh.rsv1 = false; + fh.rsv2 = false; + fh.rsv3 = false; + fh.len = 0; + fh.key = 0; + detail::write(wb_, fh); + state_ = 200; + boost::asio::async_write( + ws_.next_layer(), wb_.data(), + (*yield_)[ec_]); + if(ec_) + return fail("write"); + ws_.next_layer().shutdown( + socket_type::shutdown_both, ec_); + if(ec_) + return fail("shutdown"); + } + + inline + void + invoke() + { + } + + template + inline + void + invoke(Op op, Ops const&... ops) + { + invoke_1(op); + invoke(ops...); + } + }; + + void + testInvokable() + { + endpoint_type const ep{ + address_type::from_string( + "127.0.0.1"), 6000}; + coro_peer server(true, ep, *this, case_1{}); + coro_peer client(false, ep, *this, case_2{}); + server.step_to(10); // async_accept + client.step_to(30); // async_connect + server.step_to(20); // async_accept(ws) + client.step_to(40); // async_handshake + server.step_to(100); // case_1 + client.step_to(200); // case_2 + client.step_to(-1); + server.step_to(-1); + } + + //-------------------------------------------------------------------------- + + void + maybe_fail(error_code const& ec, std::string const& what) + { + expect(! ec, what + ": " + ec.message()); + } + + void + maybe_throw(error_code ec, std::string what) + { + if(ec) + { + maybe_fail(ec, what); + throw ec; + } + } + + template + static + std::string + buffers_to_string(Buffers const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(auto const& b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + for(auto i = s.size(); i-- > 0;) + if(s[i] == '\r') + s.replace(i, 1, "\\r"); + else if(s[i] == '\n') + s.replace(i, 1, "\\n\n"); + return s; + } + + int + makeRequest(endpoint_type ep, std::string const& s) + { + using boost::asio::buffer; + boost::asio::io_service ios; + boost::asio::ip::tcp::socket sock(ios); + sock.connect(ep); + write(sock, append_buffers( + buffer(s), buffer("\r\n"))); + + using namespace http; + parsed_response m; + streambuf sb; + read(sock, sb, m); + return m.status; + } + + void + expectStatus(endpoint_type ep, + int status, std::string const& s) + { + expect(makeRequest(ep, s) == status); + } + + void + testHandshake(endpoint_type ep) + { + expectStatus(ep, 400, "GET / HTTP/1.0\r\n"); + } + + void + syncEchoClient(endpoint_type ep) + { + using boost::asio::buffer; + error_code ec; + boost::asio::io_service ios; + wsproto::socket ws(ios); + ws.next_layer().connect(ep, ec); + maybe_fail(ec, "connect"); + ws.handshake(ep.address().to_string(), "/", ec); + maybe_fail(ec, "upgrade"); + std::string const s = "Hello, world!"; + ws.write(wsproto::opcode::text, true, buffer(s), ec); + maybe_fail(ec, "write"); + boost::asio::streambuf sb; + wsproto::opcode::value op; + read(ws, op, sb, ec); + maybe_fail(ec, "read"); + if(! ec) + expect(op == wsproto::opcode::text); + expect(buffers_to_string(sb.data()) == s); + sb.consume(sb.size()); + ws.close({}, ec); + maybe_fail(ec, "close"); + while(! ec) + { + read(ws, op, sb, ec); + if(! ec) + sb.consume(sb.size()); + } + if(ec != error::closed) + maybe_fail(ec, "teardown"); + } + + void + run() override + { + //testInvokable(); + +#if 0 + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6000}; + testcase("Echo Server"); + test::sync_echo_peer s(true, ep, *this); + testHandshake(ep); + syncEchoClient(ep); + } +#endif + { + endpoint_type ep{ + address_type::from_string("127.0.0.1"), 6001}; + testcase("Async Echo Server"); + test::async_echo_peer s(true, ep, *this); + //testHandshake(ep); + syncEchoClient(ep); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ws,asio,beast); + +} // wsproto +} // beast diff --git a/test/asio/bind_handler.cpp b/test/bind_handler.cpp similarity index 100% rename from test/asio/bind_handler.cpp rename to test/bind_handler.cpp diff --git a/test/asio/buffers_adapter.cpp b/test/buffers_adapter.cpp similarity index 100% rename from test/asio/buffers_adapter.cpp rename to test/buffers_adapter.cpp diff --git a/test/asio/buffers_debug.cpp b/test/buffers_debug.cpp similarity index 100% rename from test/asio/buffers_debug.cpp rename to test/buffers_debug.cpp diff --git a/test/asio/consuming_buffers.cpp b/test/consuming_buffers.cpp similarity index 100% rename from test/asio/consuming_buffers.cpp rename to test/consuming_buffers.cpp diff --git a/test/asio/handler_alloc.cpp b/test/handler_alloc.cpp similarity index 100% rename from test/asio/handler_alloc.cpp rename to test/handler_alloc.cpp diff --git a/test/asio/placeholders.cpp b/test/placeholders.cpp similarity index 100% rename from test/asio/placeholders.cpp rename to test/placeholders.cpp diff --git a/test/asio/prepare_buffers.cpp b/test/prepare_buffers.cpp similarity index 100% rename from test/asio/prepare_buffers.cpp rename to test/prepare_buffers.cpp diff --git a/test/ssl_error.cpp b/test/ssl_error.cpp new file mode 100644 index 00000000..d750c5de --- /dev/null +++ b/test/ssl_error.cpp @@ -0,0 +1,43 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace beast { + +class ssl_error_test : public unit_test::suite +{ +public: + void run() + { + { + boost::system::error_code ec = + boost::system::error_code (335544539, + boost::asio::error::get_ssl_category ()); + std::string const s = beast::error_message_with_ssl(ec); + expect(s == " (20,0,219) error:140000DB:SSL routines:SSL routines:short read"); + } + } +}; + +BEAST_DEFINE_TESTSUITE(ssl_error,asio,beast); + +} // beast diff --git a/test/asio/static_streambuf.cpp b/test/static_streambuf.cpp similarity index 100% rename from test/asio/static_streambuf.cpp rename to test/static_streambuf.cpp diff --git a/test/asio/streambuf.cpp b/test/streambuf.cpp similarity index 97% rename from test/asio/streambuf.cpp rename to test/streambuf.cpp index 3e5e9c62..24877194 100644 --- a/test/asio/streambuf.cpp +++ b/test/streambuf.cpp @@ -67,7 +67,7 @@ public: std::integral_constant; using propagate_on_container_swap = std::integral_constant; - + template struct rebind { @@ -122,7 +122,7 @@ public: ::operator delete(p); } - std::size_t + std::size_t id() const { return id_; @@ -274,11 +274,6 @@ public: sb_type sb3(sb, alloc_type{}); //expect(sb3.get_allocator().id() == 3); } - { - using alloc_type = - test_allocator; - using sb_type = basic_streambuf; - } } void run() override diff --git a/test/asio/streambuf_readstream.cpp b/test/streambuf_readstream.cpp similarity index 100% rename from test/asio/streambuf_readstream.cpp rename to test/streambuf_readstream.cpp diff --git a/test/asio/temp_buffer.cpp b/test/temp_buffer.cpp similarity index 100% rename from test/asio/temp_buffer.cpp rename to test/temp_buffer.cpp diff --git a/test/asio/type_check.cpp b/test/type_check.cpp similarity index 100% rename from test/asio/type_check.cpp rename to test/type_check.cpp