websocket_server_stackless_ssl.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. //
  2. // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. // Official repository: https://github.com/boostorg/beast
  8. //
  9. //------------------------------------------------------------------------------
  10. //
  11. // Example: WebSocket SSL server, stackless coroutine
  12. //
  13. //------------------------------------------------------------------------------
  14. #include "example/common/server_certificate.hpp"
  15. #include <boost/beast/core.hpp>
  16. #include <boost/beast/ssl.hpp>
  17. #include <boost/beast/websocket.hpp>
  18. #include <boost/beast/websocket/ssl.hpp>
  19. #include <boost/asio/coroutine.hpp>
  20. #include <boost/asio/strand.hpp>
  21. #include <boost/asio/dispatch.hpp>
  22. #include <algorithm>
  23. #include <cstdlib>
  24. #include <functional>
  25. #include <iostream>
  26. #include <memory>
  27. #include <string>
  28. #include <thread>
  29. #include <vector>
  30. namespace beast = boost::beast; // from <boost/beast.hpp>
  31. namespace http = beast::http; // from <boost/beast/http.hpp>
  32. namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
  33. namespace net = boost::asio; // from <boost/asio.hpp>
  34. namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
  35. using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
  36. //------------------------------------------------------------------------------
  37. // Report a failure
  38. void
  39. fail(beast::error_code ec, char const* what)
  40. {
  41. std::cerr << what << ": " << ec.message() << "\n";
  42. }
  43. // Echoes back all received WebSocket messages
  44. class session
  45. : public boost::asio::coroutine
  46. , public std::enable_shared_from_this<session>
  47. {
  48. websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_;
  49. beast::flat_buffer buffer_;
  50. public:
  51. // Take ownership of the socket
  52. session(tcp::socket&& socket, ssl::context& ctx)
  53. : ws_(std::move(socket), ctx)
  54. {
  55. }
  56. // Start the asynchronous operation
  57. void
  58. run()
  59. {
  60. // We need to be executing within a strand to perform async operations
  61. // on the I/O objects in this session. Although not strictly necessary
  62. // for single-threaded contexts, this example code is written to be
  63. // thread-safe by default.
  64. net::dispatch(ws_.get_executor(),
  65. beast::bind_front_handler(&session::loop,
  66. shared_from_this(),
  67. beast::error_code{},
  68. 0));
  69. }
  70. #include <boost/asio/yield.hpp>
  71. void
  72. loop(
  73. beast::error_code ec,
  74. std::size_t bytes_transferred)
  75. {
  76. boost::ignore_unused(bytes_transferred);
  77. reenter(*this)
  78. {
  79. // Set the timeout.
  80. beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
  81. // Perform the SSL handshake
  82. yield ws_.next_layer().async_handshake(
  83. ssl::stream_base::server,
  84. std::bind(
  85. &session::loop,
  86. shared_from_this(),
  87. std::placeholders::_1,
  88. 0));
  89. if(ec)
  90. return fail(ec, "handshake");
  91. // Turn off the timeout on the tcp_stream, because
  92. // the websocket stream has its own timeout system.
  93. beast::get_lowest_layer(ws_).expires_never();
  94. // Set suggested timeout settings for the websocket
  95. ws_.set_option(
  96. websocket::stream_base::timeout::suggested(
  97. beast::role_type::server));
  98. // Set a decorator to change the Server of the handshake
  99. ws_.set_option(websocket::stream_base::decorator(
  100. [](websocket::response_type& res)
  101. {
  102. res.set(http::field::server,
  103. std::string(BOOST_BEAST_VERSION_STRING) +
  104. " websocket-server-stackless-ssl");
  105. }));
  106. // Accept the websocket handshake
  107. yield ws_.async_accept(
  108. std::bind(
  109. &session::loop,
  110. shared_from_this(),
  111. std::placeholders::_1,
  112. 0));
  113. if(ec)
  114. return fail(ec, "accept");
  115. for(;;)
  116. {
  117. // Read a message into our buffer
  118. yield ws_.async_read(
  119. buffer_,
  120. std::bind(
  121. &session::loop,
  122. shared_from_this(),
  123. std::placeholders::_1,
  124. std::placeholders::_2));
  125. if(ec == websocket::error::closed)
  126. {
  127. // This indicates that the session was closed
  128. return;
  129. }
  130. if(ec)
  131. fail(ec, "read");
  132. // Echo the message
  133. ws_.text(ws_.got_text());
  134. yield ws_.async_write(
  135. buffer_.data(),
  136. std::bind(
  137. &session::loop,
  138. shared_from_this(),
  139. std::placeholders::_1,
  140. std::placeholders::_2));
  141. if(ec)
  142. return fail(ec, "write");
  143. // Clear the buffer
  144. buffer_.consume(buffer_.size());
  145. }
  146. }
  147. }
  148. #include <boost/asio/unyield.hpp>
  149. };
  150. //------------------------------------------------------------------------------
  151. // Accepts incoming connections and launches the sessions
  152. class listener
  153. : public boost::asio::coroutine
  154. , public std::enable_shared_from_this<listener>
  155. {
  156. net::io_context& ioc_;
  157. ssl::context& ctx_;
  158. tcp::acceptor acceptor_;
  159. tcp::socket socket_;
  160. public:
  161. listener(
  162. net::io_context& ioc,
  163. ssl::context& ctx,
  164. tcp::endpoint endpoint)
  165. : ioc_(ioc)
  166. , ctx_(ctx)
  167. , acceptor_(ioc)
  168. , socket_(ioc)
  169. {
  170. beast::error_code ec;
  171. // Open the acceptor
  172. acceptor_.open(endpoint.protocol(), ec);
  173. if(ec)
  174. {
  175. fail(ec, "open");
  176. return;
  177. }
  178. // Allow address reuse
  179. acceptor_.set_option(net::socket_base::reuse_address(true), ec);
  180. if(ec)
  181. {
  182. fail(ec, "set_option");
  183. return;
  184. }
  185. // Bind to the server address
  186. acceptor_.bind(endpoint, ec);
  187. if(ec)
  188. {
  189. fail(ec, "bind");
  190. return;
  191. }
  192. // Start listening for connections
  193. acceptor_.listen(
  194. net::socket_base::max_listen_connections, ec);
  195. if(ec)
  196. {
  197. fail(ec, "listen");
  198. return;
  199. }
  200. }
  201. // Start accepting incoming connections
  202. void
  203. run()
  204. {
  205. loop();
  206. }
  207. private:
  208. #include <boost/asio/yield.hpp>
  209. void
  210. loop(beast::error_code ec = {})
  211. {
  212. reenter(*this)
  213. {
  214. for(;;)
  215. {
  216. yield acceptor_.async_accept(
  217. socket_,
  218. std::bind(
  219. &listener::loop,
  220. shared_from_this(),
  221. std::placeholders::_1));
  222. if(ec)
  223. {
  224. fail(ec, "accept");
  225. }
  226. else
  227. {
  228. // Create the session and run it
  229. std::make_shared<session>(std::move(socket_), ctx_)->run();
  230. }
  231. // Make sure each session gets its own strand
  232. socket_ = tcp::socket(net::make_strand(ioc_));
  233. }
  234. }
  235. }
  236. #include <boost/asio/unyield.hpp>
  237. };
  238. //------------------------------------------------------------------------------
  239. int main(int argc, char* argv[])
  240. {
  241. // Check command line arguments.
  242. if (argc != 4)
  243. {
  244. std::cerr <<
  245. "Usage: websocket-server-async-ssl <address> <port> <threads>\n" <<
  246. "Example:\n" <<
  247. " websocket-server-async-ssl 0.0.0.0 8080 1\n";
  248. return EXIT_FAILURE;
  249. }
  250. auto const address = net::ip::make_address(argv[1]);
  251. auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
  252. auto const threads = std::max<int>(1, std::atoi(argv[3]));
  253. // The io_context is required for all I/O
  254. net::io_context ioc{threads};
  255. // The SSL context is required, and holds certificates
  256. ssl::context ctx{ssl::context::tlsv12};
  257. // This holds the self-signed certificate used by the server
  258. load_server_certificate(ctx);
  259. // Create and launch a listening port
  260. std::make_shared<listener>(ioc, ctx, tcp::endpoint{address, port})->run();
  261. // Run the I/O service on the requested number of threads
  262. std::vector<std::thread> v;
  263. v.reserve(threads - 1);
  264. for(auto i = threads - 1; i > 0; --i)
  265. v.emplace_back(
  266. [&ioc]
  267. {
  268. ioc.run();
  269. });
  270. ioc.run();
  271. return EXIT_SUCCESS;
  272. }