Giri's C++ Support Library
C++ library providing everything you need to quickly create awesome applications.
WebSocketServer.h
Go to the documentation of this file.
1 
10 #ifndef SUPPORTLIB_WEBSOCKETSERVER_H
11 #define SUPPORTLIB_WEBSOCKETSERVER_H
12 #include "Observer.h"
13 #include "Exception.h"
14 #include <boost/asio/ip/tcp.hpp>
15 #include <boost/beast/core.hpp>
16 #include <boost/beast/websocket.hpp>
17 #include <boost/beast/websocket/ssl.hpp>
18 #include <boost/asio/bind_executor.hpp>
19 #include <boost/asio/strand.hpp>
20 #include <boost/asio/ssl/stream.hpp>
21 #include <filesystem>
22 #include <algorithm>
23 #include <cstdlib>
24 #include <iostream>
25 #include <memory>
26 #include <string>
27 #include <thread>
28 #include <vector>
29 
30 namespace giri {
31 
32  using tcp = boost::asio::ip::tcp;
33  namespace websocket = boost::beast::websocket;
34  namespace ssl = boost::asio::ssl;
35 
40  {
41  public:
42  WebSocketServerException(const std::string &msg) : ExceptionBase(msg) {};
43  using SPtr = std::shared_ptr<WebSocketServerException>;
44  using UPtr = std::unique_ptr<WebSocketServerException>;
45  using WPtr = std::weak_ptr<WebSocketServerException>;
46  };
47 
53  class WebSocketSession : public Observable<WebSocketSession>
54  {
55  public:
65  explicit WebSocketSession(tcp::socket socket, bool ssl, const std::filesystem::path& cert, const std::filesystem::path& key, boost::asio::io_context& ioc) :
66  m_Socket(std::move(socket)),
67  m_SSL(ssl),
68  m_Strand(boost::asio::make_strand(ioc))
69  {
70  if(m_SSL)
71  {
72  m_Ctx.set_options(boost::asio::ssl::context::default_workarounds |
73  boost::asio::ssl::context::no_sslv2 |
74  boost::asio::ssl::context::no_sslv3 |
75  boost::asio::ssl::context::no_tlsv1 |
76  boost::asio::ssl::context::single_dh_use);
77  m_Ctx.use_certificate_chain_file(cert.string());
78  m_Ctx.use_private_key_file(key.string(), boost::asio::ssl::context::file_format::pem);
79  m_Wss = std::make_shared<websocket::stream<ssl::stream<tcp::socket&> >>(m_Socket, m_Ctx);
80  m_Wss->next_layer().set_verify_mode(ssl::verify_none);
81  m_Wss->text(true);
82  }
83  else
84  {
85  m_Ws = std::make_shared<websocket::stream<tcp::socket&>>(m_Socket);
86  m_Ws->text(true);
87  }
88  }
93  void run() {
94  if(m_SSL)
95  m_Wss->next_layer().async_handshake(ssl::stream_base::server,boost::asio::bind_executor(m_Strand,std::bind(&WebSocketSession::on_handshake,this->shared_from_this(),std::placeholders::_1)));
96  else
97  m_Ws->async_accept(boost::asio::bind_executor(m_Strand, std::bind(&WebSocketSession::on_accept, this->shared_from_this(), std::placeholders::_1)));
98  }
104  void send(const std::string& msg){
105  if(!m_Ec)
106  if(m_SSL)
107  {if(m_Wss->is_open()){m_Wss->write(boost::asio::buffer(msg));}}
108  else
109  {if(m_Ws->is_open()){m_Ws->write(boost::asio::buffer(msg));}}
110  }
114  std::string getMessage() const {
115  return m_Message;
116  }
120  bool getSSL() const {
121  return m_SSL;
122  }
126  boost::system::error_code getError() const {
127  return m_Ec;
128  }
132  std::string getClientIP() const {
133  return m_Socket.remote_endpoint().address().to_string();
134  }
138  std::string getClientPort() const {
139  return std::to_string(m_Socket.remote_endpoint().port());
140  }
144  void close() {
145  if(!m_Ec)
146  if(m_SSL)
147  {if(m_Wss->is_open()){m_Wss->close(websocket::close_code::normal);}}
148  else
149  {if(m_Ws->is_open()){m_Ws->close(websocket::close_code::normal);}}
150  }
151  using SPtr = std::shared_ptr<WebSocketSession>;
152  using UPtr = std::unique_ptr<WebSocketSession>;
153  using WPtr = std::weak_ptr<WebSocketSession>;
154  private:
155  void on_read(boost::system::error_code ec, std::size_t bytes_transferred) {
156  boost::ignore_unused(bytes_transferred);
157  m_Message.clear();
158  m_Ec = ec;
159  if(!ec)
160  {
161  m_Message = boost::beast::buffers_to_string(m_Buffer.data());
162  }
163  m_Buffer.consume(m_Buffer.size()); // clear buffer
164  notify(); // notify all subscribed observers
165  if(!ec)
166  do_read(); // wait for new message
167  }
168  void on_accept(boost::system::error_code ec) {
169  if(ec)
170  throw WebSocketServerException("Accept: " + ec.message());
171  do_read();
172  }
173  void do_read() {
174  if(m_SSL)
175  {if(m_Wss->is_open()){m_Wss->async_read(m_Buffer, boost::asio::bind_executor(m_Strand, std::bind(&WebSocketSession::on_read, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2)));}}
176  else
177  {if(m_Ws->is_open()){m_Ws->async_read(m_Buffer, boost::asio::bind_executor(m_Strand, std::bind(&WebSocketSession::on_read, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2)));}}
178  }
179  void on_handshake(boost::system::error_code ec) {
180  if(ec)
181  throw WebSocketServerException("Handshake: " + ec.message());
182  m_Wss->async_accept(boost::asio::bind_executor(m_Strand, std::bind( &WebSocketSession::on_accept, this->shared_from_this(), std::placeholders::_1)));
183  }
184  tcp::socket m_Socket;
185  bool m_SSL;
186  std::shared_ptr< websocket::stream<tcp::socket&> > m_Ws;
187  std::shared_ptr< websocket::stream<ssl::stream<tcp::socket&> > > m_Wss;
188  boost::asio::strand<boost::asio::io_context::executor_type> m_Strand;
189  boost::beast::multi_buffer m_Buffer;
190  std::string m_Message;
191  ssl::context m_Ctx{ssl::context::sslv23};
192  boost::system::error_code m_Ec;
193  };
194 
243  class WebSocketServer : public Observable<WebSocketServer>
244  {
245  public:
246 
257  WebSocketServer(const std::string& address = "0.0.0.0", const std::string& port = "80", bool ssl = false, const size_t numThreads = 1, const std::filesystem::path& cert = "", const std::filesystem::path& key = "") :
258  m_Endpoint(boost::asio::ip::make_address(address),
259  std::atoi(port.c_str())),
260  m_SSL(ssl),
261  m_Ioc(numThreads),
262  m_NumThreads(numThreads),
263  m_Acceptor(m_Ioc),
264  m_Socket(m_Ioc),
265  m_Cert(cert),
266  m_Key(key)
267  {
268  boost::system::error_code ec;
269  m_Acceptor.open(m_Endpoint.protocol(), ec);
270  if(ec)
271  throw WebSocketServerException("Open: " + ec.message());
272  m_Acceptor.set_option(boost::asio::socket_base::reuse_address(true));
273  if(ec)
274  throw WebSocketServerException("Set Option: " + ec.message());
275  m_Acceptor.bind(m_Endpoint, ec);
276  if(ec)
277  throw WebSocketServerException("Bind: " + ec.message());
278  m_Acceptor.listen(boost::asio::socket_base::max_listen_connections, ec);
279  if(ec)
280  throw WebSocketServerException("Listen: " + ec.message());
281  }
287  void run() {
288  if(!m_Acceptor.is_open()) return;
289  do_accept();
290 
291  m_Threads.reserve(m_NumThreads);
292  for(auto i = m_NumThreads; i > 0; --i)
293  m_Threads.emplace_back([this]{ m_Ioc.run();});
294  }
298  WebSocketSession::SPtr getSession() const {
299  return m_NewSession;
300  }
304  bool getSSL() const {
305  return m_SSL;
306  }
310  std::filesystem::path getCert() const {
311  return m_Cert;
312  }
316  std::filesystem::path getKey() const {
317  return m_Key;
318  }
319  using SPtr = std::shared_ptr<WebSocketServer>;
320  using UPtr = std::unique_ptr<WebSocketServer>;
321  using WPtr = std::weak_ptr<WebSocketServer>;
322  private:
323  void do_accept() {
324  m_Acceptor.async_accept(m_Socket, std::bind(&WebSocketServer::on_accept, this->shared_from_this(), std::placeholders::_1));
325  }
326  void on_accept(boost::system::error_code ec) {
327  if(ec)
328  throw WebSocketServerException("Accept: " + ec.message());
329  m_NewSession = std::make_shared<WebSocketSession>(std::move(m_Socket), m_SSL, m_Cert, m_Key, m_Ioc);
330  m_NewSession->run();
331  notify(); // notify all subscribed observers
332  do_accept(); // Accept another connection
333  }
334  tcp::endpoint m_Endpoint;
335  boost::asio::io_context m_Ioc;
336  std::vector<std::thread> m_Threads;
337  bool m_SSL;
338  size_t m_NumThreads;
339  tcp::acceptor m_Acceptor;
340  tcp::socket m_Socket;
341  WebSocketSession::SPtr m_NewSession;
342  std::filesystem::path m_Cert;
343  std::filesystem::path m_Key;
344  };
345 }
346 #endif //SUPPORTLIB_WEBSOCKETSERVER_H
Base exception to inherit custom exceptions from.
Observer/Obersvable Pattern implementation.
Base exception to inherit custom exceptions from.
Definition: Exception.h:48
ExceptionBase(const std::string &msg="")
Definition: Exception.h:54
Observable class. Inherited classes can notify all classes which inherit from Observer.
Definition: Observer.h:99
void notify()
Definition: Observer.h:130
Exception to be thrown on websocket server errors.
Definition: WebSocketServer.h:40
Class representing a WebSocket Server.
Definition: WebSocketServer.h:244
void run()
Definition: WebSocketServer.h:287
bool getSSL() const
Definition: WebSocketServer.h:304
std::filesystem::path getKey() const
Definition: WebSocketServer.h:316
std::filesystem::path getCert() const
Definition: WebSocketServer.h:310
WebSocketServer(const std::string &address="0.0.0.0", const std::string &port="80", bool ssl=false, const size_t numThreads=1, const std::filesystem::path &cert="", const std::filesystem::path &key="")
Definition: WebSocketServer.h:257
WebSocketSession::SPtr getSession() const
Definition: WebSocketServer.h:298
Class representing one session/connection.
Definition: WebSocketServer.h:54
void close()
Definition: WebSocketServer.h:144
boost::system::error_code getError() const
Definition: WebSocketServer.h:126
std::string getClientPort() const
Definition: WebSocketServer.h:138
std::string getClientIP() const
Definition: WebSocketServer.h:132
WebSocketSession(tcp::socket socket, bool ssl, const std::filesystem::path &cert, const std::filesystem::path &key, boost::asio::io_context &ioc)
Definition: WebSocketServer.h:65
void run()
Definition: WebSocketServer.h:93
void send(const std::string &msg)
msg Message to send
Definition: WebSocketServer.h:104
bool getSSL() const
Definition: WebSocketServer.h:120
std::string getMessage() const
Definition: WebSocketServer.h:114
Namespace for giri's C++ support library.
Definition: Base64.h:47
Definition: JSON.h:123