NAP
websocketclientendpoint.h
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 #pragma once
6 
7 // Local Includes
8 #include "iwebsocketclientendpoint.h"
9 #include "websocketmessage.h"
10 #include "websocketclient.h"
11 
12 // External Includes
13 #include <atomic>
14 #include <nap/logger.h>
15 
16 namespace nap
17 {
18  // Forward Declares
19  template<typename config>
21 
38  template<typename config>
40  {
41  RTTI_ENABLE(IWebSocketClientEndPoint)
42  public:
43 
49  virtual bool init(utility::ErrorState& errorState) override;
50 
54  virtual void stop() override;
55 
62  bool start(nap::utility::ErrorState& error) override;
63 
72  bool send(const WebSocketConnection& connection, const std::string& message, EWebSocketOPCode code, nap::utility::ErrorState& error) override;
73 
83  bool send(const WebSocketConnection& connection, void const* payload, int length, EWebSocketOPCode code, nap::utility::ErrorState& error) override;
84 
85  protected:
89  void run();
90 
98  bool registerClient(IWebSocketClient& client, utility::ErrorState& error) override;
99 
106  void unregisterClient(const IWebSocketClient& client) override;
107 
110  bool mRunning = false;
111  std::future<void> mClientTask;
112  std::vector<std::unique_ptr<WebSocketClientWrapper<config>>> mClients;
113  websocketpp::client<config> mEndPoint;
114  };
115 
116 
117  // Not secured web-socket client end point connection.
119 
120 
124  class NAPAPI SecureWebSocketClientEndPoint : public WebSocketClientEndPointSetup<wspp::ConfigTLS>
125  {
127  public:
134  bool start(nap::utility::ErrorState &error) override;
135 
136  std::string mCertificateChainFile;
137  std::string mHostName;
138 
139  private:
145  std::shared_ptr<asio::ssl::context> onTLSInit(websocketpp::connection_hdl);
146 
147  bool verifyCertificate(const char * hostname, bool preverified, asio::ssl::verify_context& ctx);
148  };
149 
150 
152  // WebSocketClientWrapper
154 
160  template<typename config>
161  class WebSocketClientWrapper final
162  {
163  template<typename X>
165  public:
166  // Destructor
168 
169  private:
174  void onConnectionOpened(wspp::ConnectionHandle connection);
175 
180  void onConnectionClosed(wspp::ConnectionHandle connection);
181 
186  void onConnectionFailed(wspp::ConnectionHandle connection);
187 
193  void onMessageReceived(wspp::ConnectionHandle connection, wspp::MessagePtr msg);
194 
201  WebSocketClientWrapper(IWebSocketClient& client, websocketpp::client<config>& endPoint,
202  typename websocketpp::endpoint<websocketpp::connection<config>, config>::connection_ptr connection);
203 
210  bool disconnect(nap::utility::ErrorState& error);
211 
212  IWebSocketClient* mResource = nullptr;
213  websocketpp::client<config>* mEndPoint = nullptr;
214  wspp::ConnectionHandle mHandle;
215  std::atomic<bool> mOpen = { false };
216  };
217 
218 
220  // Template Definitions
222 
223  template<typename config>
225  {
226  // Convert log levels
227  mLogLevel = computeWebSocketLogLevel(mLibraryLogLevel);
228  mAccessLogLevel = mLogConnectionUpdates ? websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload
229  : websocketpp::log::alevel::fail;
230 
231  // Initiate logging
232  mEndPoint.clear_error_channels(websocketpp::log::elevel::all);
233  mEndPoint.set_error_channels(mLogLevel);
234 
235  mEndPoint.clear_access_channels(websocketpp::log::alevel::all);
236  mEndPoint.set_access_channels(mAccessLogLevel);
237 
238  // Init asio
239  std::error_code stdec;
240  mEndPoint.init_asio(stdec);
241  if (stdec)
242  {
243  errorState.fail(stdec.message());
244  return false;
245  }
246 
247  return true;
248  }
249 
250  template<typename config>
252  {
253  // At this state we need to have an open end point and client thread
254  assert(mRunning);
255  assert(mClientTask.valid());
256 
257  // Make sure the end point doesn't restart
258  mEndPoint.stop_perpetual();
259 
260  // Wait until all still active clients exit clean
261  for (auto& client : mClients)
262  {
263  utility::ErrorState error;
264  if (!client->disconnect(error))
265  nap::Logger::error(error.toString());
266  }
267 
268  // Wait until all clients exited
269  mClientTask.wait();
270  mRunning = false;
271 
272  // Clear all clients
273  mClients.clear();
274  }
275 
276 
277  template<typename config>
279  {
280  // Ensure state
281  assert(!mRunning);
282 
283  // Ensure connection exists when server disconnects
284  mEndPoint.start_perpetual();
285 
286  // Run client in background
287  mClientTask = std::async(std::launch::async, std::bind(&WebSocketClientEndPointSetup<config>::run, this));
288 
289  mRunning = true;
290 
291  return true;
292  }
293 
294 
295  template<typename config>
296  bool WebSocketClientEndPointSetup<config>::send(const WebSocketConnection& connection, const std::string& message, EWebSocketOPCode code, nap::utility::ErrorState& error)
297  {
298  std::error_code stdec;
299  mEndPoint.send(connection.mConnection, message, static_cast<wspp::OpCode>(code), stdec);
300  if (stdec)
301  {
302  error.fail(stdec.message());
303  return false;
304  }
305  return true;
306  }
307 
308 
309  template<typename config>
310  bool WebSocketClientEndPointSetup<config>::send(const WebSocketConnection& connection, void const* payload, int length, EWebSocketOPCode code, nap::utility::ErrorState& error)
311  {
312  std::error_code stdec;
313  mEndPoint.send(connection.mConnection, payload, length, static_cast<wspp::OpCode>(code), stdec);
314  if (stdec)
315  {
316  error.fail(stdec.message());
317  return false;
318  }
319  return true;
320  }
321 
322 
323  template<typename config>
325  {
326  // Start running until stopped
327  mEndPoint.run();
328  }
329 
330 
331  template<typename config>
333  {
334  // Get shared pointer to connection
335  std::error_code stdec;
336  auto client_connection = mEndPoint.get_connection(client.mURI, stdec);
337  if (stdec)
338  {
339  error.fail(stdec.message());
340  return false;
341  }
342 
343  // Send identification information if present on client
344  if (client.mTicket != nullptr)
345  {
346  // Convert ticket into binary string
347  std::string binary_string;
348  if (!client.mTicket->toBinaryString(binary_string, error))
349  return false;
350 
351  // Add as sub-protocol identifier.
352  client_connection->add_subprotocol(binary_string, stdec);
353  if (stdec)
354  {
355  error.fail(stdec.message());
356  return false;
357  }
358  }
359 
360  // Create meta client and add to the list of internally managed clients
361  std::unique_ptr<WebSocketClientWrapper<config>> meta_client(new WebSocketClientWrapper<config>(client,
362  mEndPoint,
363  client_connection));
364  mClients.emplace_back(std::move(meta_client));
365 
366  // Store connection handle in client
367  client.mConnection = WebSocketConnection(client_connection->get_handle());
368 
369  // TRY to connect, this occurs on a background thread.
370  // All connection related state changes are handled in the WebSocketClientWrapper.
371  mEndPoint.connect(client_connection);
372 
373  return true;
374  }
375 
376 
377  template<typename config>
379  {
380  auto found_it = std::find_if(mClients.begin(), mClients.end(), [&](const auto& it)
381  {
382  return it->mResource == &client;
383  });
384 
385  if (found_it == mClients.end())
386  {
387  return;
388  }
389 
390  // Disconnect if connected previously
391  utility::ErrorState error;
392  if (!(*found_it)->disconnect(error))
393  {
394  nap::Logger::error(error.toString());
395  }
396  mClients.erase(found_it);
397  }
398 
399 
400  template<typename config>
401  WebSocketClientWrapper<config>::WebSocketClientWrapper(IWebSocketClient& client, websocketpp::client<config>& endPoint, typename websocketpp::endpoint<websocketpp::connection<config>, config>::connection_ptr connection) :
402  mResource(&client), mEndPoint(&endPoint), mHandle(connection->get_handle())
403  {
404  // Connect callbacks (occur on different thread)
405  connection->set_open_handler(std::bind(&WebSocketClientWrapper::onConnectionOpened, this, std::placeholders::_1));
406  connection->set_close_handler(std::bind(&WebSocketClientWrapper::onConnectionClosed, this, std::placeholders::_1));
407  connection->set_fail_handler(std::bind(&WebSocketClientWrapper::onConnectionFailed, this, std::placeholders::_1));
408 
409  // Install message handler
410  connection->set_message_handler(std::bind(
411  &WebSocketClientWrapper::onMessageReceived, this,
412  std::placeholders::_1, std::placeholders::_2));
413  }
414 
415 
416  template<typename config>
417  void WebSocketClientWrapper<config>::onConnectionOpened(wspp::ConnectionHandle connection)
418  {
419  assert(mResource != nullptr);
420  mResource->connectionOpened();
421  mOpen = true;
422  }
423 
424 
425  template<typename config>
426  void WebSocketClientWrapper<config>::onConnectionClosed(wspp::ConnectionHandle connection)
427  {
428  // Extract actual connection, must be valid at this point
429  std::error_code stdec;
430  auto cptr = mEndPoint->get_con_from_hdl(connection, stdec);
431  assert(!stdec);
432 
433  assert(mResource != nullptr);
434  mResource->connectionClosed(stdec.value(), stdec.message());
435  mOpen = false;
436  }
437 
438 
439  template<typename config>
440  void WebSocketClientWrapper<config>::onConnectionFailed(wspp::ConnectionHandle connection)
441  {
442  // Extract actual connection, must be valid at this point
443  std::error_code stdec;
444  auto cptr = mEndPoint->get_con_from_hdl(connection, stdec);
445  assert(!stdec);
446 
447  assert(mResource != nullptr);
448  mResource->connectionFailed(stdec.value(), stdec.message());
449  mOpen = false;
450  }
451 
452 
453  template<typename config>
454  void WebSocketClientWrapper<config>::onMessageReceived(wspp::ConnectionHandle connection, wspp::MessagePtr msg)
455  {
456  assert(mResource != nullptr);
457  mResource->messageReceived(WebSocketMessage(msg));
458  }
459 
460 
461  template<typename config>
462  bool WebSocketClientWrapper<config>::disconnect(nap::utility::ErrorState& error)
463  {
464  if (mOpen)
465  {
466  // Get connection from handle, should be open by this point
467  std::error_code stdec;
468  auto cptr = mEndPoint->get_con_from_hdl(mHandle, stdec);
469  if (stdec)
470  {
471  assert(false);
472  error.fail(stdec.message());
473  return false;
474  }
475 
476  // Remove callbacks
477  cptr->set_open_handler(nullptr);
478  cptr->set_close_handler(nullptr);
479  cptr->set_fail_handler(nullptr);
480  cptr->set_message_handler(nullptr);
481 
482  // Now close
483  mEndPoint->close(mHandle, websocketpp::close::status::going_away, "disconnected", stdec);
484  if (stdec)
485  {
486  nap::Logger::error(stdec.message());
487  }
488  mOpen = false;
489  }
490  return true;
491  }
492 
493  template<typename config>
495  {
496  // Not disconnected by server or client
497  assert(!mOpen);
498  mResource = nullptr;
499  mEndPoint = nullptr;
500  }
501 }
nap::WebSocketClientEndPointSetup::start
bool start(nap::utility::ErrorState &error) override
Definition: websocketclientendpoint.h:278
nap::WebSocketClientEndPointSetup
Definition: websocketclientendpoint.h:39
nap::WebSocketConnection
Definition: websocketconnection.h:27
nap::WebSocketClientWrapper
Definition: websocketclientendpoint.h:20
nap::wspp::OpCode
websocketpp::frame::opcode::value OpCode
web-socket op codes
Definition: wspp.h:20
nap::WebSocketClientEndPointSetup::init
virtual bool init(utility::ErrorState &errorState) override
Definition: websocketclientendpoint.h:224
nap::WebSocketClientEndPointSetup::registerClient
bool registerClient(IWebSocketClient &client, utility::ErrorState &error) override
Definition: websocketclientendpoint.h:332
nap::IWebSocketClient::mTicket
ResourcePtr< WebSocketTicket > mTicket
Property: 'Ticket' optional identification token.
Definition: websocketclient.h:87
nap::icon::error
constexpr const char * error
Definition: imguiservice.h:55
nap::IWebSocketClient::mConnection
WebSocketConnection mConnection
Web-socket connection.
Definition: websocketclient.h:91
nap::WebSocketClientEndPointSetup::send
bool send(const WebSocketConnection &connection, const std::string &message, EWebSocketOPCode code, nap::utility::ErrorState &error) override
Definition: websocketclientendpoint.h:296
nap::WebSocketClientEndPointSetup::mEndPoint
websocketpp::client< config > mEndPoint
websocketpp client end point
Definition: websocketclientendpoint.h:113
nap::WebSocketClientWrapper::~WebSocketClientWrapper
~WebSocketClientWrapper()
Definition: websocketclientendpoint.h:494
nap::SecureWebSocketClientEndPoint
Definition: websocketclientendpoint.h:124
nap::utility::ErrorState
Definition: errorstate.h:19
nap::SecureWebSocketClientEndPoint::mHostName
std::string mHostName
Property: "HostName" host name to verify against the certificate.
Definition: websocketclientendpoint.h:137
nap::uint32
uint32_t uint32
Definition: numeric.h:20
nap::WebSocketClientEndPointSetup::run
void run()
Definition: websocketclientendpoint.h:324
nap::utility::ErrorState::fail
void fail(T &&errorMessage)
Definition: errorstate.h:73
nap::wspp::ConnectionHandle
std::weak_ptr< void > ConnectionHandle
server / client connection
Definition: wspp.h:18
nap::computeWebSocketLogLevel
NAPAPI uint32 computeWebSocketLogLevel(EWebSocketLogLevel level)
nap::WebSocketClientEndPointSetup::mAccessLogLevel
uint32 mAccessLogLevel
Log client / server connection data.
Definition: websocketclientendpoint.h:109
nap::WebSocketClientEndPointSetup::unregisterClient
void unregisterClient(const IWebSocketClient &client) override
Definition: websocketclientendpoint.h:378
nap::WebSocketClientEndPointSetup::stop
virtual void stop() override
Definition: websocketclientendpoint.h:251
nap::SecureWebSocketClientEndPoint::mCertificateChainFile
std::string mCertificateChainFile
Property: "CertificateChainFile" path to the certificate chain file.
Definition: websocketclientendpoint.h:136
nap::wspp::MessagePtr
Config::message_type::ptr MessagePtr
internal message format
Definition: wspp.h:19
nap::WebSocketClientEndPointSetup::mRunning
bool mRunning
If the client connection to the server is open.
Definition: websocketclientendpoint.h:110
nap::WebSocketClientEndPointSetup::mClientTask
std::future< void > mClientTask
The client server thread.
Definition: websocketclientendpoint.h:111
nap::WebSocketClientEndPointSetup::mLogLevel
uint32 mLogLevel
Converted library log level.
Definition: websocketclientendpoint.h:108
nap::IWebSocketClient
Definition: websocketclient.h:34
nap
Definition: templateapp.h:17
nap::IWebSocketClientEndPoint
Definition: iwebsocketclientendpoint.h:34
nap::WebSocketClientEndPointSetup::mClients
std::vector< std::unique_ptr< WebSocketClientWrapper< config > > > mClients
All unique client connections.
Definition: websocketclientendpoint.h:112
nap::EWebSocketOPCode
EWebSocketOPCode
Definition: websocketutils.h:29
nap::IWebSocketClient::mURI
std::string mURI
Property: "URI" Server URI to open connection to.
Definition: websocketclient.h:88