]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // |
2 | // Copyright (c) 2016-2017 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 | |
13 | // | |
14 | //------------------------------------------------------------------------------ | |
15 | ||
16 | #include "example/common/root_certificates.hpp" | |
17 | ||
18 | #include <boost/beast/core.hpp> | |
19 | #include <boost/beast/websocket.hpp> | |
20 | #include <boost/beast/websocket/ssl.hpp> | |
21 | #include <boost/asio/connect.hpp> | |
22 | #include <boost/asio/ip/tcp.hpp> | |
23 | #include <boost/asio/ssl/stream.hpp> | |
24 | #include <cstdlib> | |
25 | #include <functional> | |
26 | #include <iostream> | |
27 | #include <memory> | |
28 | #include <string> | |
29 | ||
30 | using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
31 | namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> | |
32 | namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp> | |
33 | ||
34 | //------------------------------------------------------------------------------ | |
35 | ||
36 | // Report a failure | |
37 | void | |
38 | fail(boost::system::error_code ec, char const* what) | |
39 | { | |
40 | std::cerr << what << ": " << ec.message() << "\n"; | |
41 | } | |
42 | ||
43 | // Sends a WebSocket message and prints the response | |
44 | class session : public std::enable_shared_from_this<session> | |
45 | { | |
46 | tcp::resolver resolver_; | |
47 | websocket::stream<ssl::stream<tcp::socket>> ws_; | |
48 | boost::beast::multi_buffer buffer_; | |
49 | std::string host_; | |
50 | std::string text_; | |
51 | ||
52 | public: | |
53 | // Resolver and socket require an io_context | |
54 | explicit | |
55 | session(boost::asio::io_context& ioc, ssl::context& ctx) | |
56 | : resolver_(ioc) | |
57 | , ws_(ioc, ctx) | |
58 | { | |
59 | } | |
60 | ||
61 | // Start the asynchronous operation | |
62 | void | |
63 | run( | |
64 | char const* host, | |
65 | char const* port, | |
66 | char const* text) | |
67 | { | |
68 | // Save these for later | |
69 | host_ = host; | |
70 | text_ = text; | |
71 | ||
72 | // Look up the domain name | |
73 | resolver_.async_resolve( | |
74 | host, | |
75 | port, | |
76 | std::bind( | |
77 | &session::on_resolve, | |
78 | shared_from_this(), | |
79 | std::placeholders::_1, | |
80 | std::placeholders::_2)); | |
81 | } | |
82 | ||
83 | void | |
84 | on_resolve( | |
85 | boost::system::error_code ec, | |
86 | tcp::resolver::results_type results) | |
87 | { | |
88 | if(ec) | |
89 | return fail(ec, "resolve"); | |
90 | ||
91 | // Make the connection on the IP address we get from a lookup | |
92 | boost::asio::async_connect( | |
93 | ws_.next_layer().next_layer(), | |
94 | results.begin(), | |
95 | results.end(), | |
96 | std::bind( | |
97 | &session::on_connect, | |
98 | shared_from_this(), | |
99 | std::placeholders::_1)); | |
100 | } | |
101 | ||
102 | void | |
103 | on_connect(boost::system::error_code ec) | |
104 | { | |
105 | if(ec) | |
106 | return fail(ec, "connect"); | |
107 | ||
108 | // Perform the SSL handshake | |
109 | ws_.next_layer().async_handshake( | |
110 | ssl::stream_base::client, | |
111 | std::bind( | |
112 | &session::on_ssl_handshake, | |
113 | shared_from_this(), | |
114 | std::placeholders::_1)); | |
115 | } | |
116 | ||
117 | void | |
118 | on_ssl_handshake(boost::system::error_code ec) | |
119 | { | |
120 | if(ec) | |
121 | return fail(ec, "ssl_handshake"); | |
122 | ||
123 | // Perform the websocket handshake | |
124 | ws_.async_handshake(host_, "/", | |
125 | std::bind( | |
126 | &session::on_handshake, | |
127 | shared_from_this(), | |
128 | std::placeholders::_1)); | |
129 | } | |
130 | ||
131 | void | |
132 | on_handshake(boost::system::error_code ec) | |
133 | { | |
134 | if(ec) | |
135 | return fail(ec, "handshake"); | |
136 | ||
137 | // Send the message | |
138 | ws_.async_write( | |
139 | boost::asio::buffer(text_), | |
140 | std::bind( | |
141 | &session::on_write, | |
142 | shared_from_this(), | |
143 | std::placeholders::_1, | |
144 | std::placeholders::_2)); | |
145 | } | |
146 | ||
147 | void | |
148 | on_write( | |
149 | boost::system::error_code ec, | |
150 | std::size_t bytes_transferred) | |
151 | { | |
152 | boost::ignore_unused(bytes_transferred); | |
153 | ||
154 | if(ec) | |
155 | return fail(ec, "write"); | |
156 | ||
157 | // Read a message into our buffer | |
158 | ws_.async_read( | |
159 | buffer_, | |
160 | std::bind( | |
161 | &session::on_read, | |
162 | shared_from_this(), | |
163 | std::placeholders::_1, | |
164 | std::placeholders::_2)); | |
165 | } | |
166 | ||
167 | void | |
168 | on_read( | |
169 | boost::system::error_code ec, | |
170 | std::size_t bytes_transferred) | |
171 | { | |
172 | boost::ignore_unused(bytes_transferred); | |
173 | ||
174 | if(ec) | |
175 | return fail(ec, "read"); | |
176 | ||
177 | // Close the WebSocket connection | |
178 | ws_.async_close(websocket::close_code::normal, | |
179 | std::bind( | |
180 | &session::on_close, | |
181 | shared_from_this(), | |
182 | std::placeholders::_1)); | |
183 | } | |
184 | ||
185 | void | |
186 | on_close(boost::system::error_code ec) | |
187 | { | |
188 | if(ec) | |
189 | return fail(ec, "close"); | |
190 | ||
191 | // If we get here then the connection is closed gracefully | |
192 | ||
193 | // The buffers() function helps print a ConstBufferSequence | |
194 | std::cout << boost::beast::buffers(buffer_.data()) << std::endl; | |
195 | } | |
196 | }; | |
197 | ||
198 | //------------------------------------------------------------------------------ | |
199 | ||
200 | int main(int argc, char** argv) | |
201 | { | |
202 | // Check command line arguments. | |
203 | if(argc != 4) | |
204 | { | |
205 | std::cerr << | |
206 | "Usage: websocket-client-async-ssl <host> <port> <text>\n" << | |
207 | "Example:\n" << | |
208 | " websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n"; | |
209 | return EXIT_FAILURE; | |
210 | } | |
211 | auto const host = argv[1]; | |
212 | auto const port = argv[2]; | |
213 | auto const text = argv[3]; | |
214 | ||
215 | // The io_context is required for all I/O | |
216 | boost::asio::io_context ioc; | |
217 | ||
218 | // The SSL context is required, and holds certificates | |
219 | ssl::context ctx{ssl::context::sslv23_client}; | |
220 | ||
221 | // This holds the root certificate used for verification | |
222 | load_root_certificates(ctx); | |
223 | ||
224 | // Launch the asynchronous operation | |
225 | std::make_shared<session>(ioc, ctx)->run(host, port, text); | |
226 | ||
227 | // Run the I/O service. The call will return when | |
11fdf7f2 | 228 | // the socket is closed. |
b32b8144 FG |
229 | ioc.run(); |
230 | ||
231 | return EXIT_SUCCESS; | |
232 | } |