websocket_client_async_ssl.cpp 6.9 KB

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