]>
Commit | Line | Data |
---|---|---|
b32b8144 | 1 | // |
92f5a8d4 | 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) |
b32b8144 FG |
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 client, asynchronous | |
13 | // | |
14 | //------------------------------------------------------------------------------ | |
15 | ||
16 | #include <boost/beast/core.hpp> | |
17 | #include <boost/beast/websocket.hpp> | |
92f5a8d4 | 18 | #include <boost/asio/strand.hpp> |
b32b8144 FG |
19 | #include <cstdlib> |
20 | #include <functional> | |
21 | #include <iostream> | |
22 | #include <memory> | |
23 | #include <string> | |
24 | ||
92f5a8d4 TL |
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 | using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
b32b8144 FG |
30 | |
31 | //------------------------------------------------------------------------------ | |
32 | ||
33 | // Report a failure | |
34 | void | |
92f5a8d4 | 35 | fail(beast::error_code ec, char const* what) |
b32b8144 FG |
36 | { |
37 | std::cerr << what << ": " << ec.message() << "\n"; | |
38 | } | |
39 | ||
40 | // Sends a WebSocket message and prints the response | |
41 | class session : public std::enable_shared_from_this<session> | |
42 | { | |
43 | tcp::resolver resolver_; | |
92f5a8d4 TL |
44 | websocket::stream<beast::tcp_stream> ws_; |
45 | beast::flat_buffer buffer_; | |
b32b8144 FG |
46 | std::string host_; |
47 | std::string text_; | |
48 | ||
49 | public: | |
50 | // Resolver and socket require an io_context | |
51 | explicit | |
92f5a8d4 TL |
52 | session(net::io_context& ioc) |
53 | : resolver_(net::make_strand(ioc)) | |
54 | , ws_(net::make_strand(ioc)) | |
b32b8144 FG |
55 | { |
56 | } | |
57 | ||
58 | // Start the asynchronous operation | |
59 | void | |
60 | run( | |
61 | char const* host, | |
62 | char const* port, | |
63 | char const* text) | |
64 | { | |
65 | // Save these for later | |
66 | host_ = host; | |
67 | text_ = text; | |
68 | ||
69 | // Look up the domain name | |
70 | resolver_.async_resolve( | |
71 | host, | |
72 | port, | |
92f5a8d4 | 73 | beast::bind_front_handler( |
b32b8144 | 74 | &session::on_resolve, |
92f5a8d4 | 75 | shared_from_this())); |
b32b8144 FG |
76 | } |
77 | ||
78 | void | |
79 | on_resolve( | |
92f5a8d4 | 80 | beast::error_code ec, |
b32b8144 FG |
81 | tcp::resolver::results_type results) |
82 | { | |
83 | if(ec) | |
84 | return fail(ec, "resolve"); | |
85 | ||
92f5a8d4 TL |
86 | // Set the timeout for the operation |
87 | beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); | |
88 | ||
b32b8144 | 89 | // Make the connection on the IP address we get from a lookup |
92f5a8d4 TL |
90 | beast::get_lowest_layer(ws_).async_connect( |
91 | results, | |
92 | beast::bind_front_handler( | |
b32b8144 | 93 | &session::on_connect, |
92f5a8d4 | 94 | shared_from_this())); |
b32b8144 FG |
95 | } |
96 | ||
97 | void | |
f67539c2 | 98 | on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep) |
b32b8144 FG |
99 | { |
100 | if(ec) | |
101 | return fail(ec, "connect"); | |
102 | ||
92f5a8d4 TL |
103 | // Turn off the timeout on the tcp_stream, because |
104 | // the websocket stream has its own timeout system. | |
105 | beast::get_lowest_layer(ws_).expires_never(); | |
106 | ||
107 | // Set suggested timeout settings for the websocket | |
108 | ws_.set_option( | |
109 | websocket::stream_base::timeout::suggested( | |
110 | beast::role_type::client)); | |
111 | ||
112 | // Set a decorator to change the User-Agent of the handshake | |
113 | ws_.set_option(websocket::stream_base::decorator( | |
114 | [](websocket::request_type& req) | |
115 | { | |
116 | req.set(http::field::user_agent, | |
117 | std::string(BOOST_BEAST_VERSION_STRING) + | |
118 | " websocket-client-async"); | |
119 | })); | |
120 | ||
f67539c2 TL |
121 | // Update the host_ string. This will provide the value of the |
122 | // Host HTTP header during the WebSocket handshake. | |
123 | // See https://tools.ietf.org/html/rfc7230#section-5.4 | |
124 | host_ += ':' + std::to_string(ep.port()); | |
125 | ||
b32b8144 FG |
126 | // Perform the websocket handshake |
127 | ws_.async_handshake(host_, "/", | |
92f5a8d4 | 128 | beast::bind_front_handler( |
b32b8144 | 129 | &session::on_handshake, |
92f5a8d4 | 130 | shared_from_this())); |
b32b8144 FG |
131 | } |
132 | ||
133 | void | |
92f5a8d4 | 134 | on_handshake(beast::error_code ec) |
b32b8144 FG |
135 | { |
136 | if(ec) | |
137 | return fail(ec, "handshake"); | |
138 | ||
139 | // Send the message | |
140 | ws_.async_write( | |
92f5a8d4 TL |
141 | net::buffer(text_), |
142 | beast::bind_front_handler( | |
b32b8144 | 143 | &session::on_write, |
92f5a8d4 | 144 | shared_from_this())); |
b32b8144 FG |
145 | } |
146 | ||
147 | void | |
148 | on_write( | |
92f5a8d4 | 149 | beast::error_code ec, |
b32b8144 FG |
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_, | |
92f5a8d4 | 160 | beast::bind_front_handler( |
b32b8144 | 161 | &session::on_read, |
92f5a8d4 | 162 | shared_from_this())); |
b32b8144 FG |
163 | } |
164 | ||
165 | void | |
166 | on_read( | |
92f5a8d4 | 167 | beast::error_code ec, |
b32b8144 FG |
168 | std::size_t bytes_transferred) |
169 | { | |
170 | boost::ignore_unused(bytes_transferred); | |
171 | ||
172 | if(ec) | |
173 | return fail(ec, "read"); | |
174 | ||
175 | // Close the WebSocket connection | |
176 | ws_.async_close(websocket::close_code::normal, | |
92f5a8d4 | 177 | beast::bind_front_handler( |
b32b8144 | 178 | &session::on_close, |
92f5a8d4 | 179 | shared_from_this())); |
b32b8144 FG |
180 | } |
181 | ||
182 | void | |
92f5a8d4 | 183 | on_close(beast::error_code ec) |
b32b8144 FG |
184 | { |
185 | if(ec) | |
186 | return fail(ec, "close"); | |
187 | ||
188 | // If we get here then the connection is closed gracefully | |
189 | ||
92f5a8d4 TL |
190 | // The make_printable() function helps print a ConstBufferSequence |
191 | std::cout << beast::make_printable(buffer_.data()) << std::endl; | |
b32b8144 FG |
192 | } |
193 | }; | |
194 | ||
195 | //------------------------------------------------------------------------------ | |
196 | ||
197 | int main(int argc, char** argv) | |
198 | { | |
199 | // Check command line arguments. | |
200 | if(argc != 4) | |
201 | { | |
202 | std::cerr << | |
203 | "Usage: websocket-client-async <host> <port> <text>\n" << | |
204 | "Example:\n" << | |
205 | " websocket-client-async echo.websocket.org 80 \"Hello, world!\"\n"; | |
206 | return EXIT_FAILURE; | |
207 | } | |
208 | auto const host = argv[1]; | |
209 | auto const port = argv[2]; | |
210 | auto const text = argv[3]; | |
211 | ||
212 | // The io_context is required for all I/O | |
92f5a8d4 | 213 | net::io_context ioc; |
b32b8144 FG |
214 | |
215 | // Launch the asynchronous operation | |
216 | std::make_shared<session>(ioc)->run(host, port, text); | |
217 | ||
218 | // Run the I/O service. The call will return when | |
11fdf7f2 | 219 | // the socket is closed. |
b32b8144 FG |
220 | ioc.run(); |
221 | ||
222 | return EXIT_SUCCESS; | |
223 | } |