From 142b78511924c50d460fd7dff0d2dfd0b307cf6f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 24 Aug 2017 06:21:30 -0700 Subject: [PATCH] split up websocket tests --- CHANGELOG.md | 1 + include/boost/beast/websocket/stream.hpp | 7 +- test/beast/websocket/CMakeLists.txt | 9 +- test/beast/websocket/Jamfile | 6 + test/beast/websocket/accept.cpp | 586 ++++ test/beast/websocket/close.cpp | 258 ++ test/beast/websocket/handshake.cpp | 228 ++ test/beast/websocket/ping.cpp | 211 ++ test/beast/websocket/read.cpp | 160 + test/beast/websocket/stream.cpp | 2758 +---------------- test/beast/websocket/test.hpp | 947 ++++++ .../websocket/websocket_async_echo_server.hpp | 411 --- .../websocket/websocket_sync_echo_server.hpp | 354 --- test/beast/websocket/write.cpp | 527 ++++ 14 files changed, 2945 insertions(+), 3518 deletions(-) create mode 100644 test/beast/websocket/accept.cpp create mode 100644 test/beast/websocket/close.cpp create mode 100644 test/beast/websocket/handshake.cpp create mode 100644 test/beast/websocket/ping.cpp create mode 100644 test/beast/websocket/read.cpp create mode 100644 test/beast/websocket/test.hpp delete mode 100644 test/beast/websocket/websocket_async_echo_server.hpp delete mode 100644 test/beast/websocket/websocket_sync_echo_server.hpp create mode 100644 test/beast/websocket/write.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 97201146..c3ba22bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ WebSocket: * websocket ping tests * Fix websocket close_op resume state * websocket write tests +* split up websocket tests API Changes: diff --git a/include/boost/beast/websocket/stream.hpp b/include/boost/beast/websocket/stream.hpp index 953e0b3b..bc4b5f36 100644 --- a/include/boost/beast/websocket/stream.hpp +++ b/include/boost/beast/websocket/stream.hpp @@ -115,9 +115,12 @@ enum class frame_type template class stream { - friend class detail::frame_test; - friend class stream_test; friend class frame_test; + friend class stream_test; + friend class stream_close_test; + friend class stream_ping_test; + friend class stream_read_test; + friend class stream_write_test; /* The read buffer has to be at least as large as the largest possible control frame including diff --git a/test/beast/websocket/CMakeLists.txt b/test/beast/websocket/CMakeLists.txt index 1374a7de..2ddb2ae5 100644 --- a/test/beast/websocket/CMakeLists.txt +++ b/test/beast/websocket/CMakeLists.txt @@ -16,10 +16,14 @@ add_executable (tests-beast-websocket ${EXTRAS_INCLUDES} ${TEST_MAIN} Jamfile - websocket_async_echo_server.hpp - websocket_sync_echo_server.hpp + test.hpp + accept.cpp + close.cpp error.cpp + handshake.cpp option.cpp + ping.cpp + read.cpp rfc6455.cpp role.cpp stream.cpp @@ -27,4 +31,5 @@ add_executable (tests-beast-websocket frame.cpp mask.cpp utf8_checker.cpp + write.cpp ) diff --git a/test/beast/websocket/Jamfile b/test/beast/websocket/Jamfile index e9e424a0..5fa2d65f 100644 --- a/test/beast/websocket/Jamfile +++ b/test/beast/websocket/Jamfile @@ -8,8 +8,13 @@ # local SOURCES = + accept.cpp + close.cpp error.cpp + handshake.cpp option.cpp + ping.cpp + read.cpp rfc6455.cpp stream.cpp teardown.cpp @@ -17,6 +22,7 @@ local SOURCES = mask.cpp role.cpp utf8_checker.cpp + write.cpp ; local RUN_TESTS ; diff --git a/test/beast/websocket/accept.cpp b/test/beast/websocket/accept.cpp new file mode 100644 index 00000000..d662843e --- /dev/null +++ b/test/beast/websocket/accept.cpp @@ -0,0 +1,586 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_accept_test : public websocket_test_suite +{ +public: + template + void + doTestAccept(Wrap const& w) + { + class res_decorator + { + bool& b_; + + public: + res_decorator(res_decorator const&) = default; + + explicit + res_decorator(bool& b) + : b_(b) + { + } + + void + operator()(response_type&) const + { + b_ = true; + } + }; + + auto const big = [] + { + std::string s; + s += "X1: " + std::string(2000, '*') + "\r\n"; + return s; + }(); + + // request in stream + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + ts.append( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ts.read_size(20); + w.accept(ws); + // VFALCO validate contents of ws.next_layer().str? + }); + + // request in stream, oversized + { + stream ws{ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + auto tr = connect(ws.next_layer()); + try + { + w.accept(ws); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in stream, decorator + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + ts.append( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ts.read_size(20); + bool called = false; + w.accept_ex(ws, res_decorator{called}); + BEAST_EXPECT(called); + }); + + // request in stream, decorator, oversized + { + stream ws{ios_, + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + auto tr = connect(ws.next_layer()); + try + { + bool called = false; + w.accept_ex(ws, res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + // VFALCO Its the http error category... + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + w.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + )); + }); + + // request in buffers, oversize + { + stream ws{ios_}; + auto tr = connect(ws.next_layer()); + try + { + w.accept(ws, boost::asio::buffer( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n" + )); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers, decorator + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + bool called = false; + w.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + }); + + // request in buffers, decorator, oversized + { + stream ws{ios_}; + auto tr = connect(ws.next_layer()); + try + { + bool called = false; + w.accept_ex(ws, boost::asio::buffer( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"), + res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers and stream + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + ts.append( + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ts.read_size(16); + w.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + // VFALCO validate contents of ws.next_layer().str? + }); + + // request in buffers and stream, oversized + { + stream ws{ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + auto tr = connect(ws.next_layer()); + try + { + w.accept(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + )); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in buffers and stream, decorator + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + ts.append( + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ts.read_size(16); + bool called = false; + w.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + BEAST_EXPECT(called); + }); + + // request in buffers and stream, decorator, oversize + { + stream ws{ios_, + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + + big + + "\r\n"}; + auto tr = connect(ws.next_layer()); + try + { + bool called = false; + w.accept_ex(ws, sbuf( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n"), + res_decorator{called}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == http::error::buffer_overflow, + se.code().message()); + } + } + + // request in message + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + w.accept(ws, req); + }); + + // request in message, decorator + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + bool called = false; + w.accept_ex(ws, req, + res_decorator{called}); + BEAST_EXPECT(called); + }); + + // request in message, close frame in stream + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + request_type req; + req.method(http::verb::get); + req.target("/"); + req.version = 11; + req.insert(http::field::host, "localhost"); + req.insert(http::field::upgrade, "websocket"); + req.insert(http::field::connection, "upgrade"); + req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); + req.insert(http::field::sec_websocket_version, "13"); + ts.append("\x88\x82\xff\xff\xff\xff\xfc\x17"); + w.accept(ws, req); + try + { + static_buffer<1> b; + w.read(ws, b); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(e.code() != websocket::error::closed) + throw; + } + }); + + // failed handshake (missing Sec-WebSocket-Key) + doTestLoop([&](test::stream& ts) + { + stream ws{ts}; + auto tr = connect(ws.next_layer()); + ts.append( + "GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "Upgrade: websocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n"); + ts.read_size(20); + try + { + w.accept(ws); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if( e.code() != + websocket::error::handshake_failed && + e.code() != + boost::asio::error::eof) + throw; + } + }); + + // Closed by client + { + stream ws{ios_}; + auto tr = connect(ws.next_layer()); + tr.close(); + try + { + w.accept(ws); + fail("success", __FILE__, __LINE__); + } + catch(system_error const& e) + { + if(! BEAST_EXPECTS( + e.code() == error::closed, + e.code().message())) + throw; + } + } + } + + void + testAccept() + { + doTestAccept(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestAccept(AsyncClient{yield}); + }); + + // + // Bad requests + // + + auto const check = + [&](error_code const& ev, std::string const& s) + { + for(int i = 0; i < 3; ++i) + { + std::size_t n; + switch(i) + { + default: + case 0: + n = 1; + break; + case 1: + n = s.size() / 2; + break; + case 2: + n = s.size() - 1; + break; + } + stream ws{ios_}; + auto tr = connect(ws.next_layer()); + ws.next_layer().append( + s.substr(n, s.size() - n)); + try + { + ws.accept( + boost::asio::buffer(s.data(), n)); + BEAST_EXPECTS(! ev, ev.message()); + } + catch(system_error const& se) + { + BEAST_EXPECTS(se.code() == ev, se.what()); + } + } + }; + + // wrong version + check(http::error::end_of_stream, + "GET / HTTP/1.0\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // wrong method + check(error::handshake_failed, + "POST / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing Host + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing Sec-WebSocket-Key + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing Sec-WebSocket-Version + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "\r\n" + ); + // wrong Sec-WebSocket-Version + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive,upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 1\r\n" + "\r\n" + ); + // missing upgrade token + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: HTTP/2\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing connection token + check(error::handshake_failed, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // valid request + check({}, + "GET / HTTP/1.1\r\n" + "Host: localhost:80\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + } + + void + run() override + { + testAccept(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_accept); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/close.cpp b/test/beast/websocket/close.cpp new file mode 100644 index 00000000..3e00ec80 --- /dev/null +++ b/test/beast/websocket/close.cpp @@ -0,0 +1,258 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_close_test : public websocket_test_suite +{ +public: + template + void + doTestClose(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // normal close + doTest(pmd, [&](ws_type& ws) + { + w.close(ws, {}); + }); + + // double close + { + echo_server es{log}; + stream ws{ios_}; + ws.next_layer().connect(es.stream()); + w.handshake(ws, "localhost", "/"); + w.close(ws, {}); + try + { + w.close(ws, {}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == boost::asio::error::operation_aborted, + se.code().message()); + } + } + + // drain a message after close + doTest(pmd, [&](ws_type& ws) + { + ws.next_layer().append("\x81\x01\x2a"); + w.close(ws, {}); + }); + + // drain a big message after close + { + std::string s; + s = "\x81\x7e\x10\x01"; + s.append(4097, '*'); + doTest(pmd, [&](ws_type& ws) + { + ws.next_layer().append(s); + w.close(ws, {}); + }); + } + + // drain a ping after close + doTest(pmd, [&](ws_type& ws) + { + ws.next_layer().append("\x89\x01*"); + w.close(ws, {}); + }); + + // drain invalid message frame after close + { + echo_server es{log}; + stream ws{ios_}; + ws.next_layer().connect(es.stream()); + w.handshake(ws, "localhost", "/"); + ws.next_layer().append("\x81\x81\xff\xff\xff\xff*"); + try + { + w.close(ws, {}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::failed, + se.code().message()); + } + } + + // drain invalid close frame after close + { + echo_server es{log}; + stream ws{ios_}; + ws.next_layer().connect(es.stream()); + w.handshake(ws, "localhost", "/"); + ws.next_layer().append("\x88\x01*"); + try + { + w.close(ws, {}); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == error::failed, + se.code().message()); + } + } + + // close with incomplete read message + doTest(pmd, [&](ws_type& ws) + { + ws.next_layer().append("\x81\x02**"); + static_buffer<1> b; + w.read_some(ws, 1, b); + w.close(ws, {}); + }); + } + + void + testClose() + { + doTestClose(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestClose(AsyncClient{yield}); + }); + + // suspend on write + { + echo_server es{log}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + ios.run(); + BEAST_EXPECT(count == 2); + } + + // suspend on read + { + echo_server es{log}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + flat_buffer b; + std::size_t count = 0; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS( + ec == error::closed, ec.message()); + }); + BEAST_EXPECT(ws.rd_block_); + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + BEAST_EXPECT(ws.wr_close_); + ios.run(); + BEAST_EXPECT(count == 2); + } + } + + void + testCloseSuspend() + { + echo_server es{log, kind::async}; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + + // Cause close to be received + es.async_close(); + + multi_buffer b; + std::size_t count = 0; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(ec == error::closed, + ec.message()); + }); + while(! ws.wr_block_) + ios.run_one(); + // try to close + ws.async_close("payload", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 2) + break; + ios.run_one(); + } + BEAST_EXPECT(n < limit); + ios.run(); + } + + void + run() override + { + testClose(); + testCloseSuspend(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_close); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/handshake.cpp b/test/beast/websocket/handshake.cpp new file mode 100644 index 00000000..877433bc --- /dev/null +++ b/test/beast/websocket/handshake.cpp @@ -0,0 +1,228 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_handshake_test : public websocket_test_suite +{ +public: + template + void + doTestHandshake(Wrap const& w) + { + class req_decorator + { + bool& b_; + + public: + req_decorator(req_decorator const&) = default; + + explicit + req_decorator(bool& b) + : b_(b) + { + } + + void + operator()(request_type&) const + { + b_ = true; + } + }; + + // handshake + doTestLoop([&](test::stream& ts) + { + echo_server es{log}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + try + { + w.handshake(ws, "localhost", "/"); + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + + // handshake, response + doTestLoop([&](test::stream& ts) + { + echo_server es{log}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + response_type res; + try + { + w.handshake(ws, res, "localhost", "/"); + // VFALCO validate res? + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + + // handshake, decorator + doTestLoop([&](test::stream& ts) + { + echo_server es{log}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + bool called = false; + try + { + w.handshake_ex(ws, "localhost", "/", + req_decorator{called}); + BEAST_EXPECT(called); + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + + // handshake, response, decorator + doTestLoop([&](test::stream& ts) + { + echo_server es{log}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + bool called = false; + response_type res; + try + { + w.handshake_ex(ws, res, "localhost", "/", + req_decorator{called}); + // VFALCO validate res? + BEAST_EXPECT(called); + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + } + + void + testHandshake() + { + doTestHandshake(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestHandshake(AsyncClient{yield}); + }); + + auto const check = + [&](std::string const& s) + { + stream ws{ios_}; + auto tr = connect(ws.next_layer()); + ws.next_layer().append(s); + tr.close(); + try + { + ws.handshake("localhost:80", "/"); + fail(); + } + catch(system_error const& se) + { + BEAST_EXPECT(se.code() == error::handshake_failed); + } + }; + // wrong HTTP version + check( + "HTTP/1.0 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // wrong status + check( + "HTTP/1.1 200 OK\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing upgrade token + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: HTTP/2\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing connection token + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: keep-alive\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // missing accept key + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + // wrong accept key + check( + "HTTP/1.1 101 Switching Protocols\r\n" + "Server: beast\r\n" + "Upgrade: WebSocket\r\n" + "Connection: upgrade\r\n" + "Sec-WebSocket-Accept: *\r\n" + "Sec-WebSocket-Version: 13\r\n" + "\r\n" + ); + } + + void + run() override + { + testHandshake(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_handshake); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/ping.cpp b/test/beast/websocket/ping.cpp new file mode 100644 index 00000000..bb7fe3c2 --- /dev/null +++ b/test/beast/websocket/ping.cpp @@ -0,0 +1,211 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_ping_test : public websocket_test_suite +{ +public: + template + void + doTestPing(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // ping + doTest(pmd, [&](ws_type& ws) + { + w.ping(ws, {}); + }); + + // pong + doTest(pmd, [&](ws_type& ws) + { + w.pong(ws, {}); + }); + } + + void + testPing() + { + doTestPing(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestPing(AsyncClient{yield}); + }); + + // ping, already closed + { + stream ws{ios_}; + error_code ec; + ws.ping({}, ec); + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + } + + // async_ping, already closed + { + boost::asio::io_service ios; + stream ws{ios}; + ws.async_ping({}, + [&](error_code ec) + { + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ios.run(); + } + + // pong, already closed + { + stream ws{ios_}; + error_code ec; + ws.pong({}, ec); + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + } + + // async_pong, already closed + { + boost::asio::io_service ios; + stream ws{ios}; + ws.async_pong({}, + [&](error_code ec) + { + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ios.run(); + } + + // suspend on write + { + echo_server es{log}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + ws.async_write(sbuf("*"), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ws.async_close({}, [&](error_code){}); + ios.run(); + BEAST_EXPECT(count == 2); + } + } + + void + testPingSuspend() + { + echo_server es{log, kind::async}; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + + // Cause close to be received + es.async_close(); + + multi_buffer b; + std::size_t count = 0; + // Read a close frame. + // Sends a close frame, blocking writes. + ws.async_read(b, + [&](error_code ec, std::size_t) + { + // Read should complete with error::closed + ++count; + BEAST_EXPECTS(ec == error::closed, + ec.message()); + // Pings after a close are aborted + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + if(! BEAST_EXPECT(run_until(ios, 100, + [&]{ return ws.wr_close_; }))) + return; + // Try to ping + ws.async_ping("payload", + [&](error_code ec) + { + // Pings after a close are aborted + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + // Subsequent calls to close are aborted + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + static std::size_t constexpr limit = 100; + std::size_t n; + for(n = 0; n < limit; ++n) + { + if(count >= 4) + break; + ios.run_one(); + } + BEAST_EXPECT(n < limit); + ios.run(); + } + + void + run() override + { + testPing(); + testPingSuspend(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_ping); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/read.cpp b/test/beast/websocket/read.cpp new file mode 100644 index 00000000..7ca92f09 --- /dev/null +++ b/test/beast/websocket/read.cpp @@ -0,0 +1,160 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_read_test : public websocket_test_suite +{ +public: + template + void + doTestRead(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // Read close frames + { + // VFALCO What about asynchronous?? + + auto const check = + [&](error_code ev, string_view s) + { + echo_server es{log}; + stream ws{ios_}; + ws.next_layer().connect(es.stream()); + w.handshake(ws, "localhost", "/"); + ws.next_layer().append(s); + static_buffer<1> b; + error_code ec; + try + { + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + BEAST_EXPECTS(se.code() == ev, + se.code().message()); + } + ws.next_layer().close(); + }; + + // payload length 1 + check(error::failed, + "\x88\x01\x01"); + + // invalid close code 1005 + check(error::failed, + "\x88\x02\x03\xed"); + + // invalid utf8 + check(error::failed, + "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); + + // good utf8 + check(error::closed, + "\x88\x06\xfc\x15utf8"); + } + + pmd.client_enable = true; + pmd.server_enable = true; + + // invalid inflate block + doTest(pmd, [&](ws_type& ws) + { + auto const& s = random_string(); + ws.binary(true); + ws.next_layer().append( + "\xc2\x40" + s.substr(0, 64)); + flat_buffer b; + try + { + w.read(ws, b); + } + catch(system_error const& se) + { + if(se.code() == test::error::fail_error) + throw; + BEAST_EXPECTS(se.code().category() == + zlib::detail::get_error_category(), + se.code().message()); + } + catch(...) + { + throw; + } + }); + } + + void + testRead() + { + doTestRead(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestRead(AsyncClient{yield}); + }); + + // Read close frames + { + auto const check = + [&](error_code ev, string_view s) + { + echo_server es{log}; + stream ws{ios_}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + ws.next_layer().append(s); + static_buffer<1> b; + error_code ec; + ws.read(b, ec); + BEAST_EXPECTS(ec == ev, ec.message()); + ws.next_layer().close(); + }; + + // payload length 1 + check(error::failed, + "\x88\x01\x01"); + + // invalid close code 1005 + check(error::failed, + "\x88\x02\x03\xed"); + + // invalid utf8 + check(error::failed, + "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); + + // good utf8 + check(error::closed, + "\x88\x06\xfc\x15utf8"); + } + } + + void + run() override + { + testRead(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_read); + +} // websocket +} // beast +} // boost diff --git a/test/beast/websocket/stream.cpp b/test/beast/websocket/stream.cpp index bf67527a..8b131ed3 100644 --- a/test/beast/websocket/stream.cpp +++ b/test/beast/websocket/stream.cpp @@ -10,2507 +10,19 @@ // Test that header file is self-contained. #include -#include -#include -#include -#include -#include -#include -#include -#include +#include "test.hpp" namespace boost { namespace beast { namespace websocket { -class stream_test - : public beast::unit_test::suite - , public test::enable_yield_to +class stream_test : public websocket_test_suite { public: - using self = stream_test; - using endpoint_type = boost::asio::ip::tcp::endpoint; - using address_type = boost::asio::ip::address; - using socket_type = boost::asio::ip::tcp::socket; - - using ws_type = - websocket::stream; - - //-------------------------------------------------------------------------- - - enum class kind - { - sync, - async, - async_client - }; - - class echo_server - { - enum - { - buf_size = 20000 - }; - - std::ostream& log_; - boost::asio::io_service ios_; - boost::optional< - boost::asio::io_service::work> work_; - static_buffer buffer_; - test::stream ts_; - std::thread t_; - websocket::stream ws_; - - public: - explicit - echo_server( - std::ostream& log, - kind k = kind::sync) - : log_(log) - , work_(ios_) - , ts_(ios_) - , ws_(ts_) - { - permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - ws_.set_option(pmd); - - switch(k) - { - case kind::sync: - t_ = std::thread{[&]{ do_sync(); }}; - break; - - case kind::async: - t_ = std::thread{[&]{ ios_.run(); }}; - do_accept(); - break; - - case kind::async_client: - t_ = std::thread{[&]{ ios_.run(); }}; - break; - } - } - - ~echo_server() - { - work_ = boost::none; - t_.join(); - } - - test::stream& - stream() - { - return ts_; - } - - void - async_handshake() - { - ws_.async_handshake("localhost", "/", - std::bind( - &echo_server::on_handshake, - this, - std::placeholders::_1)); - } - - void - async_close() - { - ios_.post( - [&] - { - ws_.async_close({}, - std::bind( - &echo_server::on_close, - this, - std::placeholders::_1)); - }); - } - - private: - void - do_sync() - { - try - { - ws_.accept(); - for(;;) - { - ws_.read(buffer_); - ws_.text(ws_.got_text()); - ws_.write(buffer_.data()); - buffer_.consume(buffer_.size()); - } - } - catch(system_error const& se) - { - boost::ignore_unused(se); -#if 0 - if( se.code() != error::closed && - se.code() != error::failed && - se.code() != boost::asio::error::eof) - log_ << "echo_server: " << se.code().message() << std::endl; -#endif - } - catch(std::exception const& e) - { - log_ << "echo_server: " << e.what() << std::endl; - } - } - - void - do_accept() - { - ws_.async_accept(std::bind( - &echo_server::on_accept, - this, - std::placeholders::_1)); - } - - void - on_handshake(error_code ec) - { - if(ec) - return fail(ec); - do_read(); - } - - void - on_accept(error_code ec) - { - if(ec) - return fail(ec); - do_read(); - } - - void - do_read() - { - ws_.async_read(buffer_, - std::bind( - &echo_server::on_read, - this, - std::placeholders::_1)); - } - - void - on_read(error_code ec) - { - if(ec) - return fail(ec); - ws_.text(ws_.got_text()); - ws_.async_write(buffer_.data(), - std::bind( - &echo_server::on_write, - this, - std::placeholders::_1)); - } - - void - on_write(error_code ec) - { - if(ec) - return fail(ec); - buffer_.consume(buffer_.size()); - do_read(); - } - - void - on_close(error_code ec) - { - if(ec) - return fail(ec); - } - - void - fail(error_code ec) - { - boost::ignore_unused(ec); -#if 0 - if( ec != error::closed && - ec != error::failed && - ec != boost::asio::error::eof) - log_ << - "echo_server_async: " << - ec.message() << - std::endl; -#endif - } - }; - - //-------------------------------------------------------------------------- - - template - void - doTestLoop(Test const& f) - { - // This number has to be high for the - // test that writes the large buffer. - static std::size_t constexpr limit = 1000; - - std::size_t n; - for(n = 0; n <= limit; ++n) - { - test::fail_counter fc{n}; - test::stream ts{ios_, fc}; - try - { - f(ts); - ts.close(); - break; - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == test::error::fail_error, - se.code().message()); - } - catch(std::exception const& e) - { - fail(e.what(), __FILE__, __LINE__); - } - ts.close(); - continue; - } - BEAST_EXPECT(n < limit); - } - - template - void - doTest( - permessage_deflate const& pmd, - Test const& f) - { - // This number has to be high for the - // test that writes the large buffer. - static std::size_t constexpr limit = 1000; - - for(int i = 0; i < 2; ++i) - { - std::size_t n; - for(n = 0; n <= limit; ++n) - { - test::fail_counter fc{n}; - test::stream ts{ios_, fc}; - ws_type ws{ts}; - ws.set_option(pmd); - - echo_server es{log, i==1 ? - kind::async : kind::sync}; - error_code ec; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - if(ec) - { - ts.close(); - if( ! BEAST_EXPECTS( - ec == test::error::fail_error, - ec.message())) - BOOST_THROW_EXCEPTION(system_error{ec}); - continue; - } - try - { - f(ws); - ts.close(); - break; - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == test::error::fail_error, - se.code().message()); - } - catch(std::exception const& e) - { - fail(e.what(), __FILE__, __LINE__); - } - ts.close(); - continue; - } - BEAST_EXPECT(n < limit); - } - } - - template - void - doCloseTest( - Wrap const& w, - ws_type& ws, - close_code code) - { - try - { - multi_buffer b; - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - if(se.code() != error::closed) - throw; - BEAST_EXPECT( - ws.reason().code == code); - } - } - - template - void - doFailTest( - Wrap const& w, - ws_type& ws, - error_code ev) - { - try - { - multi_buffer b; - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - if(se.code() != ev) - throw; - } - } - - //-------------------------------------------------------------------------- - - template - static - std::string - to_string(ConstBufferSequence const& bs) - { - using boost::asio::buffer_cast; - using boost::asio::buffer_size; - std::string s; - s.reserve(buffer_size(bs)); - for(boost::asio::const_buffer b : bs) - s.append(buffer_cast(b), - buffer_size(b)); - return s; - } - - template - class cbuf_helper - { - std::array v_; - boost::asio::const_buffer cb_; - - public: - using value_type = decltype(cb_); - using const_iterator = value_type const*; - - template - explicit - cbuf_helper(Vn... vn) - : v_({{ static_cast(vn)... }}) - , cb_(v_.data(), v_.size()) - { - } - - const_iterator - begin() const - { - return &cb_; - } - - const_iterator - end() const - { - return begin()+1; - } - }; - - template - cbuf_helper - cbuf(Vn... vn) - { - return cbuf_helper(vn...); - } - - template - static - boost::asio::const_buffers_1 - sbuf(const char (&s)[N]) - { - return boost::asio::const_buffers_1(&s[0], N-1); - } - - template< - class DynamicBuffer, - class ConstBufferSequence> - static - void - put( - DynamicBuffer& buffer, - ConstBufferSequence const& buffers) - { - using boost::asio::buffer_copy; - using boost::asio::buffer_size; - buffer.commit(buffer_copy( - buffer.prepare(buffer_size(buffers)), - buffers)); - } - - template - static - bool - run_until(boost::asio::io_service& ios, - std::size_t limit, Pred&& pred) - { - for(std::size_t i = 0; i < limit; ++i) - { - if(pred()) - return true; - ios.run_one(); - } - return false; - } - - //-------------------------------------------------------------------------- - - struct SyncClient - { - template - void - accept(stream& ws) const - { - ws.accept(); - } - - template - typename std::enable_if< - ! http::detail::is_header::value>::type - accept(stream& ws, - Buffers const& buffers) const - { - ws.accept(buffers); - } - - template - void - accept(stream& ws, - http::request const& req) const - { - ws.accept(req); - } - - template - void - accept_ex(stream& ws, - Decorator const& d) const - { - ws.accept_ex(d); - } - - template - typename std::enable_if< - ! http::detail::is_header::value>::type - accept_ex(stream& ws, - Buffers const& buffers, - Decorator const& d) const - { - ws.accept_ex(buffers, d); - } - - template - void - accept_ex(stream& ws, - http::request const& req, - Decorator const& d) const - { - ws.accept_ex(req, d); - } - - template - void - accept_ex(stream& ws, - http::request const& req, - Buffers const& buffers, - Decorator const& d) const - { - ws.accept_ex(req, buffers, d); - } - - template - void - handshake(stream& ws, - string_view uri, - string_view path) const - { - ws.handshake(uri, path); - } - - template - void - handshake(stream& ws, - response_type& res, - string_view uri, - string_view path) const - { - ws.handshake(res, uri, path); - } - - template - void - handshake_ex(stream& ws, - string_view uri, - string_view path, - Decorator const& d) const - { - ws.handshake_ex(uri, path, d); - } - - template - void - handshake_ex(stream& ws, - response_type& res, - string_view uri, - string_view path, - Decorator const& d) const - { - ws.handshake_ex(res, uri, path, d); - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - ws.ping(payload); - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - ws.pong(payload); - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - ws.close(cr); - } - - template< - class NextLayer, class DynamicBuffer> - std::size_t - read(stream& ws, - DynamicBuffer& buffer) const - { - return ws.read(buffer); - } - - template< - class NextLayer, class DynamicBuffer> - std::size_t - read_some(stream& ws, - std::size_t limit, - DynamicBuffer& buffer) const - { - return ws.read_some(buffer, limit); - } - - template< - class NextLayer, class MutableBufferSequence> - std::size_t - read_some(stream& ws, - MutableBufferSequence const& buffers) const - { - return ws.read_some(buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - ws.write(buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_some(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - ws.write_some(fin, buffers); - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - boost::asio::write( - ws.next_layer(), buffers); - } - }; - - //-------------------------------------------------------------------------- - - class AsyncClient - { - yield_context& yield_; - - public: - explicit - AsyncClient(yield_context& yield) - : yield_(yield) - { - } - - template - void - accept(stream& ws) const - { - error_code ec; - ws.async_accept(yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - typename std::enable_if< - ! http::detail::is_header::value>::type - accept(stream& ws, - Buffers const& buffers) const - { - error_code ec; - ws.async_accept(buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - accept(stream& ws, - http::request const& req) const - { - error_code ec; - ws.async_accept(req, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - accept_ex(stream& ws, - Decorator const& d) const - { - error_code ec; - ws.async_accept_ex(d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - typename std::enable_if< - ! http::detail::is_header::value>::type - accept_ex(stream& ws, - Buffers const& buffers, - Decorator const& d) const - { - error_code ec; - ws.async_accept_ex(buffers, d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - accept_ex(stream& ws, - http::request const& req, - Decorator const& d) const - { - error_code ec; - ws.async_accept_ex(req, d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - accept_ex(stream& ws, - http::request const& req, - Buffers const& buffers, - Decorator const& d) const - { - error_code ec; - ws.async_accept_ex( - req, buffers, d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - handshake(stream& ws, - string_view uri, - string_view path) const - { - error_code ec; - ws.async_handshake( - uri, path, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - handshake(stream& ws, - response_type& res, - string_view uri, - string_view path) const - { - error_code ec; - ws.async_handshake( - res, uri, path, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - handshake_ex(stream& ws, - string_view uri, - string_view path, - Decorator const &d) const - { - error_code ec; - ws.async_handshake_ex( - uri, path, d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - handshake_ex(stream& ws, - response_type& res, - string_view uri, - string_view path, - Decorator const &d) const - { - error_code ec; - ws.async_handshake_ex( - res, uri, path, d, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - ping(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_ping(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - pong(stream& ws, - ping_data const& payload) const - { - error_code ec; - ws.async_pong(payload, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template - void - close(stream& ws, - close_reason const& cr) const - { - error_code ec; - ws.async_close(cr, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class DynamicBuffer> - std::size_t - read(stream& ws, - DynamicBuffer& buffer) const - { - error_code ec; - auto const bytes_written = - ws.async_read(buffer, yield_[ec]); - if(ec) - throw system_error{ec}; - return bytes_written; - } - - template< - class NextLayer, class DynamicBuffer> - std::size_t - read_some(stream& ws, - std::size_t limit, - DynamicBuffer& buffer) const - { - error_code ec; - auto const bytes_written = - ws.async_read_some(buffer, limit, yield_[ec]); - if(ec) - throw system_error{ec}; - return bytes_written; - } - - template< - class NextLayer, class MutableBufferSequence> - std::size_t - read_some(stream& ws, - MutableBufferSequence const& buffers) const - { - error_code ec; - auto const bytes_written = - ws.async_read_some(buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - return bytes_written; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write(buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_some(stream& ws, bool fin, - ConstBufferSequence const& buffers) const - { - error_code ec; - ws.async_write_some(fin, buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - - template< - class NextLayer, class ConstBufferSequence> - void - write_raw(stream& ws, - ConstBufferSequence const& buffers) const - { - error_code ec; - boost::asio::async_write( - ws.next_layer(), buffers, yield_[ec]); - if(ec) - throw system_error{ec}; - } - }; - - //-------------------------------------------------------------------------- - // - // Accept - // - //-------------------------------------------------------------------------- - - template - void - doTestAccept(Wrap const& w) - { - class res_decorator - { - bool& b_; - - public: - res_decorator(res_decorator const&) = default; - - explicit - res_decorator(bool& b) - : b_(b) - { - } - - void - operator()(response_type&) const - { - b_ = true; - } - }; - - auto const big = [] - { - std::string s; - s += "X1: " + std::string(2000, '*') + "\r\n"; - return s; - }(); - - // request in stream - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(20); - w.accept(ws); - // VFALCO validate contents of ws.next_layer().str? - }); - - // request in stream, oversized - { - stream ws{ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); - try - { - w.accept(ws); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - // VFALCO Its the http error category... - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); - } - } - - // request in stream, decorator - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(20); - bool called = false; - w.accept_ex(ws, res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in stream, decorator, oversized - { - stream ws{ios_, - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); - try - { - bool called = false; - w.accept_ex(ws, res_decorator{called}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - // VFALCO Its the http error category... - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); - } - } - - // request in buffers - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - w.accept(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - )); - }); - - // request in buffers, oversize - { - stream ws{ios_}; - auto tr = connect(ws.next_layer()); - try - { - w.accept(ws, boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n" - )); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == error::buffer_overflow, - se.code().message()); - } - } - - // request in buffers, decorator - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - bool called = false; - w.accept_ex(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"), - res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in buffers, decorator, oversized - { - stream ws{ios_}; - auto tr = connect(ws.next_layer()); - try - { - bool called = false; - w.accept_ex(ws, boost::asio::buffer( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"), - res_decorator{called}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == error::buffer_overflow, - se.code().message()); - } - } - - // request in buffers and stream - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(16); - w.accept(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - )); - // VFALCO validate contents of ws.next_layer().str? - }); - - // request in buffers and stream, oversized - { - stream ws{ios_, - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); - try - { - w.accept(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - )); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); - } - } - - // request in buffers and stream, decorator - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(16); - bool called = false; - w.accept_ex(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n"), - res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in buffers and stream, decorator, oversize - { - stream ws{ios_, - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - + big + - "\r\n"}; - auto tr = connect(ws.next_layer()); - try - { - bool called = false; - w.accept_ex(ws, sbuf( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n"), - res_decorator{called}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == http::error::buffer_overflow, - se.code().message()); - } - } - - // request in message - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - w.accept(ws, req); - }); - - // request in message, decorator - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - bool called = false; - w.accept_ex(ws, req, - res_decorator{called}); - BEAST_EXPECT(called); - }); - - // request in message, close frame in stream - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - request_type req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.insert(http::field::host, "localhost"); - req.insert(http::field::upgrade, "websocket"); - req.insert(http::field::connection, "upgrade"); - req.insert(http::field::sec_websocket_key, "dGhlIHNhbXBsZSBub25jZQ=="); - req.insert(http::field::sec_websocket_version, "13"); - ts.append("\x88\x82\xff\xff\xff\xff\xfc\x17"); - w.accept(ws, req); - try - { - static_buffer<1> b; - w.read(ws, b); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(e.code() != websocket::error::closed) - throw; - } - }); - - // failed handshake (missing Sec-WebSocket-Key) - doTestLoop([&](test::stream& ts) - { - stream ws{ts}; - auto tr = connect(ws.next_layer()); - ts.append( - "GET / HTTP/1.1\r\n" - "Host: localhost\r\n" - "Upgrade: websocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n"); - ts.read_size(20); - try - { - w.accept(ws); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if( e.code() != - websocket::error::handshake_failed && - e.code() != - boost::asio::error::eof) - throw; - } - }); - - // Closed by client - { - stream ws{ios_}; - auto tr = connect(ws.next_layer()); - tr.close(); - try - { - w.accept(ws); - fail("success", __FILE__, __LINE__); - } - catch(system_error const& e) - { - if(! BEAST_EXPECTS( - e.code() == error::closed, - e.code().message())) - throw; - } - } - } - - void - testAccept() - { - doTestAccept(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestAccept(AsyncClient{yield}); - }); - - // - // Bad requests - // - - auto const check = - [&](error_code const& ev, std::string const& s) - { - for(int i = 0; i < 3; ++i) - { - std::size_t n; - switch(i) - { - default: - case 0: - n = 1; - break; - case 1: - n = s.size() / 2; - break; - case 2: - n = s.size() - 1; - break; - } - stream ws{ios_}; - auto tr = connect(ws.next_layer()); - ws.next_layer().append( - s.substr(n, s.size() - n)); - try - { - ws.accept( - boost::asio::buffer(s.data(), n)); - BEAST_EXPECTS(! ev, ev.message()); - } - catch(system_error const& se) - { - BEAST_EXPECTS(se.code() == ev, se.what()); - } - } - }; - - // wrong version - check(http::error::end_of_stream, - "GET / HTTP/1.0\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // wrong method - check(error::handshake_failed, - "POST / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing Host - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing Sec-WebSocket-Key - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing Sec-WebSocket-Version - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "\r\n" - ); - // wrong Sec-WebSocket-Version - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive,upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 1\r\n" - "\r\n" - ); - // missing upgrade token - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: HTTP/2\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing connection token - check(error::handshake_failed, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // valid request - check({}, - "GET / HTTP/1.1\r\n" - "Host: localhost:80\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - } - - //-------------------------------------------------------------------------- - // - // Close - // - //-------------------------------------------------------------------------- - - template - void - doTestClose(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - - // normal close - doTest(pmd, [&](ws_type& ws) - { - w.close(ws, {}); - }); - - // double close - { - echo_server es{log}; - stream ws{ios_}; - ws.next_layer().connect(es.stream()); - w.handshake(ws, "localhost", "/"); - w.close(ws, {}); - try - { - w.close(ws, {}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == boost::asio::error::operation_aborted, - se.code().message()); - } - } - - // drain a message after close - doTest(pmd, [&](ws_type& ws) - { - ws.next_layer().append("\x81\x01\x2a"); - w.close(ws, {}); - }); - - // drain a big message after close - { - std::string s; - s = "\x81\x7e\x10\x01"; - s.append(4097, '*'); - doTest(pmd, [&](ws_type& ws) - { - ws.next_layer().append(s); - w.close(ws, {}); - }); - } - - // drain a ping after close - doTest(pmd, [&](ws_type& ws) - { - ws.next_layer().append("\x89\x01*"); - w.close(ws, {}); - }); - - // drain invalid message frame after close - { - echo_server es{log}; - stream ws{ios_}; - ws.next_layer().connect(es.stream()); - w.handshake(ws, "localhost", "/"); - ws.next_layer().append("\x81\x81\xff\xff\xff\xff*"); - try - { - w.close(ws, {}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == error::failed, - se.code().message()); - } - } - - // drain invalid close frame after close - { - echo_server es{log}; - stream ws{ios_}; - ws.next_layer().connect(es.stream()); - w.handshake(ws, "localhost", "/"); - ws.next_layer().append("\x88\x01*"); - try - { - w.close(ws, {}); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS( - se.code() == error::failed, - se.code().message()); - } - } - - // close with incomplete read message - doTest(pmd, [&](ws_type& ws) - { - ws.next_layer().append("\x81\x02**"); - static_buffer<1> b; - w.read_some(ws, 1, b); - w.close(ws, {}); - }); - } - - void - testClose() - { - doTestClose(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestClose(AsyncClient{yield}); - }); - - // suspend on write - { - echo_server es{log}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - ios.run(); - BEAST_EXPECT(count == 2); - } - - // suspend on read - { - echo_server es{log}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - flat_buffer b; - std::size_t count = 0; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS( - ec == error::closed, ec.message()); - }); - BEAST_EXPECT(ws.rd_block_); - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - BEAST_EXPECT(ws.wr_close_); - ios.run(); - BEAST_EXPECT(count == 2); - } - } - - //-------------------------------------------------------------------------- - - template - void - doTestHandshake(Wrap const& w) - { - class req_decorator - { - bool& b_; - - public: - req_decorator(req_decorator const&) = default; - - explicit - req_decorator(bool& b) - : b_(b) - { - } - - void - operator()(request_type&) const - { - b_ = true; - } - }; - - // handshake - doTestLoop([&](test::stream& ts) - { - echo_server es{log}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - try - { - w.handshake(ws, "localhost", "/"); - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - - // handshake, response - doTestLoop([&](test::stream& ts) - { - echo_server es{log}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - response_type res; - try - { - w.handshake(ws, res, "localhost", "/"); - // VFALCO validate res? - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - - // handshake, decorator - doTestLoop([&](test::stream& ts) - { - echo_server es{log}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - bool called = false; - try - { - w.handshake_ex(ws, "localhost", "/", - req_decorator{called}); - BEAST_EXPECT(called); - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - - // handshake, response, decorator - doTestLoop([&](test::stream& ts) - { - echo_server es{log}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - bool called = false; - response_type res; - try - { - w.handshake_ex(ws, res, "localhost", "/", - req_decorator{called}); - // VFALCO validate res? - BEAST_EXPECT(called); - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - } - - void - testHandshake() - { - doTestHandshake(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestHandshake(AsyncClient{yield}); - }); - - auto const check = - [&](std::string const& s) - { - stream ws{ios_}; - auto tr = connect(ws.next_layer()); - ws.next_layer().append(s); - tr.close(); - try - { - ws.handshake("localhost:80", "/"); - fail(); - } - catch(system_error const& se) - { - BEAST_EXPECT(se.code() == error::handshake_failed); - } - }; - // wrong HTTP version - check( - "HTTP/1.0 101 Switching Protocols\r\n" - "Server: beast\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // wrong status - check( - "HTTP/1.1 200 OK\r\n" - "Server: beast\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing upgrade token - check( - "HTTP/1.1 101 Switching Protocols\r\n" - "Server: beast\r\n" - "Upgrade: HTTP/2\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing connection token - check( - "HTTP/1.1 101 Switching Protocols\r\n" - "Server: beast\r\n" - "Upgrade: WebSocket\r\n" - "Connection: keep-alive\r\n" - "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // missing accept key - check( - "HTTP/1.1 101 Switching Protocols\r\n" - "Server: beast\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - // wrong accept key - check( - "HTTP/1.1 101 Switching Protocols\r\n" - "Server: beast\r\n" - "Upgrade: WebSocket\r\n" - "Connection: upgrade\r\n" - "Sec-WebSocket-Accept: *\r\n" - "Sec-WebSocket-Version: 13\r\n" - "\r\n" - ); - } - - //-------------------------------------------------------------------------- - // - // Ping - // - //-------------------------------------------------------------------------- - - template - void - doTestPing(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - - // ping - doTest(pmd, [&](ws_type& ws) - { - w.ping(ws, {}); - }); - - // pong - doTest(pmd, [&](ws_type& ws) - { - w.pong(ws, {}); - }); - } - - void - testPing() - { - doTestPing(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestPing(AsyncClient{yield}); - }); - - // ping, already closed - { - stream ws{ios_}; - error_code ec; - ws.ping({}, ec); - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - } - - // async_ping, already closed - { - boost::asio::io_service ios; - stream ws{ios}; - ws.async_ping({}, - [&](error_code ec) - { - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - ios.run(); - } - - // pong, already closed - { - stream ws{ios_}; - error_code ec; - ws.pong({}, ec); - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - } - - // async_pong, already closed - { - boost::asio::io_service ios; - stream ws{ios}; - ws.async_pong({}, - [&](error_code ec) - { - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - ios.run(); - } - - // suspend on write - { - echo_server es{log}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - ws.async_write(sbuf("*"), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - ws.async_close({}, [&](error_code){}); - ios.run(); - BEAST_EXPECT(count == 2); - } - } - - //-------------------------------------------------------------------------- - // - // Read - // - //-------------------------------------------------------------------------- - - static - std::string const& - random_string() - { - static std::string const s = [] - { - std::size_t constexpr N = 16384; - std::mt19937 mt{1}; - std::string tmp; - tmp.reserve(N); - for(std::size_t i = 0; i < N; ++ i) - tmp.push_back(static_cast( - std::uniform_int_distribution< - unsigned>{0, 255}(mt))); - return tmp; - }(); - return s; - } - - template - void - doTestRead(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - - // Read close frames - { - // VFALCO What about asynchronous?? - - auto const check = - [&](error_code ev, string_view s) - { - echo_server es{log}; - stream ws{ios_}; - ws.next_layer().connect(es.stream()); - w.handshake(ws, "localhost", "/"); - ws.next_layer().append(s); - static_buffer<1> b; - error_code ec; - try - { - w.read(ws, b); - fail("", __FILE__, __LINE__); - } - catch(system_error const& se) - { - BEAST_EXPECTS(se.code() == ev, - se.code().message()); - } - ws.next_layer().close(); - }; - - // payload length 1 - check(error::failed, - "\x88\x01\x01"); - - // invalid close code 1005 - check(error::failed, - "\x88\x02\x03\xed"); - - // invalid utf8 - check(error::failed, - "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - - // good utf8 - check(error::closed, - "\x88\x06\xfc\x15utf8"); - } - - pmd.client_enable = true; - pmd.server_enable = true; - - // invalid inflate block - doTest(pmd, [&](ws_type& ws) - { - auto const& s = random_string(); - ws.binary(true); - ws.next_layer().append( - "\xc2\x40" + s.substr(0, 64)); - flat_buffer b; - try - { - w.read(ws, b); - } - catch(system_error const& se) - { - if(se.code() == test::error::fail_error) - throw; - BEAST_EXPECTS(se.code().category() == - zlib::detail::get_error_category(), - se.code().message()); - } - catch(...) - { - throw; - } - }); - } - - void - testRead() - { - doTestRead(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestRead(AsyncClient{yield}); - }); - - // Read close frames - { - auto const check = - [&](error_code ev, string_view s) - { - echo_server es{log}; - stream ws{ios_}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - ws.next_layer().append(s); - static_buffer<1> b; - error_code ec; - ws.read(b, ec); - BEAST_EXPECTS(ec == ev, ec.message()); - ws.next_layer().close(); - }; - - // payload length 1 - check(error::failed, - "\x88\x01\x01"); - - // invalid close code 1005 - check(error::failed, - "\x88\x02\x03\xed"); - - // invalid utf8 - check(error::failed, - "\x88\x06\xfc\x15\x0f\xd7\x73\x43"); - - // good utf8 - check(error::closed, - "\x88\x06\xfc\x15utf8"); - } - } - - //-------------------------------------------------------------------------- - // - // Write - // - //-------------------------------------------------------------------------- - - template - void - doTestWrite(Wrap const& w) - { - permessage_deflate pmd; - pmd.client_enable = false; - pmd.server_enable = false; - - // continuation - doTest(pmd, [&](ws_type& ws) - { - std::string const s = "Hello"; - std::size_t const chop = 3; - BOOST_ASSERT(chop < s.size()); - w.write_some(ws, false, - boost::asio::buffer(s.data(), chop)); - w.write_some(ws, true, boost::asio::buffer( - s.data() + chop, s.size() - chop)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // mask - doTest(pmd, [&](ws_type& ws) - { - ws.auto_fragment(false); - std::string const s = "Hello"; - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // mask (large) - doTest(pmd, [&](ws_type& ws) - { - ws.auto_fragment(false); - ws.write_buffer_size(16); - std::string const s(32, '*'); - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // mask, autofrag - doTest(pmd, [&](ws_type& ws) - { - ws.auto_fragment(true); - std::string const s(16384, '*'); - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // nomask - doTestLoop([&](test::stream& ts) - { - echo_server es{log, kind::async_client}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - try - { - es.async_handshake(); - w.accept(ws); - ws.auto_fragment(false); - std::string const s = "Hello"; - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - w.close(ws, {}); - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - - // nomask, autofrag - doTestLoop([&](test::stream& ts) - { - echo_server es{log, kind::async_client}; - ws_type ws{ts}; - ws.next_layer().connect(es.stream()); - try - { - es.async_handshake(); - w.accept(ws); - ws.auto_fragment(true); - std::string const s(16384, '*'); - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - w.close(ws, {}); - } - catch(...) - { - ts.close(); - throw; - } - ts.close(); - }); - - pmd.client_enable = true; - pmd.server_enable = true; - - // deflate - doTest(pmd, [&](ws_type& ws) - { - auto const& s = random_string(); - ws.binary(true); - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // deflate, continuation - doTest(pmd, [&](ws_type& ws) - { - std::string const s = "Hello"; - std::size_t const chop = 3; - BOOST_ASSERT(chop < s.size()); - // This call should produce no - // output due to compression latency. - w.write_some(ws, false, - boost::asio::buffer(s.data(), chop)); - w.write_some(ws, true, boost::asio::buffer( - s.data() + chop, s.size() - chop)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - - // deflate, no context takeover - pmd.client_no_context_takeover = true; - doTest(pmd, [&](ws_type& ws) - { - auto const& s = random_string(); - ws.binary(true); - w.write(ws, boost::asio::buffer(s)); - flat_buffer b; - w.read(ws, b); - BEAST_EXPECT(to_string(b.data()) == s); - }); - } - - void - testWrite() - { - doTestWrite(SyncClient{}); - - yield_to([&](yield_context yield) - { - doTestWrite(AsyncClient{yield}); - }); - - // already closed - { - stream ws{ios_}; - error_code ec; - ws.write(sbuf(""), ec); - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - } - - // async, already closed - { - boost::asio::io_service ios; - stream ws{ios}; - ws.async_write(sbuf(""), - [&](error_code ec) - { - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - ios.run(); - } - - // suspend on write - { - echo_server es{log}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_write(sbuf("*"), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS( - ec == boost::asio::error::operation_aborted, - ec.message()); - }); - ws.async_close({}, [&](error_code){}); - ios.run(); - BEAST_EXPECT(count == 2); - } - - // suspend on write, nomask, frag - { - echo_server es{log, kind::async_client}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - es.async_handshake(); - ws.accept(ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - std::string const s(16384, '*'); - ws.auto_fragment(true); - ws.async_write(boost::asio::buffer(s), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - ios.run(); - ios.reset(); - BEAST_EXPECT(count == 2); - flat_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(to_string(b.data()) == s); - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - }); - ios.run(); - BEAST_EXPECT(count == 4); - } - - // suspend on write, mask, frag - { - echo_server es{log, kind::async}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - std::string const s(16384, '*'); - ws.auto_fragment(true); - ws.async_write(boost::asio::buffer(s), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - ios.run(); - ios.reset(); - BEAST_EXPECT(count == 2); - flat_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(to_string(b.data()) == s); - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - }); - ios.run(); - BEAST_EXPECT(count == 4); - } - - // suspend on write, deflate - { - echo_server es{log, kind::async}; - error_code ec; - boost::asio::io_service ios; - stream ws{ios}; - { - permessage_deflate pmd; - pmd.client_enable = true; - ws.set_option(pmd); - } - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/", ec); - BEAST_EXPECTS(! ec, ec.message()); - std::size_t count = 0; - auto const& s = random_string(); - ws.binary(true); - ws.async_write(boost::asio::buffer(s), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - BEAST_EXPECT(ws.wr_block_); - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - ios.run(); - ios.reset(); - BEAST_EXPECT(count == 2); - flat_buffer b; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - BEAST_EXPECT(to_string(b.data()) == s); - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(! ec, ec.message()); - }); - }); - ios.run(); - BEAST_EXPECT(count == 4); - } - } - - //-------------------------------------------------------------------------- - void testOptions() { - stream ws(ios_); + stream ws(ios_); ws.auto_fragment(true); ws.write_buffer_size(2048); ws.binary(false); @@ -2526,246 +38,6 @@ public: } } - void - testPausation1() - { - for(int i = 0; i < 2; ++i ) - { - echo_server es{log, i==1 ? - kind::async : kind::sync}; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - - // Make remote send a text message with bad utf8. - ws.binary(true); - put(ws.next_layer().buffer(), cbuf( - 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); - multi_buffer b; - std::size_t count = 0; - // Read text message with bad utf8. - // Causes a close to be sent, blocking writes. - ws.async_read(b, - [&](error_code ec, std::size_t) - { - // Read should fail with protocol error - ++count; - BEAST_EXPECTS( - ec == error::failed, ec.message()); - // Reads after failure are aborted - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - }); - }); - // Run until the read_op writes a close frame. - while(! ws.wr_block_) - ios.run_one(); - // Write a text message, leaving - // the write_op suspended as a pausation. - ws.async_write(sbuf("Hello"), - [&](error_code ec) - { - ++count; - // Send is canceled because close received. - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - // Writes after close are aborted. - ws.async_write(sbuf("World"), - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - }); - }); - // Run until all completions are delivered. - while(! ios.stopped()) - ios.run_one(); - BEAST_EXPECT(count == 4); - } - } - - void - testPausation2() - { - echo_server es{log, kind::async}; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - - // Cause close to be received - es.async_close(); - - multi_buffer b; - std::size_t count = 0; - // Read a close frame. - // Sends a close frame, blocking writes. - ws.async_read(b, - [&](error_code ec, std::size_t) - { - // Read should complete with error::closed - ++count; - BEAST_EXPECTS(ec == error::closed, - ec.message()); - // Pings after a close are aborted - ws.async_ping("", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - }); - }); - if(! BEAST_EXPECT(run_until(ios, 100, - [&]{ return ws.wr_close_; }))) - return; - // Try to ping - ws.async_ping("payload", - [&](error_code ec) - { - // Pings after a close are aborted - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - // Subsequent calls to close are aborted - ws.async_close({}, - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - }); - }); - static std::size_t constexpr limit = 100; - std::size_t n; - for(n = 0; n < limit; ++n) - { - if(count >= 4) - break; - ios.run_one(); - } - BEAST_EXPECT(n < limit); - ios.run(); - } - - void - testPausation3() - { - echo_server es{log, kind::async}; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - ws.handshake("localhost", "/"); - - // Cause close to be received - es.async_close(); - - multi_buffer b; - std::size_t count = 0; - ws.async_read(b, - [&](error_code ec, std::size_t) - { - ++count; - BEAST_EXPECTS(ec == error::closed, - ec.message()); - }); - while(! ws.wr_block_) - ios.run_one(); - // try to close - ws.async_close("payload", - [&](error_code ec) - { - ++count; - BEAST_EXPECTS(ec == boost::asio:: - error::operation_aborted, - ec.message()); - }); - static std::size_t constexpr limit = 100; - std::size_t n; - for(n = 0; n < limit; ++n) - { - if(count >= 2) - break; - ios.run_one(); - } - BEAST_EXPECT(n < limit); - ios.run(); - } - - /* - https://github.com/boostorg/beast/issues/300 - - Write a message as two individual frames - */ - void - testIssue300() - { - for(int i = 0; i < 2; ++i ) - { - echo_server es{log, i==1 ? - kind::async : kind::sync}; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - - error_code ec; - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - ws.write_some(false, sbuf("u")); - ws.write_some(true, sbuf("v")); - multi_buffer b; - ws.read(b, ec); - BEAST_EXPECTS(! ec, ec.message()); - } - } - - void - testAsyncWriteFrame() - { - for(int i = 0; i < 2; ++i) - { - for(;;) - { - echo_server es{log, i==1 ? - kind::async : kind::sync}; - boost::asio::io_service ios; - stream ws{ios}; - ws.next_layer().connect(es.stream()); - - error_code ec; - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - break; - ws.async_write_some(false, - boost::asio::null_buffers{}, - [&](error_code) - { - fail(); - }); - if(! BEAST_EXPECTS(! ec, ec.message())) - break; - // - // Destruction of the io_service will cause destruction - // of the write_some_op without invoking the final handler. - // - break; - } - } - } - //-------------------------------------------------------------------------- template @@ -3009,39 +281,27 @@ public: run() override { BOOST_STATIC_ASSERT(std::is_constructible< - stream, boost::asio::io_service&>::value); + stream, boost::asio::io_service&>::value); BOOST_STATIC_ASSERT(std::is_move_constructible< - stream>::value); + stream>::value); BOOST_STATIC_ASSERT(std::is_move_assignable< - stream>::value); + stream>::value); BOOST_STATIC_ASSERT(std::is_constructible< - stream, socket_type&>::value); + stream, test::stream&>::value); BOOST_STATIC_ASSERT(std::is_move_constructible< - stream>::value); + stream>::value); BOOST_STATIC_ASSERT(! std::is_move_assignable< - stream>::value); + stream>::value); log << "sizeof(websocket::stream) == " << sizeof(websocket::stream) << std::endl; - testAccept(); - testClose(); - testHandshake(); - testPing(); - testRead(); - testWrite(); - testOptions(); - testPausation1(); - testPausation2(); - testPausation3(); - testIssue300(); - testAsyncWriteFrame(); auto const testStream = [this](permessage_deflate const& pmd) diff --git a/test/beast/websocket/test.hpp b/test/beast/websocket/test.hpp new file mode 100644 index 00000000..d7700aa9 --- /dev/null +++ b/test/beast/websocket/test.hpp @@ -0,0 +1,947 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BEAST_TEST_WEBSOCKET_TEST_HPP +#define BEAST_TEST_WEBSOCKET_TEST_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast { +namespace websocket { + +class websocket_test_suite + : public beast::unit_test::suite + , public test::enable_yield_to +{ +public: + using ws_type = + websocket::stream; + + enum class kind + { + sync, + async, + async_client + }; + + class echo_server + { + enum + { + buf_size = 20000 + }; + + std::ostream& log_; + boost::asio::io_service ios_; + boost::optional< + boost::asio::io_service::work> work_; + static_buffer buffer_; + test::stream ts_; + std::thread t_; + websocket::stream ws_; + + public: + explicit + echo_server( + std::ostream& log, + kind k = kind::sync) + : log_(log) + , work_(ios_) + , ts_(ios_) + , ws_(ts_) + { + permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + ws_.set_option(pmd); + + switch(k) + { + case kind::sync: + t_ = std::thread{[&]{ do_sync(); }}; + break; + + case kind::async: + t_ = std::thread{[&]{ ios_.run(); }}; + do_accept(); + break; + + case kind::async_client: + t_ = std::thread{[&]{ ios_.run(); }}; + break; + } + } + + ~echo_server() + { + work_ = boost::none; + t_.join(); + } + + test::stream& + stream() + { + return ts_; + } + + void + async_handshake() + { + ws_.async_handshake("localhost", "/", + std::bind( + &echo_server::on_handshake, + this, + std::placeholders::_1)); + } + + void + async_close() + { + ios_.post( + [&] + { + ws_.async_close({}, + std::bind( + &echo_server::on_close, + this, + std::placeholders::_1)); + }); + } + + private: + void + do_sync() + { + try + { + ws_.accept(); + for(;;) + { + ws_.read(buffer_); + ws_.text(ws_.got_text()); + ws_.write(buffer_.data()); + buffer_.consume(buffer_.size()); + } + } + catch(system_error const& se) + { + boost::ignore_unused(se); + #if 0 + if( se.code() != error::closed && + se.code() != error::failed && + se.code() != boost::asio::error::eof) + log_ << "echo_server: " << se.code().message() << std::endl; + #endif + } + catch(std::exception const& e) + { + log_ << "echo_server: " << e.what() << std::endl; + } + } + + void + do_accept() + { + ws_.async_accept(std::bind( + &echo_server::on_accept, + this, + std::placeholders::_1)); + } + + void + on_handshake(error_code ec) + { + if(ec) + return fail(ec); + do_read(); + } + + void + on_accept(error_code ec) + { + if(ec) + return fail(ec); + do_read(); + } + + void + do_read() + { + ws_.async_read(buffer_, + std::bind( + &echo_server::on_read, + this, + std::placeholders::_1)); + } + + void + on_read(error_code ec) + { + if(ec) + return fail(ec); + ws_.text(ws_.got_text()); + ws_.async_write(buffer_.data(), + std::bind( + &echo_server::on_write, + this, + std::placeholders::_1)); + } + + void + on_write(error_code ec) + { + if(ec) + return fail(ec); + buffer_.consume(buffer_.size()); + do_read(); + } + + void + on_close(error_code ec) + { + if(ec) + return fail(ec); + } + + void + fail(error_code ec) + { + boost::ignore_unused(ec); + #if 0 + if( ec != error::closed && + ec != error::failed && + ec != boost::asio::error::eof) + log_ << + "echo_server_async: " << + ec.message() << + std::endl; + #endif + } + }; + + template + void + doCloseTest( + Wrap const& w, + ws_type& ws, + close_code code) + { + try + { + multi_buffer b; + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + if(se.code() != error::closed) + throw; + BEAST_EXPECT( + ws.reason().code == code); + } + } + + template + void + doFailTest( + Wrap const& w, + ws_type& ws, + error_code ev) + { + try + { + multi_buffer b; + w.read(ws, b); + fail("", __FILE__, __LINE__); + } + catch(system_error const& se) + { + if(se.code() != ev) + throw; + } + } + + template + void + doTestLoop(Test const& f) + { + // This number has to be high for the + // test that writes the large buffer. + static std::size_t constexpr limit = 1000; + + std::size_t n; + for(n = 0; n <= limit; ++n) + { + test::fail_counter fc{n}; + test::stream ts{ios_, fc}; + try + { + f(ts); + ts.close(); + break; + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == test::error::fail_error, + se.code().message()); + } + catch(std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); + } + ts.close(); + continue; + } + BEAST_EXPECT(n < limit); + } + + template + void + doTest( + permessage_deflate const& pmd, + Test const& f) + { + // This number has to be high for the + // test that writes the large buffer. + static std::size_t constexpr limit = 1000; + + for(int i = 0; i < 2; ++i) + { + std::size_t n; + for(n = 0; n <= limit; ++n) + { + test::fail_counter fc{n}; + test::stream ts{ios_, fc}; + ws_type ws{ts}; + ws.set_option(pmd); + + echo_server es{log, i==1 ? + kind::async : kind::sync}; + error_code ec; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + if(ec) + { + ts.close(); + if( ! BEAST_EXPECTS( + ec == test::error::fail_error, + ec.message())) + BOOST_THROW_EXCEPTION(system_error{ec}); + continue; + } + try + { + f(ws); + ts.close(); + break; + } + catch(system_error const& se) + { + BEAST_EXPECTS( + se.code() == test::error::fail_error, + se.code().message()); + } + catch(std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); + } + ts.close(); + continue; + } + BEAST_EXPECT(n < limit); + } + } + + //-------------------------------------------------------------------------- + + template + std::string + to_string(ConstBufferSequence const& bs) + { + using boost::asio::buffer_cast; + using boost::asio::buffer_size; + std::string s; + s.reserve(buffer_size(bs)); + for(boost::asio::const_buffer b : bs) + s.append(buffer_cast(b), + buffer_size(b)); + return s; + } + + template + class cbuf_helper + { + std::array v_; + boost::asio::const_buffer cb_; + + public: + using value_type = decltype(cb_); + using const_iterator = value_type const*; + + template + explicit + cbuf_helper(Vn... vn) + : v_({{ static_cast(vn)... }}) + , cb_(v_.data(), v_.size()) + { + } + + const_iterator + begin() const + { + return &cb_; + } + + const_iterator + end() const + { + return begin()+1; + } + }; + + template + cbuf_helper + cbuf(Vn... vn) + { + return cbuf_helper(vn...); + } + + template + static + boost::asio::const_buffers_1 + sbuf(const char (&s)[N]) + { + return boost::asio::const_buffers_1(&s[0], N-1); + } + + template< + class DynamicBuffer, + class ConstBufferSequence> + void + put( + DynamicBuffer& buffer, + ConstBufferSequence const& buffers) + { + using boost::asio::buffer_copy; + using boost::asio::buffer_size; + buffer.commit(buffer_copy( + buffer.prepare(buffer_size(buffers)), + buffers)); + } + + template + bool + run_until(boost::asio::io_service& ios, + std::size_t limit, Pred&& pred) + { + for(std::size_t i = 0; i < limit; ++i) + { + if(pred()) + return true; + ios.run_one(); + } + return false; + } + + inline + std::string const& + random_string() + { + static std::string const s = [] + { + std::size_t constexpr N = 16384; + std::mt19937 mt{1}; + std::string tmp; + tmp.reserve(N); + for(std::size_t i = 0; i < N; ++ i) + tmp.push_back(static_cast( + std::uniform_int_distribution< + unsigned>{0, 255}(mt))); + return tmp; + }(); + return s; + } + + //-------------------------------------------------------------------------- + + struct SyncClient + { + template + void + accept(stream& ws) const + { + ws.accept(); + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const + { + ws.accept(buffers); + } + + template + void + accept(stream& ws, + http::request const& req) const + { + ws.accept(req); + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + ws.accept_ex(d); + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(buffers, d); + } + + template + void + accept_ex(stream& ws, + http::request const& req, + Decorator const& d) const + { + ws.accept_ex(req, d); + } + + template + void + accept_ex(stream& ws, + http::request const& req, + Buffers const& buffers, + Decorator const& d) const + { + ws.accept_ex(req, buffers, d); + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + ws.handshake(uri, path); + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + ws.handshake(res, uri, path); + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(uri, path, d); + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const& d) const + { + ws.handshake_ex(res, uri, path, d); + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + ws.ping(payload); + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + ws.pong(payload); + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + ws.close(cr); + } + + template< + class NextLayer, class DynamicBuffer> + std::size_t + read(stream& ws, + DynamicBuffer& buffer) const + { + return ws.read(buffer); + } + + template< + class NextLayer, class DynamicBuffer> + std::size_t + read_some(stream& ws, + std::size_t limit, + DynamicBuffer& buffer) const + { + return ws.read_some(buffer, limit); + } + + template< + class NextLayer, class MutableBufferSequence> + std::size_t + read_some(stream& ws, + MutableBufferSequence const& buffers) const + { + return ws.read_some(buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + ws.write(buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_some(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + ws.write_some(fin, buffers); + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + boost::asio::write( + ws.next_layer(), buffers); + } + }; + + //-------------------------------------------------------------------------- + + class AsyncClient + { + boost::asio::yield_context& yield_; + + public: + explicit + AsyncClient(boost::asio::yield_context& yield) + : yield_(yield) + { + } + + template + void + accept(stream& ws) const + { + error_code ec; + ws.async_accept(yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept(stream& ws, + Buffers const& buffers) const + { + error_code ec; + ws.async_accept(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept(stream& ws, + http::request const& req) const + { + error_code ec; + ws.async_accept(req, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + typename std::enable_if< + ! http::detail::is_header::value>::type + accept_ex(stream& ws, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::request const& req, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex(req, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + accept_ex(stream& ws, + http::request const& req, + Buffers const& buffers, + Decorator const& d) const + { + error_code ec; + ws.async_accept_ex( + req, buffers, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake(stream& ws, + response_type& res, + string_view uri, + string_view path) const + { + error_code ec; + ws.async_handshake( + res, uri, path, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + handshake_ex(stream& ws, + response_type& res, + string_view uri, + string_view path, + Decorator const &d) const + { + error_code ec; + ws.async_handshake_ex( + res, uri, path, d, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + ping(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_ping(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + pong(stream& ws, + ping_data const& payload) const + { + error_code ec; + ws.async_pong(payload, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template + void + close(stream& ws, + close_reason const& cr) const + { + error_code ec; + ws.async_close(cr, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class DynamicBuffer> + std::size_t + read(stream& ws, + DynamicBuffer& buffer) const + { + error_code ec; + auto const bytes_written = + ws.async_read(buffer, yield_[ec]); + if(ec) + throw system_error{ec}; + return bytes_written; + } + + template< + class NextLayer, class DynamicBuffer> + std::size_t + read_some(stream& ws, + std::size_t limit, + DynamicBuffer& buffer) const + { + error_code ec; + auto const bytes_written = + ws.async_read_some(buffer, limit, yield_[ec]); + if(ec) + throw system_error{ec}; + return bytes_written; + } + + template< + class NextLayer, class MutableBufferSequence> + std::size_t + read_some(stream& ws, + MutableBufferSequence const& buffers) const + { + error_code ec; + auto const bytes_written = + ws.async_read_some(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + return bytes_written; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write(buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_some(stream& ws, bool fin, + ConstBufferSequence const& buffers) const + { + error_code ec; + ws.async_write_some(fin, buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + + template< + class NextLayer, class ConstBufferSequence> + void + write_raw(stream& ws, + ConstBufferSequence const& buffers) const + { + error_code ec; + boost::asio::async_write( + ws.next_layer(), buffers, yield_[ec]); + if(ec) + throw system_error{ec}; + } + }; +}; + +} // websocket +} // beast +} // boost + +#endif diff --git a/test/beast/websocket/websocket_async_echo_server.hpp b/test/beast/websocket/websocket_async_echo_server.hpp deleted file mode 100644 index 80fc6660..00000000 --- a/test/beast/websocket/websocket_async_echo_server.hpp +++ /dev/null @@ -1,411 +0,0 @@ -// -// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP -#define BOOST_BEAST_WEBSOCKET_ASYNC_ECHO_SERVER_HPP - -#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 = boost::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: - /** 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()( - boost::beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(boost::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()(boost::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(boost::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_) - { - 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, - std::placeholders::_1)); - } - -private: - class peer - { - struct data - { - async_echo_server& server; - endpoint_type ep; - int state = 0; - boost::beast::websocket::stream ws; - boost::asio::io_service::strand strand; - boost::beast::multi_buffer 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_ex( - [](boost::beast::websocket::response_type& res) - { - res.insert( - "Server", "async_echo_server"); - }, - std::move(*this)); - } - - template - static - bool - match(DynamicBuffer& db, char const(&s)[N]) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - if(db.size() < N-1) - return false; - boost::beast::static_string t; - t.resize(N-1); - buffer_copy(buffer(t.data(), t.size()), - db.data()); - if(t != s) - return false; - db.consume(N-1); - return true; - } - - 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.db, - d.strand.wrap(std::move(*this))); - return; - - // got message - case 2: - if(ec == boost::beast::websocket::error::closed) - return; - if(ec) - return fail("async_read", ec); - if(match(d.db, "RAW")) - { - d.state = 1; - boost::asio::async_write(d.ws.next_layer(), - d.db.data(), d.strand.wrap(std::move(*this))); - return; - } - else if(match(d.db, "TEXT")) - { - d.state = 1; - d.ws.binary(false); - d.ws.async_write( - d.db.data(), d.strand.wrap(std::move(*this))); - return; - } - else if(match(d.db, "PING")) - { - boost::beast::websocket::ping_data payload; - d.db.consume(buffer_copy( - buffer(payload.data(), payload.size()), - d.db.data())); - d.state = 1; - d.ws.async_ping(payload, - d.strand.wrap(std::move(*this))); - return; - } - else if(match(d.db, "CLOSE")) - { - d.state = 1; - d.ws.async_close({}, - d.strand.wrap(std::move(*this))); - return; - } - // write message - d.state = 1; - d.ws.binary(d.ws.got_binary()); - 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 != boost::beast::websocket::error::closed) - d.server.fail("[#" + std::to_string(d.id) + - " " + d.ep.address().to_string() + ":" + - std::to_string(d.ep.port()) + "] " + 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, - std::placeholders::_1)); - } -}; - -} // websocket - -#endif diff --git a/test/beast/websocket/websocket_sync_echo_server.hpp b/test/beast/websocket/websocket_sync_echo_server.hpp deleted file mode 100644 index 069f74bd..00000000 --- a/test/beast/websocket/websocket_sync_echo_server.hpp +++ /dev/null @@ -1,354 +0,0 @@ -// -// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP -#define BOOST_BEAST_WEBSOCKET_SYNC_ECHO_SERVER_HPP - -#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 = boost::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: - /** 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()( - boost::beast::websocket::stream&) = 0; - }; - - template - class callable_impl : public callable - { - T t_; - - public: - template - callable_impl(U&& u) - : t_(std::forward(u)) - { - } - - void - operator()(boost::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()(boost::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(boost::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_) - { - } - - /** 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, - std::placeholders::_1)); - 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, - std::size_t id, endpoint_type const& ep) - { - if(log_) - if(ec != boost::beast::websocket::error::closed) - fail("[#" + std::to_string(id) + " " + - ep.address().to_string() + ":" + - std::to_string(ep.port()) + "] " + 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, - std::placeholders::_1)); - } - - template - static - bool - match(DynamicBuffer& db, char const(&s)[N]) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - if(db.size() < N-1) - return false; - boost::beast::static_string t; - t.resize(N-1); - buffer_copy(buffer(t.data(), t.size()), - db.data()); - if(t != s) - return false; - db.consume(N-1); - return true; - } - - void - do_peer(std::size_t id, - endpoint_type const& ep, socket_type&& sock) - { - using boost::asio::buffer; - using boost::asio::buffer_copy; - boost::beast::websocket::stream< - socket_type> ws{std::move(sock)}; - opts_.set_options(ws); - error_code ec; - ws.accept_ex( - [](boost::beast::websocket::response_type& res) - { - res.insert( - "Server", "sync_echo_server"); - }, - ec); - if(ec) - { - fail("accept", ec, id, ep); - return; - } - for(;;) - { - boost::beast::multi_buffer b; - ws.read(b, ec); - if(ec) - { - auto const s = ec.message(); - break; - } - ws.binary(ws.got_binary()); - if(match(b, "RAW")) - { - boost::asio::write( - ws.next_layer(), b.data(), ec); - } - else if(match(b, "TEXT")) - { - ws.binary(false); - ws.write(b.data(), ec); - } - else if(match(b, "PING")) - { - boost::beast::websocket::ping_data payload; - b.consume(buffer_copy( - buffer(payload.data(), payload.size()), - b.data())); - ws.ping(payload, ec); - } - else if(match(b, "CLOSE")) - { - ws.close({}, ec); - } - else - { - ws.write(b.data(), ec); - } - if(ec) - break; - } - if(ec && ec != boost::beast::websocket::error::closed) - { - fail("read", ec, id, ep); - } - } -}; - -} // websocket - -#endif diff --git a/test/beast/websocket/write.cpp b/test/beast/websocket/write.cpp new file mode 100644 index 00000000..67bc9d69 --- /dev/null +++ b/test/beast/websocket/write.cpp @@ -0,0 +1,527 @@ +// +// Copyright (w) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test.hpp" + +namespace boost { +namespace beast { +namespace websocket { + +class stream_write_test : public websocket_test_suite +{ +public: + template + void + doTestWrite(Wrap const& w) + { + permessage_deflate pmd; + pmd.client_enable = false; + pmd.server_enable = false; + + // continuation + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "Hello"; + std::size_t const chop = 3; + BOOST_ASSERT(chop < s.size()); + w.write_some(ws, false, + boost::asio::buffer(s.data(), chop)); + w.write_some(ws, true, boost::asio::buffer( + s.data() + chop, s.size() - chop)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // mask + doTest(pmd, [&](ws_type& ws) + { + ws.auto_fragment(false); + std::string const s = "Hello"; + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // mask (large) + doTest(pmd, [&](ws_type& ws) + { + ws.auto_fragment(false); + ws.write_buffer_size(16); + std::string const s(32, '*'); + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // mask, autofrag + doTest(pmd, [&](ws_type& ws) + { + ws.auto_fragment(true); + std::string const s(16384, '*'); + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // nomask + doTestLoop([&](test::stream& ts) + { + echo_server es{log, kind::async_client}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + try + { + es.async_handshake(); + w.accept(ws); + ws.auto_fragment(false); + std::string const s = "Hello"; + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + w.close(ws, {}); + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + + // nomask, autofrag + doTestLoop([&](test::stream& ts) + { + echo_server es{log, kind::async_client}; + ws_type ws{ts}; + ws.next_layer().connect(es.stream()); + try + { + es.async_handshake(); + w.accept(ws); + ws.auto_fragment(true); + std::string const s(16384, '*'); + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + w.close(ws, {}); + } + catch(...) + { + ts.close(); + throw; + } + ts.close(); + }); + + pmd.client_enable = true; + pmd.server_enable = true; + + // deflate + doTest(pmd, [&](ws_type& ws) + { + auto const& s = random_string(); + ws.binary(true); + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // deflate, continuation + doTest(pmd, [&](ws_type& ws) + { + std::string const s = "Hello"; + std::size_t const chop = 3; + BOOST_ASSERT(chop < s.size()); + // This call should produce no + // output due to compression latency. + w.write_some(ws, false, + boost::asio::buffer(s.data(), chop)); + w.write_some(ws, true, boost::asio::buffer( + s.data() + chop, s.size() - chop)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + + // deflate, no context takeover + pmd.client_no_context_takeover = true; + doTest(pmd, [&](ws_type& ws) + { + auto const& s = random_string(); + ws.binary(true); + w.write(ws, boost::asio::buffer(s)); + flat_buffer b; + w.read(ws, b); + BEAST_EXPECT(to_string(b.data()) == s); + }); + } + + void + testWrite() + { + doTestWrite(SyncClient{}); + + yield_to([&](yield_context yield) + { + doTestWrite(AsyncClient{yield}); + }); + + // already closed + { + stream ws{ios_}; + error_code ec; + ws.write(sbuf(""), ec); + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + } + + // async, already closed + { + boost::asio::io_service ios; + stream ws{ios}; + ws.async_write(sbuf(""), + [&](error_code ec) + { + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ios.run(); + } + + // suspend on write + { + echo_server es{log}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_write(sbuf("*"), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS( + ec == boost::asio::error::operation_aborted, + ec.message()); + }); + ws.async_close({}, [&](error_code){}); + ios.run(); + BEAST_EXPECT(count == 2); + } + + // suspend on write, nomask, frag + { + echo_server es{log, kind::async_client}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + es.async_handshake(); + ws.accept(ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + std::string const s(16384, '*'); + ws.auto_fragment(true); + ws.async_write(boost::asio::buffer(s), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + ios.run(); + ios.reset(); + BEAST_EXPECT(count == 2); + flat_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(to_string(b.data()) == s); + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + }); + ios.run(); + BEAST_EXPECT(count == 4); + } + + // suspend on write, mask, frag + { + echo_server es{log, kind::async}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + std::string const s(16384, '*'); + ws.auto_fragment(true); + ws.async_write(boost::asio::buffer(s), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + ios.run(); + ios.reset(); + BEAST_EXPECT(count == 2); + flat_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(to_string(b.data()) == s); + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + }); + ios.run(); + BEAST_EXPECT(count == 4); + } + + // suspend on write, deflate + { + echo_server es{log, kind::async}; + error_code ec; + boost::asio::io_service ios; + stream ws{ios}; + { + permessage_deflate pmd; + pmd.client_enable = true; + ws.set_option(pmd); + } + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/", ec); + BEAST_EXPECTS(! ec, ec.message()); + std::size_t count = 0; + auto const& s = random_string(); + ws.binary(true); + ws.async_write(boost::asio::buffer(s), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + BEAST_EXPECT(ws.wr_block_); + ws.async_ping("", + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + ios.run(); + ios.reset(); + BEAST_EXPECT(count == 2); + flat_buffer b; + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + BEAST_EXPECT(to_string(b.data()) == s); + ws.async_close({}, + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(! ec, ec.message()); + }); + }); + ios.run(); + BEAST_EXPECT(count == 4); + } + } + + /* + https://github.com/boostorg/beast/issues/300 + + Write a message as two individual frames + */ + void + testIssue300() + { + for(int i = 0; i < 2; ++i ) + { + echo_server es{log, i==1 ? + kind::async : kind::sync}; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + + error_code ec; + ws.handshake("localhost", "/", ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + return; + ws.write_some(false, sbuf("u")); + ws.write_some(true, sbuf("v")); + multi_buffer b; + ws.read(b, ec); + BEAST_EXPECTS(! ec, ec.message()); + } + } + + void + testWriteSuspend() + { + for(int i = 0; i < 2; ++i ) + { + echo_server es{log, i==1 ? + kind::async : kind::sync}; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + ws.handshake("localhost", "/"); + + // Make remote send a text message with bad utf8. + ws.binary(true); + put(ws.next_layer().buffer(), cbuf( + 0x03, 0xea, 0xf0, 0x28, 0x8c, 0xbc)); + multi_buffer b; + std::size_t count = 0; + // Read text message with bad utf8. + // Causes a close to be sent, blocking writes. + ws.async_read(b, + [&](error_code ec, std::size_t) + { + // Read should fail with protocol error + ++count; + BEAST_EXPECTS( + ec == error::failed, ec.message()); + // Reads after failure are aborted + ws.async_read(b, + [&](error_code ec, std::size_t) + { + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + // Run until the read_op writes a close frame. + while(! ws.wr_block_) + ios.run_one(); + // Write a text message, leaving + // the write_op suspended as a pausation. + ws.async_write(sbuf("Hello"), + [&](error_code ec) + { + ++count; + // Send is canceled because close received. + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + // Writes after close are aborted. + ws.async_write(sbuf("World"), + [&](error_code ec) + { + ++count; + BEAST_EXPECTS(ec == boost::asio:: + error::operation_aborted, + ec.message()); + }); + }); + // Run until all completions are delivered. + while(! ios.stopped()) + ios.run_one(); + BEAST_EXPECT(count == 4); + } + } + + void + testAsyncWriteFrame() + { + for(int i = 0; i < 2; ++i) + { + for(;;) + { + echo_server es{log, i==1 ? + kind::async : kind::sync}; + boost::asio::io_service ios; + stream ws{ios}; + ws.next_layer().connect(es.stream()); + + error_code ec; + ws.handshake("localhost", "/", ec); + if(! BEAST_EXPECTS(! ec, ec.message())) + break; + ws.async_write_some(false, + boost::asio::null_buffers{}, + [&](error_code) + { + fail(); + }); + if(! BEAST_EXPECTS(! ec, ec.message())) + break; + // + // Destruction of the io_service will cause destruction + // of the write_some_op without invoking the final handler. + // + break; + } + } + } + + void + run() override + { + testWrite(); + testWriteSuspend(); + testIssue300(); + testAsyncWriteFrame(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,websocket,stream_write); + +} // websocket +} // beast +} // boost