Simple Web Game Server  0.1
A C++ library for creating authenticated scalable backends for multiplayer web games.
client.hpp
1 /*
2  * Copyright (c) 2020 Daniel Aven Bross
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 
23 #ifndef JWT_GAME_SERVER_BASE_CLIENT_HPP
24 #define JWT_GAME_SERVER_BASE_CLIENT_HPP
25 
26 #include <websocketpp/client.hpp>
27 
28 #include <jwt-cpp/jwt.h>
29 #include <spdlog/spdlog.h>
30 
31 #include <mutex>
32 #include <functional>
33 
34 #include <exception>
35 
36 namespace simple_web_game_server {
37  // websocketpp types
38  using websocketpp::connection_hdl;
39 
40  // functional types
41  using std::function;
42  using std::bind;
43  using std::placeholders::_1;
44  using std::placeholders::_2;
45 
46  // threading type implementations
47  using std::atomic;
48  using std::mutex;
49  using std::lock_guard;
50 
52  template<typename client_config>
53  class client {
54  // type definitions
55  private:
56  using ws_client = websocketpp::client<client_config>;
57  using message_ptr = typename ws_client::message_ptr;
58 
59  public:
61  class client_error : public std::runtime_error {
62  public:
63  using super = std::runtime_error;
64  explicit client_error(const std::string& what_arg) noexcept :
65  super(what_arg) {}
66  explicit client_error(const char* what_arg) noexcept : super(what_arg) {}
67  };
68 
69  // main class body
70  public:
72  client() : m_is_running{false}, m_has_failed{false},
73  m_handle_open{[](){}}, m_handle_close{[](){}},
74  m_handle_message{[](const std::string& s){}}
75  {
76  m_client.init_asio();
77 
78  m_client.set_open_handler(bind(&client::on_open, this,
79  simple_web_game_server::_1));
80  m_client.set_close_handler(bind(&client::on_close, this,
81  simple_web_game_server::_1));
82  m_client.set_message_handler(
83  bind(&client::on_message, this, simple_web_game_server::_1,
84  simple_web_game_server::_2)
85  );
86  }
87 
90  std::function<void()> of,
91  std::function<void()> cf,
92  std::function<void(const std::string&)> mf
93  ) : m_is_running{false}, m_has_failed{false},
94  m_handle_open{of}, m_handle_close{cf},
95  m_handle_message{mf}
96  {
97  m_client.init_asio();
98 
99  m_client.set_open_handler(bind(&client::on_open, this,
100  simple_web_game_server::_1));
101  m_client.set_close_handler(bind(&client::on_close, this,
102  simple_web_game_server::_1));
103  m_client.set_message_handler(
104  bind(&client::on_message, this, simple_web_game_server::_1,
105  simple_web_game_server::_2)
106  );
107  }
108 
109  client(const client& c) :
110  client{c.m_handle_open, c.m_handle_close, c.m_handle_message} {}
111 
113  void connect(const std::string& uri, const std::string& jwt) {
114  if(m_is_running) {
115  throw client_error("connect called on running client");
116  return;
117  }
118 
119  m_jwt = jwt;
120 
121  websocketpp::lib::error_code ec;
122  m_connection = m_client.get_connection(uri, ec);
123  if(ec) {
124  spdlog::debug(ec.message());
125  } else {
126  try {
127  m_client.connect(m_connection);
128  m_is_running = true;
129  m_has_failed = false;
130  m_client.run();
131  } catch(std::exception& e) {
132  m_is_running = false;
133  m_has_failed = true;
134  spdlog::error("error with client connection: {}", e.what());
135  }
136  }
137  }
138 
140  bool is_running() {
141  return m_is_running;
142  }
143 
145  bool has_failed() {
146  return m_has_failed;
147  }
148 
150  void disconnect() {
151  if(m_is_running) {
152  try {
153  spdlog::trace("closing client connection");
154  m_connection->close(
155  websocketpp::close::status::normal,
156  "client closed connection"
157  );
158  } catch(std::exception& e) {
159  spdlog::error("error closing client connection: {}", e.what());
160  }
161  } else {
162  throw client_error("disconnect called on stopped client");
163  }
164  }
165 
167  void reset() {
168  if(m_is_running) {
169  this->disconnect();
170  }
171  m_client.reset();
172  }
173 
175  void send(const std::string& msg) {
176  if(m_is_running) {
177  try {
178  m_connection->send(
179  msg,
180  websocketpp::frame::opcode::text
181  );
182  spdlog::debug("client sent message: {}", msg);
183  } catch(std::exception& e) {
184  spdlog::error("error sending client message \"{}\": {}", msg,
185  e.what());
186  }
187  } else {
188  throw client_error{
189  std::string{"send called on stopped client with message: "} +
190  msg
191  };
192  }
193  }
194 
196  void set_open_handler(std::function<void()> f) {
197  if(!m_is_running) {
198  m_handle_open = f;
199  } else {
200  throw client_error{"set_open_handler called on running client"};
201  }
202  }
203 
205  void set_close_handler(std::function<void()> f) {
206  if(!m_is_running) {
207  m_handle_close = f;
208  } else {
209  throw client_error{"set_close_handler called on running client"};
210  }
211  }
212 
214  void set_message_handler(std::function<void(const std::string&)> f) {
215  if(!m_is_running) {
216  m_handle_message = f;
217  } else {
218  throw client_error{"set_message_handler called on running client"};
219  }
220  }
221 
222  private:
223  void on_open(connection_hdl hdl) {
224  spdlog::trace("client connection opened");
225  this->send(m_jwt);
226  try {
227  m_handle_open();
228  } catch(std::exception& e) {
229  spdlog::error("error in open handler: {}", e.what());
230  }
231  }
232 
233  void on_close(connection_hdl hdl) {
234  spdlog::trace("client connection closed");
235  m_is_running = false;
236  try {
237  m_handle_close();
238  } catch(std::exception& e) {
239  spdlog::error("error in close handler: {}", e.what());
240  }
241  }
242 
243  void on_message(connection_hdl hdl, message_ptr msg) {
244  spdlog::trace("client received message: {}", msg->get_payload());
245  try {
246  m_handle_message(msg->get_payload());
247  } catch(std::exception& e) {
248  spdlog::error("error in message handler: {}", e.what());
249  }
250  }
251 
252  // member variables
253  ws_client m_client;
254  typename ws_client::connection_ptr m_connection;
255  bool m_is_running;
256  bool m_has_failed;
257  std::string m_jwt;
258  function<void()> m_handle_open;
259  function<void()> m_handle_close;
260  function<void(const std::string&)> m_handle_message;
261  };
262 }
263 
264 #endif // JWT_GAME_SERVER_BASE_CLIENT_HPP
The class representing errors with the client.
Definition: client.hpp:61
A simple WebSocket client to connect to an instance of base_server.
Definition: client.hpp:53
void set_close_handler(std::function< void()> f)
Sets the given function to be called when the client disconnects.
Definition: client.hpp:205
void disconnect()
Close the connection to the server.
Definition: client.hpp:150
client()
Constructs the client with empty handler functions.
Definition: client.hpp:72
bool has_failed()
Returns whether the connection has failed.
Definition: client.hpp:145
void set_message_handler(std::function< void(const std::string &)> f)
Sets the given function to be called when the client gets a message.
Definition: client.hpp:214
client(std::function< void()> of, std::function< void()> cf, std::function< void(const std::string &)> mf)
Constructs the client with the given handler functions.
Definition: client.hpp:89
void send(const std::string &msg)
Synchronously send the given string to the server.
Definition: client.hpp:175
bool is_running()
Returns whether the underlying WebSocket client is running.
Definition: client.hpp:140
void connect(const std::string &uri, const std::string &jwt)
Connects to a server at the given URI and sends the given string.
Definition: client.hpp:113
void set_open_handler(std::function< void()> f)
Sets the given function to be called when the client connects.
Definition: client.hpp:196
void reset()
Resets the client so it may connect to a new server.
Definition: client.hpp:167
Definition: base_server.hpp:52