]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp
import quincy beta 17.1.0
[ceph.git] / ceph / src / boost / libs / beast / example / websocket / client / async-ssl-system-executor / websocket_client_async_ssl_system_executor.cpp
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 //
12 // Example: WebSocket SSL client, asynchronous, using system_executor
13 //
14 //------------------------------------------------------------------------------
15
16 #include "example/common/root_certificates.hpp"
17
18 #include <boost/beast/core.hpp>
19 #include <boost/beast/ssl.hpp>
20 #include <boost/beast/websocket.hpp>
21 #include <boost/beast/websocket/ssl.hpp>
22 #include <boost/asio/strand.hpp>
23 #include <boost/asio/system_executor.hpp>
24 #include <cstdlib>
25 #include <functional>
26 #include <iostream>
27 #include <memory>
28 #include <string>
29
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 //------------------------------------------------------------------------------
38
39 // Report a failure
40 void
41 fail(beast::error_code ec, char const* what)
42 {
43 std::cerr << what << ": " << ec.message() << "\n";
44 }
45
46 // Sends a WebSocket message and prints the response
47 class session : public std::enable_shared_from_this<session>
48 {
49 tcp::resolver resolver_;
50 websocket::stream<
51 beast::ssl_stream<beast::tcp_stream>> ws_;
52 beast::flat_buffer buffer_;
53 std::string host_;
54 std::string text_;
55
56 // Objects are constructed with a strand to
57 // ensure that handlers do not execute concurrently.
58 session(net::strand<net::system_executor> ex, ssl::context& ctx)
59 : resolver_(ex)
60 , ws_(ex, ctx)
61 {
62 }
63 public:
64 // Delegate construction to a prive constructor to be able to use
65 // the same strand for both I/O objects.
66 explicit
67 session(ssl::context& ctx)
68 : session(net::make_strand(net::system_executor{}), ctx)
69 {
70 }
71
72 // Start the asynchronous operation
73 void
74 run(
75 char const* host,
76 char const* port,
77 char const* text)
78 {
79 // Save these for later
80 host_ = host;
81 text_ = text;
82
83 // Look up the domain name
84 resolver_.async_resolve(
85 host,
86 port,
87 beast::bind_front_handler(
88 &session::on_resolve,
89 shared_from_this()));
90 }
91
92 void
93 on_resolve(
94 beast::error_code ec,
95 tcp::resolver::results_type results)
96 {
97 if(ec)
98 return fail(ec, "resolve");
99
100 // Set a timeout on the operation
101 beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
102
103 // Make the connection on the IP address we get from a lookup
104 beast::get_lowest_layer(ws_).async_connect(
105 results,
106 beast::bind_front_handler(
107 &session::on_connect,
108 shared_from_this()));
109 }
110
111 void
112 on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
113 {
114 if(ec)
115 return fail(ec, "connect");
116
117 // Update the host_ string. This will provide the value of the
118 // Host HTTP header during the WebSocket handshake.
119 // See https://tools.ietf.org/html/rfc7230#section-5.4
120 host_ += ':' + std::to_string(ep.port());
121
122 // Set a timeout on the operation
123 beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
124
125 // Set SNI Hostname (many hosts need this to handshake successfully)
126 if(! SSL_set_tlsext_host_name(
127 ws_.next_layer().native_handle(),
128 host_.c_str()))
129 {
130 ec = beast::error_code(static_cast<int>(::ERR_get_error()),
131 net::error::get_ssl_category());
132 return fail(ec, "connect");
133 }
134
135 // Perform the SSL handshake
136 ws_.next_layer().async_handshake(
137 ssl::stream_base::client,
138 beast::bind_front_handler(
139 &session::on_ssl_handshake,
140 shared_from_this()));
141 }
142
143 void
144 on_ssl_handshake(beast::error_code ec)
145 {
146 if(ec)
147 return fail(ec, "ssl_handshake");
148
149 // Turn off the timeout on the tcp_stream, because
150 // the websocket stream has its own timeout system.
151 beast::get_lowest_layer(ws_).expires_never();
152
153 // Set suggested timeout settings for the websocket
154 ws_.set_option(
155 websocket::stream_base::timeout::suggested(
156 beast::role_type::client));
157
158 // Set a decorator to change the User-Agent of the handshake
159 ws_.set_option(websocket::stream_base::decorator(
160 [](websocket::request_type& req)
161 {
162 req.set(http::field::user_agent,
163 std::string(BOOST_BEAST_VERSION_STRING) +
164 " websocket-client-async-ssl");
165 }));
166
167 // Perform the websocket handshake
168 ws_.async_handshake(host_, "/",
169 beast::bind_front_handler(
170 &session::on_handshake,
171 shared_from_this()));
172 }
173
174 void
175 on_handshake(beast::error_code ec)
176 {
177 if(ec)
178 return fail(ec, "handshake");
179
180 // Send the message
181 ws_.async_write(
182 net::buffer(text_),
183 beast::bind_front_handler(
184 &session::on_write,
185 shared_from_this()));
186 }
187
188 void
189 on_write(
190 beast::error_code ec,
191 std::size_t bytes_transferred)
192 {
193 boost::ignore_unused(bytes_transferred);
194
195 if(ec)
196 return fail(ec, "write");
197
198 // Read a message into our buffer
199 ws_.async_read(
200 buffer_,
201 beast::bind_front_handler(
202 &session::on_read,
203 shared_from_this()));
204 }
205
206 void
207 on_read(
208 beast::error_code ec,
209 std::size_t bytes_transferred)
210 {
211 boost::ignore_unused(bytes_transferred);
212
213 if(ec)
214 return fail(ec, "read");
215
216 // Close the WebSocket connection
217 ws_.async_close(websocket::close_code::normal,
218 beast::bind_front_handler(
219 &session::on_close,
220 shared_from_this()));
221 }
222
223 void
224 on_close(beast::error_code ec)
225 {
226 if(ec)
227 return fail(ec, "close");
228
229 // If we get here then the connection is closed gracefully
230
231 // The make_printable() function helps print a ConstBufferSequence
232 std::cout << beast::make_printable(buffer_.data()) << std::endl;
233 }
234 };
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-client-async-ssl <host> <port> <text>\n" <<
245 "Example:\n" <<
246 " websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n";
247 return EXIT_FAILURE;
248 }
249 auto const host = argv[1];
250 auto const port = argv[2];
251 auto const text = argv[3];
252
253
254 // The SSL context is required, and holds certificates
255 ssl::context ctx{ssl::context::tlsv12_client};
256
257 // This holds the root certificate used for verification
258 load_root_certificates(ctx);
259
260 // Launch the asynchronous operation
261 std::make_shared<session>(ctx)->run(host, port, text);
262
263 // The async operations will run on the system_executor.
264 // Because the main thread has nothing to do in this example, we just wait
265 // for the system_executor to run out of work.
266 net::query(net::system_executor(), net::execution::context).join();
267
268 return EXIT_SUCCESS;
269 }