websocket_server_async_ssl.cpp 8.2 KB

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