websocket_client_async_ssl_system_executor.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 client, asynchronous, using system_executor
  12. //
  13. //------------------------------------------------------------------------------
  14. #include "example/common/root_certificates.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/system_executor.hpp>
  21. #include <cstdlib>
  22. #include <functional>
  23. #include <iostream>
  24. #include <memory>
  25. #include <string>
  26. namespace beast = boost::beast; // from <boost/beast.hpp>
  27. namespace http = beast::http; // from <boost/beast/http.hpp>
  28. namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
  29. namespace net = boost::asio; // from <boost/asio.hpp>
  30. namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
  31. using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
  32. //------------------------------------------------------------------------------
  33. // Report a failure
  34. void
  35. fail(beast::error_code ec, char const* what)
  36. {
  37. std::cerr << what << ": " << ec.message() << "\n";
  38. }
  39. // Sends a WebSocket message and prints the response
  40. class session : public std::enable_shared_from_this<session>
  41. {
  42. tcp::resolver resolver_;
  43. websocket::stream<
  44. beast::ssl_stream<beast::tcp_stream>> ws_;
  45. beast::flat_buffer buffer_;
  46. std::string host_;
  47. std::string text_;
  48. // Objects are constructed with a strand to
  49. // ensure that handlers do not execute concurrently.
  50. session(net::strand<net::system_executor> ex, ssl::context& ctx)
  51. : resolver_(ex)
  52. , ws_(ex, ctx)
  53. {
  54. }
  55. public:
  56. // Delegate construction to a prive constructor to be able to use
  57. // the same strand for both I/O objects.
  58. explicit
  59. session(ssl::context& ctx)
  60. : session(net::make_strand(net::system_executor{}), ctx)
  61. {
  62. }
  63. // Start the asynchronous operation
  64. void
  65. run(
  66. char const* host,
  67. char const* port,
  68. char const* text)
  69. {
  70. // Save these for later
  71. host_ = host;
  72. text_ = text;
  73. // Look up the domain name
  74. resolver_.async_resolve(
  75. host,
  76. port,
  77. beast::bind_front_handler(
  78. &session::on_resolve,
  79. shared_from_this()));
  80. }
  81. void
  82. on_resolve(
  83. beast::error_code ec,
  84. tcp::resolver::results_type results)
  85. {
  86. if(ec)
  87. return fail(ec, "resolve");
  88. // Set a timeout on the operation
  89. beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
  90. // Make the connection on the IP address we get from a lookup
  91. beast::get_lowest_layer(ws_).async_connect(
  92. results,
  93. beast::bind_front_handler(
  94. &session::on_connect,
  95. shared_from_this()));
  96. }
  97. void
  98. on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
  99. {
  100. if(ec)
  101. return fail(ec, "connect");
  102. // Set a timeout on the operation
  103. beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
  104. // Perform the SSL handshake
  105. ws_.next_layer().async_handshake(
  106. ssl::stream_base::client,
  107. beast::bind_front_handler(
  108. &session::on_ssl_handshake,
  109. shared_from_this()));
  110. }
  111. void
  112. on_ssl_handshake(beast::error_code ec)
  113. {
  114. if(ec)
  115. return fail(ec, "ssl_handshake");
  116. // Turn off the timeout on the tcp_stream, because
  117. // the websocket stream has its own timeout system.
  118. beast::get_lowest_layer(ws_).expires_never();
  119. // Set suggested timeout settings for the websocket
  120. ws_.set_option(
  121. websocket::stream_base::timeout::suggested(
  122. beast::role_type::client));
  123. // Set a decorator to change the User-Agent of the handshake
  124. ws_.set_option(websocket::stream_base::decorator(
  125. [](websocket::request_type& req)
  126. {
  127. req.set(http::field::user_agent,
  128. std::string(BOOST_BEAST_VERSION_STRING) +
  129. " websocket-client-async-ssl");
  130. }));
  131. // Perform the websocket handshake
  132. ws_.async_handshake(host_, "/",
  133. beast::bind_front_handler(
  134. &session::on_handshake,
  135. shared_from_this()));
  136. }
  137. void
  138. on_handshake(beast::error_code ec)
  139. {
  140. if(ec)
  141. return fail(ec, "handshake");
  142. // Send the message
  143. ws_.async_write(
  144. net::buffer(text_),
  145. beast::bind_front_handler(
  146. &session::on_write,
  147. shared_from_this()));
  148. }
  149. void
  150. on_write(
  151. beast::error_code ec,
  152. std::size_t bytes_transferred)
  153. {
  154. boost::ignore_unused(bytes_transferred);
  155. if(ec)
  156. return fail(ec, "write");
  157. // Read a message into our buffer
  158. ws_.async_read(
  159. buffer_,
  160. beast::bind_front_handler(
  161. &session::on_read,
  162. shared_from_this()));
  163. }
  164. void
  165. on_read(
  166. beast::error_code ec,
  167. std::size_t bytes_transferred)
  168. {
  169. boost::ignore_unused(bytes_transferred);
  170. if(ec)
  171. return fail(ec, "read");
  172. // Close the WebSocket connection
  173. ws_.async_close(websocket::close_code::normal,
  174. beast::bind_front_handler(
  175. &session::on_close,
  176. shared_from_this()));
  177. }
  178. void
  179. on_close(beast::error_code ec)
  180. {
  181. if(ec)
  182. return fail(ec, "close");
  183. // If we get here then the connection is closed gracefully
  184. // The make_printable() function helps print a ConstBufferSequence
  185. std::cout << beast::make_printable(buffer_.data()) << std::endl;
  186. }
  187. };
  188. //------------------------------------------------------------------------------
  189. int main(int argc, char** argv)
  190. {
  191. // Check command line arguments.
  192. if(argc != 4)
  193. {
  194. std::cerr <<
  195. "Usage: websocket-client-async-ssl <host> <port> <text>\n" <<
  196. "Example:\n" <<
  197. " websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n";
  198. return EXIT_FAILURE;
  199. }
  200. auto const host = argv[1];
  201. auto const port = argv[2];
  202. auto const text = argv[3];
  203. // The SSL context is required, and holds certificates
  204. ssl::context ctx{ssl::context::tlsv12_client};
  205. // This holds the root certificate used for verification
  206. load_root_certificates(ctx);
  207. // Launch the asynchronous operation
  208. std::make_shared<session>(ctx)->run(host, port, text);
  209. // The async operations will run on the system_executor.
  210. // Because the main thread has nothing to do in this example, we just wait
  211. // for the system_executor to run out of work.
  212. net::system_executor().context().join();
  213. return EXIT_SUCCESS;
  214. }