diff --git a/.travis.yml b/.travis.yml
index 0035abc4c2..236e8da718 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,21 +13,25 @@ env:
- VALGRIND_ROOT=$HOME/valgrind-install
- BOOST_ROOT=$HOME/boost_1_61_0
- BOOST_URL='http://sourceforge.net/projects/boost/files/boost/1.61.0/boost_1_61_0.tar.gz'
-packages: &gcc5_pkgs
- - gcc-5
- - g++-5
- - python-software-properties
- - libssl-dev
- - libffi-dev
- - libstdc++6
- - binutils-gold
- # Provides a backtrace if the unittests crash
- - gdb
- # Needed for installing valgrind
- - subversion
- - automake
- - autotools-dev
- - libc6-dbg
+
+addons:
+ apt:
+ sources: ['ubuntu-toolchain-r-test']
+ packages:
+ - gcc-5
+ - g++-5
+ - python-software-properties
+ - libssl-dev
+ - libffi-dev
+ - libstdc++6
+ - binutils-gold
+ # Provides a backtrace if the unittests crash
+ - gdb
+ # Needed for installing valgrind
+ - subversion
+ - automake
+ - autotools-dev
+ - libc6-dbg
matrix:
include:
@@ -39,10 +43,6 @@ matrix:
- ADDRESS_MODEL=64
- BUILD_SYSTEM=cmake
- PATH=$PWD/cmake/bin:$PATH
- addons: &ao_gcc5
- apt:
- sources: ['ubuntu-toolchain-r-test']
- packages: *gcc5_pkgs
# Clang/UndefinedBehaviourSanitizer
- compiler: clang
@@ -55,7 +55,6 @@ matrix:
- BUILD_SYSTEM=cmake
- PATH=$PWD/cmake/bin:$PATH
- PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH
- addons: *ao_gcc5
# Clang/AddressSanitizer
- compiler: clang
@@ -65,7 +64,6 @@ matrix:
- CLANG_VER=3.8
- ADDRESS_MODEL=64
- PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH
- addons: *ao_gcc5
cache:
directories:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 515988fbca..4aec8f03bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,33 @@
+1.0.0-b26
+
+* Tidy up warnings and tests
+
+--------------------------------------------------------------------------------
+
+1.0.0-b25
+
+* Fixes for WebSocket echo server
+* Fix 32-bit arm7 warnings
+* Remove unnecessary include
+* WebSocket server examples and test tidying
+* Fix deflate setup bug
+
+API Changes:
+
+* Better handler_ptr
+
+--------------------------------------------------------------------------------
+
+1.0.0-b24
+
+* bjam use clang on MACOSX
+* Simplify Travis package install specification
+* Add optional yield_to arguments
+* Make decorator copyable
+* Add WebSocket permessage-deflate extension support
+
+--------------------------------------------------------------------------------
+
1.0.0-b23
* Tune websocket echo server for performance
@@ -5,7 +35,7 @@
* Better logging in async echo server
* Add copy special members
* Fix message constructor and special members
-* Travis CI improvements
+* Travis CI improvements
--------------------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f760999a3..61a243c030 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,6 +7,7 @@ project (Beast)
set_property (GLOBAL PROPERTY USE_FOLDERS ON)
if (MSVC)
+ # /wd4244 /wd4127
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4244 /MP /W4 /wd4100 /bigobj /D _WIN32_WINNT=0x0601 /D _SCL_SECURE_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ob2 /Oi /Ot /GL /MT")
@@ -96,6 +97,29 @@ endfunction()
include_directories (extras)
include_directories (include)
+set(ZLIB_SOURCES
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffixed.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zlib.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.h
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/adler32.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/compress.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/crc32.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/deflate.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/infback.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inffast.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inflate.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/inftrees.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/trees.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/uncompr.c
+ ${PROJECT_SOURCE_DIR}/test/zlib/zlib-1.2.8/zutil.c
+)
+
file(GLOB_RECURSE BEAST_INCLUDES
${PROJECT_SOURCE_DIR}/include/beast/*.hpp
${PROJECT_SOURCE_DIR}/include/beast/*.ipp
diff --git a/Jamroot b/Jamroot
index 1f82785357..543305236a 100644
--- a/Jamroot
+++ b/Jamroot
@@ -45,6 +45,11 @@ else
lib crypto ;
}
+if [ os.name ] = MACOSX
+{
+ using clang : : ;
+}
+
variant coverage
:
debug
diff --git a/README.md b/README.md
index c2fdc0f607..ef9b317d7a 100644
--- a/README.md
+++ b/README.md
@@ -8,19 +8,15 @@
(https://img.shields.io/badge/documentation-master-brightgreen.svg)](http://vinniefalco.github.io/beast/) [![License]
(https://img.shields.io/badge/license-boost-brightgreen.svg)](LICENSE_1_0.txt)
-# HTTP and WebSocket implementations built on Boost.Asio
+# HTTP and WebSocket built on Boost.Asio in C++11
---
-## Beast at CppCon 2016
+## Appearances
-Presentation
-(slides: CppCon2016.pdf)
-
-
-
-
+| CppCast 2017 | CppCon 2016 |
+| ------------ | ----------- |
+|
|
|
---
diff --git a/doc/design.qbk b/doc/design.qbk
index 55f62cead1..50e086a6e5 100644
--- a/doc/design.qbk
+++ b/doc/design.qbk
@@ -194,15 +194,10 @@ start. Other design goals:
[[
What about message compression?
][
- The author is currently porting ZLib 1.2.8 to modern, header-only C++11
- that does not use macros or try to support ancient architectures. This
- deflate implementation will be available as its own individually
- usable interface, and also will be used to power Beast WebSocket's
- permessage-deflate implementation, due Q1 of 2017.
-
- However, Beast currently has sufficient functionality that users can
- begin taking advantage of the WebSocket protocol using this library
- immediately.
+ Beast WebSocket supports the permessage-deflate extension described in
+ [@https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-00 draft-ietf-hybi-permessage-compression-00].
+ The library comes with a header-only, C++11 port of ZLib's "deflate" codec
+ used in the implementation of the permessage-deflate extension.
]]
[[
diff --git a/doc/examples.qbk b/doc/examples.qbk
index fd86953671..3ad72b09a4 100644
--- a/doc/examples.qbk
+++ b/doc/examples.qbk
@@ -85,6 +85,15 @@ int main()
}
```
+[heading WebSocket Echo Server]
+
+This example demonstrates both synchronous and asynchronous
+WebSocket server implementations.
+
+* [@examples/websocket_async_echo_server.hpp]
+* [@examples/websocket_ssync_echo_server.hpp]
+* [@examples/websocket_echo.cpp]
+
[heading Secure WebSocket]
Establish a WebSocket connection over an encrypted TLS connection,
diff --git a/doc/overview.qbk b/doc/overview.qbk
index c0639a7c8a..f0a3346172 100644
--- a/doc/overview.qbk
+++ b/doc/overview.qbk
@@ -59,7 +59,7 @@ your particular build system.
[heading Motivation]
-Beast is built on Boost.Asio A proposal to add networking functionality to the
+Beast is built on Boost.Asio. A proposal to add networking functionality to the
C++ standard library, based on Boost.Asio, is under consideration by the
committee and on track for standardization. Since the final approved networking
interface for the C++ standard library will likely closely resemble the current
diff --git a/doc/quickref.xml b/doc/quickref.xml
index 3fe63b3efc..238a49fedc 100644
--- a/doc/quickref.xml
+++ b/doc/quickref.xml
@@ -128,6 +128,7 @@
decorate
keep_alive
message_type
+ permessage_deflate
pong_callback
read_buffer_size
read_message_max
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index d4815e907f..6d9e8edb36 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -41,6 +41,17 @@ if (NOT WIN32)
target_link_libraries(http-example ${Boost_LIBRARIES} Threads::Threads)
endif()
+add_executable (websocket-echo
+ ${BEAST_INCLUDES}
+ websocket_async_echo_server.hpp
+ websocket_sync_echo_server.hpp
+ websocket_echo.cpp
+)
+
+if (NOT WIN32)
+ target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads)
+endif()
+
add_executable (websocket-example
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
diff --git a/examples/Jamfile.v2 b/examples/Jamfile.v2
index 2dd0427bed..0bfbb59472 100644
--- a/examples/Jamfile.v2
+++ b/examples/Jamfile.v2
@@ -18,6 +18,10 @@ exe http-example :
http_example.cpp
;
+exe websocket-echo :
+ websocket_echo.cpp
+ ;
+
exe websocket-example :
websocket_example.cpp
;
diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp
index 2c1df4b435..2e248a3904 100644
--- a/examples/http_async_server.hpp
+++ b/examples/http_async_server.hpp
@@ -114,9 +114,8 @@ private:
template
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), s,
- std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
(*this)(error_code{}, false);
}
diff --git a/examples/websocket_async_echo_server.hpp b/examples/websocket_async_echo_server.hpp
new file mode 100644
index 0000000000..3b0b9ae11c
--- /dev/null
+++ b/examples/websocket_async_echo_server.hpp
@@ -0,0 +1,375 @@
+//
+// 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)
+//
+
+#ifndef WEBSOCKET_ASYNC_ECHO_SERVER_HPP
+#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace websocket {
+
+/** Asynchronous WebSocket echo client/server
+*/
+class async_echo_server
+{
+public:
+ using error_code = beast::error_code;
+ using address_type = boost::asio::ip::address;
+ using socket_type = boost::asio::ip::tcp::socket;
+ using endpoint_type = boost::asio::ip::tcp::endpoint;
+
+private:
+ struct identity
+ {
+ template
+ void
+ operator()(beast::http::message<
+ true, Body, Fields>& req) const
+ {
+ req.fields.replace("User-Agent", "async_echo_client");
+ }
+
+ template
+ void
+ operator()(beast::http::message<
+ false, Body, Fields>& resp) const
+ {
+ resp.fields.replace("Server", "async_echo_server");
+ }
+ };
+
+ /** A container of type-erased option setters.
+ */
+ template
+ class options_set
+ {
+ // workaround for std::function bug in msvc
+ struct callable
+ {
+ virtual ~callable() = default;
+ virtual void operator()(
+ beast::websocket::stream&) = 0;
+ };
+
+ template
+ class callable_impl : public callable
+ {
+ T t_;
+
+ public:
+ template
+ callable_impl(U&& u)
+ : t_(std::forward(u))
+ {
+ }
+
+ void
+ operator()(beast::websocket::stream& ws)
+ {
+ t_(ws);
+ }
+ };
+
+ template
+ class lambda
+ {
+ Opt opt_;
+
+ public:
+ lambda(lambda&&) = default;
+ lambda(lambda const&) = default;
+
+ lambda(Opt const& opt)
+ : opt_(opt)
+ {
+ }
+
+ void
+ operator()(beast::websocket::stream& ws) const
+ {
+ ws.set_option(opt_);
+ }
+ };
+
+ std::unordered_map> list_;
+
+ public:
+ template
+ void
+ set_option(Opt const& opt)
+ {
+ std::unique_ptr p;
+ p.reset(new callable_impl>{opt});
+ list_[std::type_index{
+ typeid(Opt)}] = std::move(p);
+ }
+
+ void
+ set_options(beast::websocket::stream& ws)
+ {
+ for(auto const& op : list_)
+ (*op.second)(ws);
+ }
+ };
+
+ std::ostream* log_;
+ boost::asio::io_service ios_;
+ socket_type sock_;
+ endpoint_type ep_;
+ boost::asio::ip::tcp::acceptor acceptor_;
+ std::vector thread_;
+ boost::optional work_;
+ options_set opts_;
+
+public:
+ async_echo_server(async_echo_server const&) = delete;
+ async_echo_server& operator=(async_echo_server const&) = delete;
+
+ /** Constructor.
+
+ @param log A pointer to a stream to log to, or `nullptr`
+ to disable logging.
+
+ @param threads The number of threads in the io_service.
+ */
+ async_echo_server(std::ostream* log,
+ std::size_t threads)
+ : log_(log)
+ , sock_(ios_)
+ , acceptor_(ios_)
+ , work_(ios_)
+ {
+ opts_.set_option(
+ beast::websocket::decorate(identity{}));
+ thread_.reserve(threads);
+ for(std::size_t i = 0; i < threads; ++i)
+ thread_.emplace_back(
+ [&]{ ios_.run(); });
+ }
+
+ /** Destructor.
+ */
+ ~async_echo_server()
+ {
+ work_ = boost::none;
+ error_code ec;
+ ios_.dispatch(
+ [&]{ acceptor_.close(ec); });
+ for(auto& t : thread_)
+ t.join();
+ }
+
+ /** Return the listening endpoint.
+ */
+ endpoint_type
+ local_endpoint() const
+ {
+ return acceptor_.local_endpoint();
+ }
+
+ /** Set a websocket option.
+
+ The option will be applied to all new connections.
+
+ @param opt The option to apply.
+ */
+ template
+ void
+ set_option(Opt const& opt)
+ {
+ opts_.set_option(opt);
+ }
+
+ /** Open a listening port.
+
+ @param ep The address and port to bind to.
+
+ @param ec Set to the error, if any occurred.
+ */
+ void
+ open(endpoint_type const& ep, error_code& ec)
+ {
+ acceptor_.open(ep.protocol(), ec);
+ if(ec)
+ return fail("open", ec);
+ acceptor_.set_option(
+ boost::asio::socket_base::reuse_address{true});
+ acceptor_.bind(ep, ec);
+ if(ec)
+ return fail("bind", ec);
+ acceptor_.listen(
+ boost::asio::socket_base::max_connections, ec);
+ if(ec)
+ return fail("listen", ec);
+ acceptor_.async_accept(sock_, ep_,
+ std::bind(&async_echo_server::on_accept, this,
+ beast::asio::placeholders::error));
+ }
+
+private:
+ class peer
+ {
+ struct data
+ {
+ async_echo_server& server;
+ endpoint_type ep;
+ int state = 0;
+ beast::websocket::stream ws;
+ boost::asio::io_service::strand strand;
+ beast::websocket::opcode op;
+ beast::streambuf db;
+ std::size_t id;
+
+ data(async_echo_server& server_,
+ endpoint_type const& ep_,
+ socket_type&& sock_)
+ : server(server_)
+ , ep(ep_)
+ , ws(std::move(sock_))
+ , strand(ws.get_io_service())
+ , id([]
+ {
+ static std::atomic n{0};
+ return ++n;
+ }())
+ {
+ }
+ };
+
+ // VFALCO This could be unique_ptr in [Net.TS]
+ std::shared_ptr d_;
+
+ public:
+ peer(peer&&) = default;
+ peer(peer const&) = default;
+ peer& operator=(peer&&) = delete;
+ peer& operator=(peer const&) = delete;
+
+ template
+ explicit
+ peer(async_echo_server& server,
+ endpoint_type const& ep, socket_type&& sock,
+ Args&&... args)
+ : d_(std::make_shared(server, ep,
+ std::forward(sock),
+ std::forward(args)...))
+ {
+ auto& d = *d_;
+ d.server.opts_.set_options(d.ws);
+ run();
+ }
+
+ void run()
+ {
+ auto& d = *d_;
+ d.ws.async_accept(std::move(*this));
+ }
+
+ void operator()(error_code ec, std::size_t)
+ {
+ (*this)(ec);
+ }
+
+ void operator()(error_code ec)
+ {
+ using boost::asio::buffer;
+ using boost::asio::buffer_copy;
+ auto& d = *d_;
+ switch(d.state)
+ {
+ // did accept
+ case 0:
+ if(ec)
+ return fail("async_accept", ec);
+
+ // start
+ case 1:
+ if(ec)
+ return fail("async_handshake", ec);
+ d.db.consume(d.db.size());
+ // read message
+ d.state = 2;
+ d.ws.async_read(d.op, d.db,
+ d.strand.wrap(std::move(*this)));
+ return;
+
+ // got message
+ case 2:
+ if(ec == beast::websocket::error::closed)
+ return;
+ if(ec)
+ return fail("async_read", ec);
+ // write message
+ d.state = 1;
+ d.ws.set_option(
+ beast::websocket::message_type(d.op));
+ d.ws.async_write(d.db.data(),
+ d.strand.wrap(std::move(*this)));
+ return;
+ }
+ }
+
+ private:
+ void
+ fail(std::string what, error_code ec)
+ {
+ auto& d = *d_;
+ if(d.server.log_)
+ if(ec != beast::websocket::error::closed)
+ d.server.fail("[#" + std::to_string(d.id) +
+ " " + boost::lexical_cast(d.ep) +
+ "] " + what, ec);
+ }
+ };
+
+ void
+ fail(std::string what, error_code ec)
+ {
+ if(log_)
+ {
+ static std::mutex m;
+ std::lock_guard lock{m};
+ (*log_) << what << ": " <<
+ ec.message() << std::endl;
+ }
+ }
+
+ void
+ on_accept(error_code ec)
+ {
+ if(! acceptor_.is_open())
+ return;
+ if(ec == boost::asio::error::operation_aborted)
+ return;
+ if(ec)
+ fail("accept", ec);
+ peer{*this, ep_, std::move(sock_)};
+ acceptor_.async_accept(sock_, ep_,
+ std::bind(&async_echo_server::on_accept, this,
+ beast::asio::placeholders::error));
+ }
+};
+
+} // websocket
+
+#endif
diff --git a/examples/websocket_echo.cpp b/examples/websocket_echo.cpp
new file mode 100644
index 0000000000..bf7ac9cb66
--- /dev/null
+++ b/examples/websocket_echo.cpp
@@ -0,0 +1,56 @@
+//
+// 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)
+//
+
+#include "websocket_async_echo_server.hpp"
+#include "websocket_sync_echo_server.hpp"
+#include
+#include
+#include
+
+/// Block until SIGINT or SIGTERM is received.
+void
+sig_wait()
+{
+ boost::asio::io_service ios;
+ boost::asio::signal_set signals(
+ ios, SIGINT, SIGTERM);
+ signals.async_wait(
+ [&](boost::system::error_code const&, int)
+ {
+ });
+ ios.run();
+}
+
+int main()
+{
+ using namespace beast::websocket;
+ using endpoint_type = boost::asio::ip::tcp::endpoint;
+ using address_type = boost::asio::ip::address;
+
+ beast::error_code ec;
+
+ permessage_deflate pmd;
+ pmd.client_enable = true;
+ pmd.server_enable = true;
+ pmd.compLevel = 3;
+
+ websocket::async_echo_server s1{&std::cout, 1};
+ s1.set_option(read_message_max{64 * 1024 * 1024});
+ s1.set_option(auto_fragment{false});
+ s1.set_option(pmd);
+ s1.open(endpoint_type{
+ address_type::from_string("127.0.0.1"), 6000 }, ec);
+
+ websocket::sync_echo_server s2{&std::cout};
+ s2.set_option(read_message_max{64 * 1024 * 1024});
+ s2.set_option(auto_fragment{false});
+ s2.set_option(pmd);
+ s2.open(endpoint_type{
+ address_type::from_string("127.0.0.1"), 6001 }, ec);
+
+ sig_wait();
+}
diff --git a/examples/websocket_sync_echo_server.hpp b/examples/websocket_sync_echo_server.hpp
new file mode 100644
index 0000000000..a4b0ba3a04
--- /dev/null
+++ b/examples/websocket_sync_echo_server.hpp
@@ -0,0 +1,326 @@
+//
+// 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)
+//
+
+#ifndef WEBSOCKET_SYNC_ECHO_SERVER_HPP
+#define WEBSOCKET_SYNC_ECHO_SERVER_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace websocket {
+
+/** Synchronous WebSocket echo client/server
+*/
+class sync_echo_server
+{
+public:
+ using error_code = beast::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:
+ struct identity
+ {
+ template
+ void
+ operator()(beast::http::message<
+ true, Body, Fields>& req) const
+ {
+ req.fields.replace("User-Agent", "sync_echo_client");
+ }
+
+ template
+ void
+ operator()(beast::http::message<
+ false, Body, Fields>& resp) const
+ {
+ resp.fields.replace("Server", "sync_echo_server");
+ }
+ };
+
+ /** A container of type-erased option setters.
+ */
+ template
+ class options_set
+ {
+ // workaround for std::function bug in msvc
+ struct callable
+ {
+ virtual ~callable() = default;
+ virtual void operator()(
+ beast::websocket::stream&) = 0;
+ };
+
+ template
+ class callable_impl : public callable
+ {
+ T t_;
+
+ public:
+ template
+ callable_impl(U&& u)
+ : t_(std::forward(u))
+ {
+ }
+
+ void
+ operator()(beast::websocket::stream& ws)
+ {
+ t_(ws);
+ }
+ };
+
+ template
+ class lambda
+ {
+ Opt opt_;
+
+ public:
+ lambda(lambda&&) = default;
+ lambda(lambda const&) = default;
+
+ lambda(Opt const& opt)
+ : opt_(opt)
+ {
+ }
+
+ void
+ operator()(beast::websocket::stream& ws) const
+ {
+ ws.set_option(opt_);
+ }
+ };
+
+ std::unordered_map> list_;
+
+ public:
+ template
+ void
+ set_option(Opt const& opt)
+ {
+ std::unique_ptr p;
+ p.reset(new callable_impl>{opt});
+ list_[std::type_index{
+ typeid(Opt)}] = std::move(p);
+ }
+
+ void
+ set_options(beast::websocket::stream& ws)
+ {
+ for(auto const& op : list_)
+ (*op.second)(ws);
+ }
+ };
+
+ std::ostream* log_;
+ boost::asio::io_service ios_;
+ socket_type sock_;
+ endpoint_type ep_;
+ boost::asio::ip::tcp::acceptor acceptor_;
+ std::thread thread_;
+ options_set opts_;
+
+public:
+ /** Constructor.
+
+ @param log A pointer to a stream to log to, or `nullptr`
+ to disable logging.
+ */
+ sync_echo_server(std::ostream* log)
+ : log_(log)
+ , sock_(ios_)
+ , acceptor_(ios_)
+ {
+ opts_.set_option(
+ beast::websocket::decorate(identity{}));
+ }
+
+ /** Destructor.
+ */
+ ~sync_echo_server()
+ {
+ if(thread_.joinable())
+ {
+ error_code ec;
+ ios_.dispatch(
+ [&]{ acceptor_.close(ec); });
+ thread_.join();
+ }
+ }
+
+ /** Return the listening endpoint.
+ */
+ endpoint_type
+ local_endpoint() const
+ {
+ return acceptor_.local_endpoint();
+ }
+
+ /** Set a websocket option.
+
+ The option will be applied to all new connections.
+
+ @param opt The option to apply.
+ */
+ template
+ void
+ set_option(Opt const& opt)
+ {
+ opts_.set_option(opt);
+ }
+
+ /** Open a listening port.
+
+ @param ep The address and port to bind to.
+
+ @param ec Set to the error, if any occurred.
+ */
+ void
+ open(endpoint_type const& ep, error_code& ec)
+ {
+ acceptor_.open(ep.protocol(), ec);
+ if(ec)
+ return fail("open", ec);
+ acceptor_.set_option(
+ boost::asio::socket_base::reuse_address{true});
+ acceptor_.bind(ep, ec);
+ if(ec)
+ return fail("bind", ec);
+ acceptor_.listen(
+ boost::asio::socket_base::max_connections, ec);
+ if(ec)
+ return fail("listen", ec);
+ acceptor_.async_accept(sock_, ep_,
+ std::bind(&sync_echo_server::on_accept, this,
+ beast::asio::placeholders::error));
+ thread_ = std::thread{[&]{ ios_.run(); }};
+ }
+
+private:
+ void
+ fail(std::string what, error_code ec)
+ {
+ if(log_)
+ {
+ static std::mutex m;
+ std::lock_guard lock{m};
+ (*log_) << what << ": " <<
+ ec.message() << std::endl;
+ }
+ }
+
+ void
+ fail(std::string what, error_code ec,
+ int id, endpoint_type const& ep)
+ {
+ if(log_)
+ if(ec != beast::websocket::error::closed)
+ fail("[#" + std::to_string(id) + " " +
+ boost::lexical_cast(ep) +
+ "] " + what, ec);
+ }
+
+ void
+ on_accept(error_code ec)
+ {
+ if(ec == boost::asio::error::operation_aborted)
+ return;
+ if(ec)
+ return fail("accept", ec);
+ struct lambda
+ {
+ std::size_t id;
+ endpoint_type ep;
+ sync_echo_server& self;
+ boost::asio::io_service::work work;
+ // Must be destroyed before work otherwise the
+ // io_service could be destroyed before the socket.
+ socket_type sock;
+
+ lambda(sync_echo_server& self_,
+ endpoint_type const& ep_,
+ socket_type&& sock_)
+ : id([]
+ {
+ static std::atomic n{0};
+ return ++n;
+ }())
+ , ep(ep_)
+ , self(self_)
+ , work(sock_.get_io_service())
+ , sock(std::move(sock_))
+ {
+ }
+
+ void operator()()
+ {
+ self.do_peer(id, ep, std::move(sock));
+ }
+ };
+ std::thread{lambda{*this, ep_, std::move(sock_)}}.detach();
+ acceptor_.async_accept(sock_, ep_,
+ std::bind(&sync_echo_server::on_accept, this,
+ beast::asio::placeholders::error));
+ }
+
+ void
+ do_peer(std::size_t id,
+ endpoint_type const& ep, socket_type&& sock)
+ {
+ using boost::asio::buffer;
+ using boost::asio::buffer_copy;
+ beast::websocket::stream<
+ socket_type> ws{std::move(sock)};
+ opts_.set_options(ws);
+ error_code ec;
+ ws.accept(ec);
+ if(ec)
+ {
+ fail("accept", ec, id, ep);
+ return;
+ }
+ for(;;)
+ {
+ beast::websocket::opcode op;
+ beast::streambuf sb;
+ ws.read(op, sb, ec);
+ if(ec)
+ {
+ auto const s = ec.message();
+ break;
+ }
+ ws.set_option(beast::websocket::message_type{op});
+ ws.write(sb.data(), ec);
+ if(ec)
+ break;
+ }
+ if(ec && ec != beast::websocket::error::closed)
+ {
+ fail("read", ec, id, ep);
+ }
+ }
+};
+
+} // websocket
+
+#endif
diff --git a/extras/beast/test/sig_wait.hpp b/extras/beast/test/sig_wait.hpp
index 5abcdec5d3..a1071bdf2d 100644
--- a/extras/beast/test/sig_wait.hpp
+++ b/extras/beast/test/sig_wait.hpp
@@ -9,8 +9,6 @@
#define BEAST_TEST_SIG_WAIT_HPP
#include
-#include
-#include
namespace beast {
namespace test {
diff --git a/extras/beast/test/yield_to.hpp b/extras/beast/test/yield_to.hpp
index 5c4d3c01f9..fecd8d4bfd 100644
--- a/extras/beast/test/yield_to.hpp
+++ b/extras/beast/test/yield_to.hpp
@@ -21,9 +21,9 @@ namespace test {
/** Mix-in to support tests using asio coroutines.
- Derive from this class and use yield_to to launch test functions
- inside coroutines. This is handy for testing asynchronous asio
- code.
+ Derive from this class and use yield_to to launch test
+ functions inside coroutines. This is handy for testing
+ asynchronous asio code.
*/
class enable_yield_to
{
@@ -72,12 +72,32 @@ public:
Function will be called with this signature:
@code
- void f(yield_context);
+ void f(args..., yield_context);
@endcode
+
+ @param f The Callable object to invoke.
+
+ @param args Optional arguments forwarded to the callable object.
*/
- template
+#if GENERATING_DOCS
+ template
void
- yield_to(Function&& f);
+ yield_to(F&& f, Args&&... args);
+#else
+ template
+ void
+ yield_to(F&& f);
+
+ template
+ void
+ yield_to(Function&& f, Arg&& arg, Args&&... args)
+ {
+ yield_to(std::bind(f,
+ std::forward(arg),
+ std::forward(args)...,
+ std::placeholders::_1));
+ }
+#endif
};
template
diff --git a/include/beast/core/detail/buffer_cat.hpp b/include/beast/core/detail/buffer_cat.hpp
index b94d996566..e414b7ed49 100644
--- a/include/beast/core/detail/buffer_cat.hpp
+++ b/include/beast/core/detail/buffer_cat.hpp
@@ -83,7 +83,8 @@ class buffer_cat_helper::const_iterator
iter()
{
return *reinterpret_cast<
- iter_t*>(buf_.data());
+ iter_t*>(static_cast(
+ buf_.data()));
}
template
@@ -91,7 +92,8 @@ class buffer_cat_helper::const_iterator
iter() const
{
return *reinterpret_cast<
- iter_t const*>(buf_.data());
+ iter_t const*>(static_cast<
+ void const*>(buf_.data()));
}
public:
diff --git a/include/beast/core/handler_ptr.hpp b/include/beast/core/handler_ptr.hpp
index 175be8bbd4..45285f48df 100644
--- a/include/beast/core/handler_ptr.hpp
+++ b/include/beast/core/handler_ptr.hpp
@@ -11,23 +11,25 @@
#include
#include
#include
+#include
#include
namespace beast {
-/** A smart pointer container.
+/** A smart pointer container with associated completion handler.
This is a smart pointer that retains shared ownership of an
object through a pointer. Memory is managed using the allocation
and deallocation functions associated with a completion handler,
- which is also stored in the object. The object is destroyed and
- its memory deallocated when one of the following happens:
+ which is also stored in the object. The managed object is
+ destroyed and its memory deallocated when one of the following
+ happens:
@li The function @ref invoke is called.
-
- @li The function @ref release_handler is called
-
- @li The last remaining container owning the object is destroyed
+
+ @li The function @ref release_handler is called.
+
+ @li The last remaining container owning the object is destroyed.
Objects of this type are used in the implementation of
composed operations. Typically the composed operation's shared
@@ -38,6 +40,10 @@ namespace beast {
@note The reference count is stored using a 16 bit unsigned
integer. Making more than 2^16 copies of one object results
in undefined behavior.
+
+ @tparam T The type of the owned object.
+
+ @tparam Handler The type of the completion handler.
*/
template
class handler_ptr
@@ -60,10 +66,10 @@ class handler_ptr
P* p_;
- template
- handler_ptr(int, DeducedHandler&& handler, Args&&... args);
-
public:
+ /// The type of element this object stores
+ using element_type = T;
+
/// The type of handler this object stores
using handler_type = Handler;
@@ -88,6 +94,46 @@ public:
/// Copy constructor
handler_ptr(handler_ptr const& other);
+ /** Construct a new @ref handler_ptr
+
+ This creates a new @ref handler_ptr with an owned object
+ of type `T`. The allocator associated with the handler will
+ be used to allocate memory for the owned object. The constructor
+ for the owned object will be called thusly:
+
+ @code
+ T(handler, std::forward(args)...)
+ @endcode
+
+ @param handler The handler to associate with the owned
+ object. The argument will be moved.
+
+ @param args Optional arguments forwarded to
+ the owned object's constructor.
+ */
+ template
+ handler_ptr(Handler&& handler, Args&&... args);
+
+ /** Construct a new @ref handler_ptr
+
+ This creates a new @ref handler_ptr with an owned object
+ of type `T`. The allocator associated with the handler will
+ be used to allocate memory for the owned object. The constructor
+ for the owned object will be called thusly:
+
+ @code
+ T(handler, std::forward(args)...)
+ @endcode
+
+ @param handler The handler to associate with the owned
+ object. The argument will be copied.
+
+ @param args Optional arguments forwarded to
+ the owned object's constructor.
+ */
+ template
+ handler_ptr(Handler const& handler, Args&&... args);
+
/// Returns a reference to the handler
handler_type&
handler() const
@@ -95,25 +141,36 @@ public:
return p_->handler;
}
- /// Returns a pointer to the owned object
+ /// Returns `true` if `*this` owns an object.
+ explicit
+ operator bool() const
+ {
+ return p_ && p_->t;
+ }
+
+ /** Returns a pointer to the owned object.
+
+ If `*this` owns an object, a pointer to the
+ object is returned, else `nullptr` is returned.
+ */
T*
get() const
{
- return p_->t;
+ return p_ ? p_->t : nullptr;
}
/// Return a reference to the owned object.
T&
operator*() const
{
- return *get();
+ return *p_->t;
}
/// Return a pointer to the owned object.
T*
operator->() const
{
- return get();
+ return p_->t;
}
/** Release ownership of the handler
@@ -137,33 +194,6 @@ public:
template
void
invoke(Args&&... args);
-
- // VFALCO The free function interface works around
- // a horrible Visual Studio 15 Update 3 bug
-
- /** Construct a new `handler_ptr`.
-
- @param handler The handler. The allocator associated with
- the handler will be used to allocate memory for the owned
- object. This argument will be forwarded to the owned object's
- constructor.
-
- @param args Optional arguments forwarded to
- the owned object's constructor.
- */
- /** @{ */
- template
- friend
- handler_ptr
- make_handler_ptr(
- CompletionHandler&& handler, Args&&... args);
-
- template
- friend
- handler_ptr
- make_handler_ptr(
- CompletionHandler const& handler, Args&&... args);
- /** @} */
};
} // beast
diff --git a/include/beast/core/impl/basic_streambuf.ipp b/include/beast/core/impl/basic_streambuf.ipp
index a3fdcef706..710a5a1a7a 100644
--- a/include/beast/core/impl/basic_streambuf.ipp
+++ b/include/beast/core/impl/basic_streambuf.ipp
@@ -598,9 +598,9 @@ basic_streambuf::prepare(size_type n) ->
while(n > 0)
{
auto const size = std::max(alloc_size_, n);
- auto& e = *reinterpret_cast(
- alloc_traits::allocate(this->member(),
- sizeof(element) + size));
+ auto& e = *reinterpret_cast(static_cast<
+ void*>(alloc_traits::allocate(this->member(),
+ sizeof(element) + size)));
alloc_traits::construct(this->member(), &e, size);
list_.push_back(e);
if(out_ == list_.end())
diff --git a/include/beast/core/impl/dynabuf_readstream.ipp b/include/beast/core/impl/dynabuf_readstream.ipp
index bcd7a0a517..3981c771da 100644
--- a/include/beast/core/impl/dynabuf_readstream.ipp
+++ b/include/beast/core/impl/dynabuf_readstream.ipp
@@ -45,9 +45,8 @@ public:
template
read_some_op(DeducedHandler&& h,
dynabuf_readstream& srs, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), srs,
- std::forward(args)...))
+ : d_(std::forward(h),
+ srs, std::forward(args)...)
{
(*this)(error_code{}, 0);
}
diff --git a/include/beast/core/impl/handler_ptr.ipp b/include/beast/core/impl/handler_ptr.ipp
index 0f57a24d32..eb29e99843 100644
--- a/include/beast/core/impl/handler_ptr.ipp
+++ b/include/beast/core/impl/handler_ptr.ipp
@@ -39,15 +39,6 @@ P(DeducedHandler&& h, Args&&... args)
}
}
-template
-template
-handler_ptr::
-handler_ptr(int, DeducedHandler&& handler, Args&&... args)
- : p_(new P(std::forward(handler),
- std::forward(args)...))
-{
-}
-
template
handler_ptr::
~handler_ptr()
@@ -82,6 +73,27 @@ handler_ptr(handler_ptr const& other)
++p_->n;
}
+template
+template
+handler_ptr::
+handler_ptr(Handler&& handler, Args&&... args)
+ : p_(new P{std::move(handler),
+ std::forward(args)...})
+{
+ static_assert(! std::is_array::value,
+ "T must not be an array type");
+}
+
+template
+template
+handler_ptr::
+handler_ptr(Handler const& handler, Args&&... args)
+ : p_(new P{handler, std::forward(args)...})
+{
+ static_assert(! std::is_array::value,
+ "T must not be an array type");
+}
+
template
auto
handler_ptr::
@@ -112,27 +124,6 @@ invoke(Args&&... args)
p_->handler(std::forward(args)...);
}
-template<
- class T, class CompletionHandler, class... Args>
-handler_ptr
-make_handler_ptr(
- CompletionHandler&& handler, Args&&... args)
-{
- return handler_ptr{0,
- std::move(handler),
- std::forward(args)...};
-}
-
-template<
- class T, class CompletionHandler, class... Args>
-handler_ptr
-make_handler_ptr(
- CompletionHandler const& handler, Args&&... args)
-{
- return handler_ptr{0,
- handler, std::forward(args)...};
-}
-
} // beast
#endif
diff --git a/include/beast/http/impl/parse.ipp b/include/beast/http/impl/parse.ipp
index 913b41b165..c2ddae8ba8 100644
--- a/include/beast/http/impl/parse.ipp
+++ b/include/beast/http/impl/parse.ipp
@@ -53,9 +53,8 @@ public:
template
parse_op(DeducedHandler&& h, Stream& s, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), s,
- std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
(*this)(error_code{}, 0, false);
}
diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp
index 3f3961ddee..e009170f3b 100644
--- a/include/beast/http/impl/read.ipp
+++ b/include/beast/http/impl/read.ipp
@@ -64,9 +64,8 @@ public:
template
read_header_op(
DeducedHandler&& h, Stream& s, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), s,
- std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
(*this)(error_code{}, false);
}
@@ -236,9 +235,8 @@ public:
template
read_op(DeducedHandler&& h, Stream& s, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), s,
- std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
(*this)(error_code{}, false);
}
diff --git a/include/beast/http/impl/write.ipp b/include/beast/http/impl/write.ipp
index c2f7104364..13a5078472 100644
--- a/include/beast/http/impl/write.ipp
+++ b/include/beast/http/impl/write.ipp
@@ -126,9 +126,8 @@ public:
template
write_streambuf_op(DeducedHandler&& h, Stream& s,
Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h),
- s, std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
(*this)(error_code{}, 0, false);
}
@@ -373,9 +372,8 @@ public:
template
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
- : d_(make_handler_ptr(
- std::forward(h), s,
- std::forward(args)...))
+ : d_(std::forward(h),
+ s, std::forward(args)...)
{
auto& d = *d_;
auto sp = d_;
diff --git a/include/beast/version.hpp b/include/beast/version.hpp
index cc71abf074..10d4028c01 100644
--- a/include/beast/version.hpp
+++ b/include/beast/version.hpp
@@ -16,6 +16,6 @@
//
#define BEAST_VERSION 100000
-#define BEAST_VERSION_STRING "1.0.0-b23"
+#define BEAST_VERSION_STRING "1.0.0-b26"
#endif
diff --git a/include/beast/websocket/detail/decorator.hpp b/include/beast/websocket/detail/decorator.hpp
index 1250d35b1e..4be383da23 100644
--- a/include/beast/websocket/detail/decorator.hpp
+++ b/include/beast/websocket/detail/decorator.hpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
namespace beast {
@@ -29,104 +30,136 @@ struct abstract_decorator
virtual
void
- operator()(request_type& req) = 0;
+ operator()(request_type& req) const = 0;
virtual
void
- operator()(response_type& resp) = 0;
+ operator()(response_type& res) const = 0;
};
-template
+template
class decorator : public abstract_decorator
{
- T t_;
+ F f_;
class call_req_possible
{
template().operator()(
+ std::declval().operator()(
std::declval()),
std::true_type{})>
static R check(int);
template
static std::false_type check(...);
public:
- using type = decltype(check(0));
+ using type = decltype(check(0));
};
class call_res_possible
{
template().operator()(
+ std::declval().operator()(
std::declval()),
std::true_type{})>
static R check(int);
template
static std::false_type check(...);
public:
- using type = decltype(check(0));
+ using type = decltype(check(0));
};
public:
- decorator() = default;
-
- decorator(T&& t)
- : t_(std::move(t))
+ decorator(F&& t)
+ : f_(std::move(t))
{
}
- decorator(T const& t)
- : t_(t)
+ decorator(F const& t)
+ : f_(t)
{
}
void
- operator()(request_type& req) override
+ operator()(request_type& req) const override
{
(*this)(req, typename call_req_possible::type{});
}
void
- operator()(response_type& resp) override
+ operator()(response_type& res) const override
{
- (*this)(resp, typename call_res_possible::type{});
+ (*this)(res, typename call_res_possible::type{});
}
private:
void
- operator()(request_type& req, std::true_type)
+ operator()(request_type& req, std::true_type) const
{
- t_(req);
+ f_(req);
}
void
- operator()(request_type& req, std::false_type)
+ operator()(request_type& req, std::false_type) const
{
req.fields.replace("User-Agent",
std::string{"Beast/"} + BEAST_VERSION_STRING);
}
void
- operator()(response_type& res, std::true_type)
+ operator()(response_type& res, std::true_type) const
{
- t_(res);
+ f_(res);
}
void
- operator()(response_type& res, std::false_type)
+ operator()(response_type& res, std::false_type) const
{
res.fields.replace("Server",
std::string{"Beast/"} + BEAST_VERSION_STRING);
}
};
+class decorator_type
+{
+ std::shared_ptr p_;
+
+public:
+ decorator_type() = delete;
+ decorator_type(decorator_type&&) = default;
+ decorator_type(decorator_type const&) = default;
+ decorator_type& operator=(decorator_type&&) = default;
+ decorator_type& operator=(decorator_type const&) = default;
+
+ template::type,
+ decorator_type>::value>>
+ decorator_type(F&& f)
+ : p_(std::make_shared>(
+ std::forward(f)))
+ {
+ BOOST_ASSERT(p_);
+ }
+
+ void
+ operator()(request_type& req)
+ {
+ (*p_)(req);
+ BOOST_ASSERT(p_);
+ }
+
+ void
+ operator()(response_type& res)
+ {
+ (*p_)(res);
+ BOOST_ASSERT(p_);
+ }
+};
+
struct default_decorator
{
};
-using decorator_type =
- std::unique_ptr;
-
} // detail
} // websocket
} // beast
diff --git a/include/beast/websocket/detail/invokable.hpp b/include/beast/websocket/detail/invokable.hpp
index 7fcfc2cf9b..e2a9a24bc9 100644
--- a/include/beast/websocket/detail/invokable.hpp
+++ b/include/beast/websocket/detail/invokable.hpp
@@ -125,7 +125,7 @@ public:
void
emplace(F&& f);
- void
+ bool
maybe_invoke()
{
if(base_)
@@ -133,7 +133,9 @@ public:
auto const basep = base_;
base_ = nullptr;
(*basep)();
+ return true;
}
+ return false;
}
};
diff --git a/include/beast/websocket/detail/mask.hpp b/include/beast/websocket/detail/mask.hpp
index 02ca38fda9..8fb1bcac78 100644
--- a/include/beast/websocket/detail/mask.hpp
+++ b/include/beast/websocket/detail/mask.hpp
@@ -60,11 +60,17 @@ void
maskgen_t<_>::rekey()
{
std::random_device rng;
+#if 0
std::array e;
for(auto& i : e)
i = rng();
+ // VFALCO This constructor causes
+ // address sanitizer to fail, no idea why.
std::seed_seq ss(e.begin(), e.end());
g_.seed(ss);
+#else
+ g_.seed(rng());
+#endif
}
// VFALCO NOTE This generator has 5KB of state!
@@ -73,7 +79,7 @@ using maskgen = maskgen_t;
//------------------------------------------------------------------------------
-using prepared_key_type =
+using prepared_key =
std::conditional::type;
diff --git a/include/beast/websocket/detail/pmd_extension.hpp b/include/beast/websocket/detail/pmd_extension.hpp
new file mode 100644
index 0000000000..8bccb1c877
--- /dev/null
+++ b/include/beast/websocket/detail/pmd_extension.hpp
@@ -0,0 +1,472 @@
+//
+// 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)
+//
+
+#ifndef BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
+#define BEAST_WEBSOCKET_DETAIL_PMD_EXTENSION_HPP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace beast {
+namespace websocket {
+namespace detail {
+
+// permessage-deflate offer parameters
+//
+// "context takeover" means:
+// preserve sliding window across messages
+//
+struct pmd_offer
+{
+ bool accept;
+
+ // 0 = absent, or 8..15
+ int server_max_window_bits;
+
+ // -1 = present, 0 = absent, or 8..15
+ int client_max_window_bits;
+
+ // `true` if server_no_context_takeover offered
+ bool server_no_context_takeover;
+
+ // `true` if client_no_context_takeover offered
+ bool client_no_context_takeover;
+};
+
+template
+int
+parse_bits(boost::string_ref const& s)
+{
+ if(s.size() == 0)
+ return -1;
+ if(s.size() > 2)
+ return -1;
+ if(s[0] < '1' || s[0] > '9')
+ return -1;
+ int i = 0;
+ for(auto c : s)
+ {
+ if(c < '0' || c > '9')
+ return -1;
+ i = 10 * i + (c - '0');
+ }
+ return i;
+}
+
+// Parse permessage-deflate request fields
+//
+template
+void
+pmd_read(pmd_offer& offer, Fields const& fields)
+{
+ offer.accept = false;
+ offer.server_max_window_bits= 0;
+ offer.client_max_window_bits = 0;
+ offer.server_no_context_takeover = false;
+ offer.client_no_context_takeover = false;
+
+ using beast::detail::ci_equal;
+ http::ext_list list{
+ fields["Sec-WebSocket-Extensions"]};
+ for(auto const& ext : list)
+ {
+ if(ci_equal(ext.first, "permessage-deflate"))
+ {
+ for(auto const& param : ext.second)
+ {
+ if(ci_equal(param.first,
+ "server_max_window_bits"))
+ {
+ if(offer.server_max_window_bits != 0)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(param.second.empty())
+ {
+ // The negotiation offer extension
+ // parameter is missing the value.
+ //
+ return; // MUST decline
+ }
+ offer.server_max_window_bits =
+ parse_bits(param.second);
+ if( offer.server_max_window_bits < 8 ||
+ offer.server_max_window_bits > 15)
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ }
+ else if(ci_equal(param.first,
+ "client_max_window_bits"))
+ {
+ if(offer.client_max_window_bits != 0)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ offer.client_max_window_bits =
+ parse_bits(param.second);
+ if( offer.client_max_window_bits < 8 ||
+ offer.client_max_window_bits > 15)
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ }
+ else
+ {
+ offer.client_max_window_bits = -1;
+ }
+ }
+ else if(ci_equal(param.first,
+ "server_no_context_takeover"))
+ {
+ if(offer.server_no_context_takeover)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ offer.server_no_context_takeover = true;
+ }
+ else if(ci_equal(param.first,
+ "client_no_context_takeover"))
+ {
+ if(offer.client_no_context_takeover)
+ {
+ // The negotiation offer contains multiple
+ // extension parameters with the same name.
+ //
+ return; // MUST decline
+ }
+ if(! param.second.empty())
+ {
+ // The negotiation offer contains an
+ // extension parameter with an invalid value.
+ //
+ return; // MUST decline
+ }
+ offer.client_no_context_takeover = true;
+ }
+ else
+ {
+ // The negotiation offer contains an extension
+ // parameter not defined for use in an offer.
+ //
+ return; // MUST decline
+ }
+ }
+ offer.accept = true;
+ return;
+ }
+ }
+}
+
+// Set permessage-deflate fields for a client offer
+//
+template
+void
+pmd_write(Fields& fields, pmd_offer const& offer)
+{
+ std::string s;
+ s = "permessage-deflate";
+ if(offer.server_max_window_bits != 0)
+ {
+ if(offer.server_max_window_bits != -1)
+ {
+ s += "; server_max_window_bits=";
+ s += std::to_string(
+ offer.server_max_window_bits);
+ }
+ else
+ {
+ s += "; server_max_window_bits";
+ }
+ }
+ if(offer.client_max_window_bits != 0)
+ {
+ if(offer.client_max_window_bits != -1)
+ {
+ s += "; client_max_window_bits=";
+ s += std::to_string(
+ offer.client_max_window_bits);
+ }
+ else
+ {
+ s += "; client_max_window_bits";
+ }
+ }
+ if(offer.server_no_context_takeover)
+ {
+ s += "; server_no_context_takeover";
+ }
+ if(offer.client_no_context_takeover)
+ {
+ s += "; client_no_context_takeover";
+ }
+ fields.replace("Sec-WebSocket-Extensions", s);
+}
+
+// Negotiate a permessage-deflate client offer
+//
+template
+void
+pmd_negotiate(
+ Fields& fields,
+ pmd_offer& config,
+ pmd_offer const& offer,
+ permessage_deflate const& o)
+{
+ if(! (offer.accept && o.server_enable))
+ {
+ config.accept = false;
+ return;
+ }
+ config.accept = true;
+
+ std::string s = "permessage-deflate";
+
+ config.server_no_context_takeover =
+ offer.server_no_context_takeover ||
+ o.server_no_context_takeover;
+ if(config.server_no_context_takeover)
+ s += "; server_no_context_takeover";
+
+ config.client_no_context_takeover =
+ o.client_no_context_takeover ||
+ offer.client_no_context_takeover;
+ if(config.client_no_context_takeover)
+ s += "; client_no_context_takeover";
+
+ if(offer.server_max_window_bits != 0)
+ config.server_max_window_bits = std::min(
+ offer.server_max_window_bits,
+ o.server_max_window_bits);
+ else
+ config.server_max_window_bits =
+ o.server_max_window_bits;
+ if(config.server_max_window_bits < 15)
+ {
+ // ZLib's deflateInit silently treats 8 as
+ // 9 due to a bug, so prevent 8 from being used.
+ //
+ if(config.server_max_window_bits < 9)
+ config.server_max_window_bits = 9;
+
+ s += "; server_max_window_bits=";
+ s += std::to_string(
+ config.server_max_window_bits);
+ }
+
+ switch(offer.client_max_window_bits)
+ {
+ case -1:
+ // extension parameter is present with no value
+ config.client_max_window_bits =
+ o.client_max_window_bits;
+ if(config.client_max_window_bits < 15)
+ {
+ s += "; client_max_window_bits=";
+ s += std::to_string(
+ config.client_max_window_bits);
+ }
+ break;
+
+ case 0:
+ /* extension parameter is absent.
+
+ If a received extension negotiation offer doesn't have the
+ "client_max_window_bits" extension parameter, the corresponding
+ extension negotiation response to the offer MUST NOT include the
+ "client_max_window_bits" extension parameter.
+ */
+ if(o.client_max_window_bits == 15)
+ config.client_max_window_bits = 15;
+ else
+ config.accept = false;
+ break;
+
+ default:
+ // extension parameter has value in [8..15]
+ config.client_max_window_bits = std::min(
+ o.client_max_window_bits,
+ offer.client_max_window_bits);
+ s += "; client_max_window_bits=";
+ s += std::to_string(
+ config.client_max_window_bits);
+ break;
+ }
+ if(config.accept)
+ fields.replace("Sec-WebSocket-Extensions", s);
+}
+
+// Normalize the server's response
+//
+inline
+void
+pmd_normalize(pmd_offer& offer)
+{
+ if(offer.accept)
+ {
+ if( offer.server_max_window_bits == 0)
+ offer.server_max_window_bits = 15;
+
+ if( offer.client_max_window_bits == 0 ||
+ offer.client_max_window_bits == -1)
+ offer.client_max_window_bits = 15;
+ }
+}
+
+//--------------------------------------------------------------------
+
+// Decompress into a DynamicBuffer
+//
+template
+void
+inflate(
+ InflateStream& zi,
+ DynamicBuffer& dynabuf,
+ boost::asio::const_buffer const& in,
+ error_code& ec)
+{
+ using boost::asio::buffer_cast;
+ using boost::asio::buffer_size;
+ zlib::z_params zs;
+ zs.avail_in = buffer_size(in);
+ zs.next_in = buffer_cast(in);
+ for(;;)
+ {
+ // VFALCO we could be smarter about the size
+ auto const bs = dynabuf.prepare(
+ read_size_helper(dynabuf, 65536));
+ auto const out = *bs.begin();
+ zs.avail_out = buffer_size(out);
+ zs.next_out = buffer_cast(out);
+ zi.write(zs, zlib::Flush::sync, ec);
+ dynabuf.commit(zs.total_out);
+ zs.total_out = 0;
+ if( ec == zlib::error::need_buffers ||
+ ec == zlib::error::end_of_stream)
+ {
+ ec = {};
+ break;
+ }
+ if(ec)
+ return;
+ }
+}
+
+// Compress a buffer sequence
+// Returns: `true` if more calls are needed
+//
+template
+bool
+deflate(
+ DeflateStream& zo,
+ boost::asio::mutable_buffer& out,
+ consuming_buffers& cb,
+ bool fin,
+ error_code& ec)
+{
+ using boost::asio::buffer;
+ using boost::asio::buffer_cast;
+ using boost::asio::buffer_size;
+ BOOST_ASSERT(buffer_size(out) >= 6);
+ zlib::z_params zs;
+ zs.avail_in = 0;
+ zs.next_in = nullptr;
+ zs.avail_out = buffer_size(out);
+ zs.next_out = buffer_cast(out);
+ for(auto const& in : cb)
+ {
+ zs.avail_in = buffer_size(in);
+ if(zs.avail_in == 0)
+ continue;
+ zs.next_in = buffer_cast(in);
+ zo.write(zs, zlib::Flush::none, ec);
+ if(ec)
+ {
+ if(ec != zlib::error::need_buffers)
+ return false;
+ BOOST_ASSERT(zs.avail_out == 0);
+ BOOST_ASSERT(zs.total_out == buffer_size(out));
+ ec = {};
+ break;
+ }
+ if(zs.avail_out == 0)
+ {
+ BOOST_ASSERT(zs.total_out == buffer_size(out));
+ break;
+ }
+ BOOST_ASSERT(zs.avail_in == 0);
+ }
+ cb.consume(zs.total_in);
+ if(zs.avail_out > 0 && fin)
+ {
+ auto const remain = buffer_size(cb);
+ if(remain == 0)
+ {
+ // Inspired by Mark Adler
+ // https://github.com/madler/zlib/issues/149
+ //
+ // VFALCO We could do this flush twice depending
+ // on how much space is in the output.
+ zo.write(zs, zlib::Flush::block, ec);
+ BOOST_ASSERT(! ec || ec == zlib::error::need_buffers);
+ if(ec == zlib::error::need_buffers)
+ ec = {};
+ if(ec)
+ return false;
+ if(zs.avail_out >= 6)
+ {
+ zo.write(zs, zlib::Flush::full, ec);
+ BOOST_ASSERT(! ec);
+ // remove flush marker
+ zs.total_out -= 4;
+ out = buffer(
+ buffer_cast(out), zs.total_out);
+ return false;
+ }
+ }
+ }
+ out = buffer(
+ buffer_cast(out), zs.total_out);
+ return true;
+}
+
+} // detail
+} // websocket
+} // beast
+
+#endif
diff --git a/include/beast/websocket/detail/stream_base.hpp b/include/beast/websocket/detail/stream_base.hpp
index 2256d14035..b183fc46c7 100644
--- a/include/beast/websocket/detail/stream_base.hpp
+++ b/include/beast/websocket/detail/stream_base.hpp
@@ -15,10 +15,13 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -53,20 +56,13 @@ protected:
std::size_t rd_msg_max_ =
16 * 1024 * 1024; // max message size
bool wr_autofrag_ = true; // auto fragment
- std::size_t wr_buf_size_ = 4096; // mask buffer size
+ std::size_t wr_buf_size_ = 4096; // write buffer size
+ std::size_t rd_buf_size_ = 4096; // read buffer size
opcode wr_opcode_ = opcode::text; // outgoing message type
pong_cb pong_cb_; // pong callback
role_type role_; // server or client
bool failed_; // the connection failed
- 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_; // expecting a continuation frame
-
bool wr_close_; // sent close frame
op* wr_block_; // op currenly writing
@@ -75,6 +71,34 @@ protected:
invokable wr_op_; // invoked after read completes
close_reason cr_; // set from received close frame
+ // State information for the message being received
+ //
+ struct rd_t
+ {
+ // opcode of current message being read
+ opcode op;
+
+ // `true` if the next frame is a continuation.
+ bool cont;
+
+ // Checks that test messages are valid utf8
+ detail::utf8_checker utf8;
+
+ // Size of the current message so far.
+ std::uint64_t size;
+
+ // Size of the read buffer.
+ // This gets set to the read buffer size option at the
+ // beginning of sending a message, so that the option can be
+ // changed mid-send without affecting the current message.
+ std::size_t buf_size;
+
+ // The read buffer. Used for compression and masking.
+ std::unique_ptr buf;
+ };
+
+ rd_t rd_;
+
// State information for the message being sent
//
struct wr_t
@@ -99,36 +123,43 @@ protected:
// This gets set to the write buffer size option at the
// beginning of sending a message, so that the option can be
// changed mid-send without affecting the current message.
- std::size_t size;
+ std::size_t buf_size;
- // The write buffer.
+ // The write buffer. Used for compression and masking.
// The buffer is allocated or reallocated at the beginning of
// sending a message.
std::unique_ptr buf;
-
- void
- open()
- {
- cont = false;
- size = 0;
- }
-
- void
- close()
- {
- buf.reset();
- }
};
wr_t wr_;
+ // State information for the permessage-deflate extension
+ struct pmd_t
+ {
+ // `true` if current read message is compressed
+ bool rd_set;
+
+ zlib::deflate_stream zo;
+ zlib::inflate_stream zi;
+ };
+
+ // If not engaged, then permessage-deflate is not
+ // enabled for the currently active session.
+ std::unique_ptr pmd_;
+
+ // Local options for permessage-deflate
+ permessage_deflate pmd_opts_;
+
+ // Offer for clients, negotiated result for servers
+ pmd_offer pmd_config_;
+
stream_base(stream_base&&) = default;
stream_base(stream_base const&) = delete;
stream_base& operator=(stream_base&&) = default;
stream_base& operator=(stream_base const&) = delete;
stream_base()
- : d_(new decorator{})
+ : d_(detail::default_decorator{})
{
}
@@ -142,15 +173,24 @@ protected:
template