OSSIA
Open Scenario System for Interactive Application
websocket_client.hpp
1 #pragma once
2 #include <ossia/detail/config.hpp>
3 
4 #include <ossia/detail/json.hpp>
6 
7 #include <websocketpp/client.hpp>
8 #include <websocketpp/common/thread.hpp>
9 #include <websocketpp/config/asio_no_tls_client.hpp>
10 
11 #include <nano_signal_slot.hpp>
12 
13 namespace ossia::net
14 {
15 
18 {
19 public:
20  using connection_handler = websocketpp::connection_hdl;
21  Nano::Signal<void()> on_open;
22  Nano::Signal<void()> on_close;
23  Nano::Signal<void()> on_fail;
24 
26  : m_open{false}
27  {
28  init_client();
29  }
30 
31  void init_client()
32  {
33  m_client = std::make_shared<client_t>();
34  assert(m_client);
35  std::weak_ptr<client_t> weak_client = m_client;
36  m_client->clear_access_channels(websocketpp::log::alevel::all);
37  m_client->clear_error_channels(websocketpp::log::elevel::all);
38 
39  m_client->set_open_handler([this, weak_client](connection_handler hdl) {
40  if(!weak_client.lock())
41  return;
42  scoped_lock guard(m_lock);
43  m_open = true;
44 
45  on_open();
46  });
47 
48  m_client->set_close_handler([this, weak_client](connection_handler hdl) {
49  if(!weak_client.lock())
50  return;
51  {
52  scoped_lock guard(m_lock);
53  m_open = false;
54  }
55  on_close();
56  });
57 
58  m_client->set_fail_handler([this, weak_client](connection_handler hdl) {
59  if(!weak_client.lock())
60  return;
61  {
62  scoped_lock guard(m_lock);
63  m_open = false;
64  }
65  on_fail();
66  });
67  assert(m_client);
68  }
69 
72  template <typename MessageHandler>
73  websocket_client(MessageHandler&& onMessage)
75  {
76  assert(m_client);
77  m_client->init_asio();
78 
79  std::weak_ptr<client_t> weak_client = m_client;
80  m_client->set_message_handler(
81  [handler = std::move(onMessage),
82  weak_client](connection_handler hdl, client_t::message_ptr msg) {
83  if(!weak_client.lock())
84  return;
85  handler(hdl, msg->get_opcode(), msg->get_raw_payload());
86  });
87  assert(m_client);
88  }
89 
90  template <typename MessageHandler>
91  websocket_client(boost::asio::io_context& ctx, MessageHandler&& onMessage)
93  {
94  assert(m_client);
95  m_client->init_asio(&ctx);
96  m_ctx = &ctx;
97 
98  std::weak_ptr<client_t> weak_client = m_client;
99  m_client->set_message_handler(
100  [handler = std::move(onMessage),
101  weak_client](connection_handler hdl, client_t::message_ptr msg) {
102  if(!weak_client.lock())
103  return;
104  handler(hdl, msg->get_opcode(), msg->get_raw_payload());
105  });
106  assert(m_client);
107  }
108 
109  ~websocket_client()
110  {
111  if(m_open)
112  stop();
113  }
114 
115  bool connected() const { return m_open; }
116 
117  void stop()
118  {
119  if(!m_open)
120  {
121  if(m_client)
122  m_client->stop();
123  m_connected = false;
124  return;
125  }
126 
127  scoped_lock guard(m_lock);
128  m_client->close(m_hdl, websocketpp::close::status::normal, "");
129  m_client->stop();
130  m_open = false;
131  }
132 
133  auto& client() { return m_client; }
134  auto& handle() { return m_hdl; }
135  bool after_connect() { return m_connected; }
136 
137  void connect(const std::string& uri)
138  {
139  websocketpp::lib::error_code ec;
140  if(!m_client)
141  {
142  init_client();
143  assert(m_client);
144  if(m_ctx)
145  m_client->init_asio(m_ctx);
146  else
147  m_client->init_asio();
148  }
149 
150  auto con = m_client->get_connection(uri, ec);
151  if(ec)
152  {
153  m_client->get_alog().write(
154  websocketpp::log::alevel::app, "Get Connection Error: " + ec.message());
155  return;
156  }
157 
158  m_hdl = con->get_handle();
159  m_client->connect(con);
160  m_connected = true;
161  }
162 
163  void finish_connection()
164  {
165  m_connected = false;
166  m_client.reset(); // In order to be able to reconnect afterwards.
167  }
168 
169  // This function returns if the connection is stopped / fails.
170  void connect_and_run(const std::string& uri)
171  {
172  connect(uri);
173 
174  m_client->run();
175 
176  finish_connection();
177  }
178 
179  void send_message(const std::string& request)
180  {
181  if(!m_open || !m_client)
182  return;
183 
184  websocketpp::lib::error_code ec;
185 
186  m_client->send(m_hdl, request, websocketpp::frame::opcode::text, ec);
187 
188  if(ec)
189  {
190  m_client->get_alog().write(
191  websocketpp::log::alevel::app, "Send Error: " + ec.message());
192  }
193  }
194 
195  void send_message(const rapidjson::StringBuffer& request)
196  {
197  if(!m_open || !m_client)
198  return;
199 
200  websocketpp::lib::error_code ec;
201 
202  m_client->send(
203  m_hdl, request.GetString(), request.GetSize(), websocketpp::frame::opcode::text,
204  ec);
205 
206  if(ec)
207  {
208  m_client->get_alog().write(
209  websocketpp::log::alevel::app, "Send Error: " + ec.message());
210  }
211  }
212 
213  void send_binary_message(std::string_view request)
214  {
215  if(!m_open || !m_client)
216  return;
217 
218  websocketpp::lib::error_code ec;
219 
220  m_client->send(
221  m_hdl, request.data(), request.size(), websocketpp::frame::opcode::binary, ec);
222 
223  if(ec)
224  {
225  m_client->get_alog().write(
226  websocketpp::log::alevel::app, "Send Error: " + ec.message());
227  }
228  }
229 
230 protected:
231  using client_t = websocketpp::client<websocketpp::config::asio_client>;
232  using scoped_lock = websocketpp::lib::lock_guard<websocketpp::lib::mutex>;
233 
234  boost::asio::io_context* m_ctx{};
235  std::shared_ptr<client_t> m_client;
236  connection_handler m_hdl;
237  websocketpp::lib::mutex m_lock;
238  std::atomic_bool m_open{false};
239  std::atomic_bool m_connected{false};
240 };
241 }
Low-level Websocket client.
Definition: websocket_client.hpp:18
websocket_client(MessageHandler &&onMessage)
Definition: websocket_client.hpp:73