OSSIA
Open Scenario System for Interactive Application
websocket_server.hpp
1 #pragma once
2 #include <ossia/detail/config.hpp>
3 
4 #include <ossia/detail/json.hpp>
6 #include <ossia/network/exceptions.hpp>
7 #include <ossia/network/sockets/websocket_reply.hpp>
8 
9 #include <websocketpp/config/asio_no_tls.hpp>
10 #include <websocketpp/http/request.hpp>
11 #include <websocketpp/server.hpp>
12 #if defined(OSSIA_BENCHMARK)
13 #include <chrono>
14 #endif
15 namespace ossia::net
16 {
17 
20 {
21 public:
22  using server_t = websocketpp::server<websocketpp::config::asio>;
23  using connection_handler = websocketpp::connection_hdl;
24 
26  {
27  m_server.init_asio();
28  m_server.set_reuse_addr(true);
29  m_server.clear_access_channels(websocketpp::log::alevel::all);
30  m_server.set_socket_init_handler(init_handler);
31  }
32 
33  websocket_server(boost::asio::io_context& ctx)
34  {
35  m_server.init_asio(&ctx);
36  m_server.set_reuse_addr(true);
37  m_server.clear_access_channels(websocketpp::log::alevel::all);
38  m_server.set_socket_init_handler(init_handler);
39  }
40 
41  static void init_handler(websocketpp::connection_hdl, boost::asio::ip::tcp::socket& s)
42  {
43  boost::asio::ip::tcp::no_delay option(true);
44  try
45  {
46  s.set_option(option);
47  }
48  catch(...)
49  {
50  ossia::logger().trace("Could not set TCP nodelay option");
51  }
52  }
53 
54  template <typename Handler>
55  void set_open_handler(Handler h)
56  {
57  m_server.set_open_handler(h);
58  }
59 
60  template <typename Handler>
61  void set_close_handler(Handler h)
62  {
63  m_server.set_close_handler(h);
64  }
65 
66  template <typename Handler>
67  void set_message_handler(Handler h)
68  {
69  m_server.set_message_handler(
70  [this, h](connection_handler hdl, server_t::message_ptr msg) {
71 #if defined OSSIA_BENCHMARK
72  auto t1 = std::chrono::high_resolution_clock::now();
73 #endif
74  try
75  {
76  auto res = h(hdl, msg->get_opcode(), msg->get_raw_payload());
77  if(res.data.size() > 0)
78  {
79  send_message(hdl, res);
80  }
81  }
82  catch(const ossia::node_not_found_error& e)
83  {
84  auto con = m_server.get_con_from_hdl(hdl);
85  ossia::logger().error(
86  "Node not found: {} ==> {}", con->get_uri()->get_resource(), e.what());
87  }
88  catch(const ossia::bad_request_error& e)
89  {
90  auto con = m_server.get_con_from_hdl(hdl);
91  ossia::logger().error(
92  "Error in request: {} ==> {}", con->get_uri()->get_resource(), e.what());
93  }
94  catch(const std::exception& e)
95  {
96  auto con = m_server.get_con_from_hdl(hdl);
97  ossia::logger().error("Error in request: {}", e.what());
98  }
99  catch(...)
100  {
101  auto con = m_server.get_con_from_hdl(hdl);
102  ossia::logger().error("Error in request");
103  }
104 
105 #if defined OSSIA_BENCHMARK
106  auto t2 = std::chrono::high_resolution_clock::now();
107  ossia::logger().info(
108  "Time taken: {}",
109  std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count());
110 #endif
111  });
112 
113  m_server.set_http_handler([this, h](connection_handler hdl) {
114  auto con = m_server.get_con_from_hdl(hdl);
115 
116  // enable cross origin requests from anywhere
117  con->append_header("Access-Control-Allow-Origin", "*");
118 
119  try
120  {
121  ossia::net::server_reply str
122  = h(hdl, websocketpp::frame::opcode::TEXT, con->get_uri()->get_resource());
123 
124  switch(str.type)
125  {
126  case server_reply::data_type::json: {
127  con->replace_header("Content-Type", "application/json; charset=utf-8");
128  str.data += "\0";
129  break;
130  }
131  case server_reply::data_type::html: {
132  con->replace_header("Content-Type", "text/html; charset=utf-8");
133  break;
134  }
135  default:
136  break;
137  }
138  con->replace_header("Connection", "close");
139  con->set_body(std::move(str.data));
140  con->set_status(websocketpp::http::status_code::ok);
141  }
142  catch(const ossia::node_not_found_error& e)
143  {
144  con->set_status(websocketpp::http::status_code::not_found);
145  }
146  catch(const ossia::bad_request_error& e)
147  {
148  ossia::logger().error(
149  "Error in request: {} ==> {}", con->get_uri()->get_resource(), e.what());
150  con->set_status(websocketpp::http::status_code::bad_request);
151  }
152  catch(const std::exception& e)
153  {
154  ossia::logger().error("Error in request: {}", e.what());
155  }
156  catch(...)
157  {
158  ossia::logger().error("Error in request");
159  }
160  });
161  }
162 
163  void listen(uint16_t port = 9002)
164  {
165  m_server.listen(boost::asio::ip::tcp::v4(), port);
166  m_server.start_accept();
167  }
168 
169  void run()
170  {
171  m_server.run();
172  }
173 
174  void stop()
175  {
176  // this change was undone because of OSSIA/libossia#416 :
177 
178  // // (temporarily?) changed to stop_listening()
179  // // "Straight up stop forcibly stops a bunch of things
180  // // in a way that bypasses most, if not all, of the cleanup routines"
181  // if(m_server.is_listening())
182  // m_server.stop_listening();
183 
184  m_server.stop();
185  }
186 
187  void close(connection_handler hdl)
188  {
189  auto con = m_server.get_con_from_hdl(hdl);
190  con->close(websocketpp::close::status::going_away, "Server shutdown");
191  }
192 
193  void send_message(connection_handler hdl, const std::string& message)
194  {
195  auto con = m_server.get_con_from_hdl(hdl);
196  con->send(message);
197  }
198 
199  void send_message(connection_handler hdl, const ossia::net::server_reply& message)
200  {
201  auto con = m_server.get_con_from_hdl(hdl);
202  switch(message.type)
203  {
204  case server_reply::data_type::json:
205  case server_reply::data_type::html:
206  con->send(message.data, websocketpp::frame::opcode::TEXT);
207  break;
208  default:
209  con->send(message.data, websocketpp::frame::opcode::BINARY);
210  break;
211  }
212  }
213 
214  void send_message(connection_handler hdl, const rapidjson::StringBuffer& message)
215  {
216  auto con = m_server.get_con_from_hdl(hdl);
217  con->send(message.GetString(), message.GetSize(), websocketpp::frame::opcode::text);
218  }
219 
220  void send_binary_message(connection_handler hdl, const std::string& message)
221  {
222  auto con = m_server.get_con_from_hdl(hdl);
223  con->send(message.data(), message.size(), websocketpp::frame::opcode::binary);
224  }
225 
226  void send_binary_message(connection_handler hdl, std::string_view message)
227  {
228  auto con = m_server.get_con_from_hdl(hdl);
229  con->send(message.data(), message.size(), websocketpp::frame::opcode::binary);
230  }
231 
232  server_t& impl()
233  {
234  return m_server;
235  }
236 
237 protected:
238  server_t m_server;
239 };
240 }
Low-level websocket & http server for oscquery.
Definition: websocket_server.hpp:20
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition: context.cpp:104
Used when a bad network request is done on a local server.
Definition: network/exceptions.hpp:72
The message struct.
Definition: message.hpp:29
Used when a requested node could not be found.
Definition: network/exceptions.hpp:60