Jamoma API  0.6.0.a19
TTWebSocket.cpp
1 /*
2  * @file TTWebSocket.cpp
3  * @group network library
4  * @brief Jamoma WebSocket server
5  * @see TTWebSend, TTWebReceive
6  *
7  * @copyright © 2013, Laurent Garnier
8  * License: This code is licensed under the terms of the "New BSD License"
9  * http://creativecommons.org/licenses/BSD/
10  *
11  * @details Use cross platform civetweb server from https://github.com/sunsetbrew/civetweb
12  * @details Some parts of this code are taken from ws_server example
13  */
14 
15 
16 #include "TTWebSocket.h"
17 #include <chrono>
18 #include <thread>
19 
20 // ws_server_thread()
21 static void *ws_server_thread(void *parm)
22 {
23  int wsd = (long)parm;
24  struct mg_connection *conn = ws_conn[wsd].conn;
25  int timer = 0;
26 
27  /* While the connection is open, send periodic updates */
28  while(!ws_conn[wsd].closing) {
29  std::this_thread::sleep_for(std::chrono::milliseconds(100)); /* 0.1 second */
30  timer++;
31 
32  /* Send periodic PING to assure websocket remains connected, except if we are closing */
33  if (timer%100 == 0 && !ws_conn[wsd].closing)
34  mg_websocket_write(conn, WEBSOCKET_OPCODE_PING, NULL, 0);
35  }
36 
37  // reset connection information to allow reuse by new client
38  ws_conn[wsd].conn = NULL;
39  ws_conn[wsd].update = 0;
40  ws_conn[wsd].closing = 2;
41 
42  return NULL;
43 }
44 
45 // websocket_connect_handler()
46 // On new client connection, find next available server connection and store
47 // new connection information. If no more server connections are available
48 // tell civetweb to not accept the client request.
49 static int websocket_connect_handler(const struct mg_connection *conn)
50 {
51  int i;
52 
53  fprintf(stderr, "websocket connect handler\n");
54 
55  for(i=0; i < CONNECTIONS; ++i) {
56  if (ws_conn[i].conn == NULL) {
57  ws_conn[i].conn = (struct mg_connection *)conn;
58  ws_conn[i].closing = 0;
59  ws_conn[i].update = 0;
60  break;
61  }
62  }
63  if (i >= CONNECTIONS) {
64  fprintf(stderr, "websocket refused connection: Max connections exceeded\n");
65  return 1;
66  }
67 
68  return 0;
69 }
70 
71 // websocket_ready_handler()
72 // Once websocket negotiation is complete, start a server for the connection
73 static void websocket_ready_handler(struct mg_connection *conn)
74 {
75  int i;
76 
77  fprintf(stderr, "websocket ready handler\n");
78 
79  for(i=0; i < CONNECTIONS; ++i) {
80  if (ws_conn[i].conn == conn) {
81  fprintf(stderr, "websocket start server %d\n", i);
82  mg_start_thread(ws_server_thread, (void *)(long)i);
83  break;
84  }
85  }
86 }
87 
88 // websocket_close_handler()
89 // When websocket is closed, tell the associated server to shut down
90 static void websocket_close_handler(struct mg_connection *conn)
91 {
92  int i;
93 
94  //fprintf(stderr, "close handler\n"); /* called for every close, not just websockets */
95 
96  for(i=0; i < CONNECTIONS; ++i) {
97  if (ws_conn[i].conn == conn) {
98  fprintf(stderr, "websocket close server %d\n", i);
99  ws_conn[i].closing = 1;
100  }
101  }
102 }
103 
104 // Arguments:
105 // flags: first byte of websocket frame, see websocket RFC,
106 // http://tools.ietf.org/html/rfc6455, section 5.2
107 // data, data_len: payload data. Mask, if any, is already applied.
108 static int websocket_data_handler(struct mg_connection *conn, int flags,
109  char *data, size_t data_len)
110 {
111  int i;
112  int wsd;
113  TTValue receivedMessage;
114 
115  for(i=0; i < CONNECTIONS; ++i) {
116  if (ws_conn[i].conn == conn) {
117  wsd = i;
118  break;
119  }
120  }
121  if (i >= CONNECTIONS) {
122  fprintf(stderr, "websocket received websocket data from unknown connection\n");
123  return 1;
124  }
125 
126  std::string s1 = data;
127  std::string s2 = "set";
128  std::string s3 = "get";
129  std::string s4 = "namespace";
130  std::string s5 = "listen";
131  std::size_t found1 = s1.find(s2);
132  std::size_t found2 = s1.find(s3);
133  std::size_t found3 = s1.find(s4);
134  std::size_t found4 = s1.find(s5);
135 
136  if (flags & 0x80) {
137  flags &= 0x7f;
138  switch (flags) {
139  case WEBSOCKET_OPCODE_CONTINUATION:
140  fprintf(stderr, "websocket receive CONTINUATION...\n");
141  break;
142  case WEBSOCKET_OPCODE_TEXT:
143  fprintf(stderr, "websocket receive : %-.*s\n", (int)data_len, data);
144 
145  // store this connection to send back data
146  mLastConnection = conn;
147 
148  // if it is an unknown message, send to other connected clients
149  if (found1 == std::string::npos && found2 == std::string::npos && found3 == std::string::npos && found4 == std::string::npos)
150  for(i=0; i < CONNECTIONS; ++i) {
151  if (ws_conn[i].conn && i != wsd)
152  mg_websocket_write(ws_conn[i].conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
153  }
154  else
155  {
156  // if it is a set message, send to the other clients
157  if (found1 != std::string::npos)
158  for(i=0; i < CONNECTIONS; ++i) {
159  if (ws_conn[i].conn && i != wsd)
160  mg_websocket_write(ws_conn[i].conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
161  }
162 
163  // add end character to received string
164  data[data_len] = '\0';
165 
166  // send received datas to JamomaModular WebSocket plugin
167  receivedMessage.clear();
168  receivedMessage = TTString(data);
169  ((TTWebSocketPtr)(mg_get_request_info(conn)->user_data))->mOwner->sendMessage(TTSymbol("WebSocketReceive"), receivedMessage, kTTValNONE);
170  }
171  break;
172  case WEBSOCKET_OPCODE_BINARY:
173  fprintf(stderr, "websocket receive BINARY...\n");
174  break;
175  case WEBSOCKET_OPCODE_CONNECTION_CLOSE:
176  fprintf(stderr, "websocket receive CLOSE...\n");
177  /* If client initiated close, respond with close message in acknowlegement */
178  if (!ws_conn[wsd].closing) {
179  mg_websocket_write(conn, WEBSOCKET_OPCODE_CONNECTION_CLOSE, data, data_len);
180  ws_conn[wsd].closing = 1; /* we should not send addional messages when close requested/acknowledged */
181  }
182  return 0; /* time to close the connection */
183  break;
184  case WEBSOCKET_OPCODE_PING:
185  /* client sent PING, respond with PONG */
186  mg_websocket_write(conn, WEBSOCKET_OPCODE_PONG, data, data_len);
187  break;
188  case WEBSOCKET_OPCODE_PONG:
189  /* received PONG to our PING, no action */
190  break;
191  default:
192  fprintf(stderr, "websocket Unknown flags: %02x\n", flags);
193  break;
194  }
195  }
196 
197  return 1; /* keep connection open */
198 }
199 
200 
201 TTWebSocket::TTWebSocket(const TTObjectBasePtr owner, const TTUInt16 port, const TTString& htmlPath)
202 {
203  mOwner = owner;
204  mPort = port;
205  mHtmlPath = htmlPath;
206 }
207 
209 {
210 }
211 
212 TTWebSocket::~TTWebSocket()
213 {
214  mg_stop(mContext);
215 }
216 
218 {
219  char portString[20];
220  sprintf(portString, "%d", mPort);
221 
222  char server_name[40];
223  struct mg_callbacks callbacks;
224  const char *options[] = {
225  "listening_ports", portString,
226  "document_root", mHtmlPath,
227  NULL
228  };
229 
230  /* get simple greeting for the web server */
231  snprintf(server_name, sizeof(server_name),
232  "Civetweb websocket server v. %s",
233  mg_version());
234 
235  memset(&callbacks, 0, sizeof(callbacks));
236  callbacks.websocket_connect = websocket_connect_handler;
237  callbacks.websocket_ready = websocket_ready_handler;
238  callbacks.websocket_data = websocket_data_handler;
239  callbacks.connection_close = websocket_close_handler;
240 
241  mContext = mg_start(&callbacks, this, options);
242 
243  if (mContext) {
244  /* show the greeting and some basic information */
245  printf("%s started on port(s) %s with web root [%s]\n",
246  server_name, mg_get_option(mContext, "listening_ports"),
247  mg_get_option(mContext, "document_root"));
248 
249  return kTTErrNone;
250  }
251 
252  return kTTErrGeneric;
253 }
254 
256 {
257  if (mLastConnection) {
258  int result = mg_websocket_write(mLastConnection, WEBSOCKET_OPCODE_TEXT, message.c_str(), message.string().size());
259  }
260  else {
261  TTLogError("WebSocket message not sent : %s\n", message.c_str());
262  return kTTErrGeneric;
263  }
264 
265  return kTTErrNone;
266 }
std::uint16_t TTUInt16
16 bit unsigned integer
Definition: TTBase.h:176
Create a websocket server.
Definition: TTWebSocket.h:47
TTWebSocket()
Create a socket to send websocket messages.
Base class for all first-class Jamoma objects.
Definition: TTObjectBase.h:109
The TTSymbol class is used to represent a string and efficiently pass and compare that string...
Definition: TTSymbol.h:26
TTObjectBasePtr mOwner
The object that instantiated this socket and will receive notifications.
Definition: TTWebSocket.h:52
void TTFOUNDATION_EXPORT TTLogError(TTImmutableCString message,...)
Platform and host independent method for posting errors.
Definition: TTBase.cpp:572
const char * c_str() const
Return a pointer to the internal string as a C-string.
Definition: TTSymbol.h:77
TTErr bind()
Bind websocket server.
void clear()
Clear all values from the vector, leaving with size of 0.
Definition: TTValue.h:131
Something went wrong, but what exactly is not known. Typically used for context-specific problems...
Definition: TTBase.h:344
TTErr
Jamoma Error Codes Enumeration of error codes that might be returned by any of the TTBlue functions a...
Definition: TTBase.h:342
size_t size() const
Find out the length of a string.
Definition: TTString.h:144
No Error.
Definition: TTBase.h:343
The TTString class is used to represent a string.
Definition: TTString.h:34
[doxygenAppendixC_copyExample]
Definition: TTValue.h:34
TTErr SendMessage(TTSymbol &message)
Send message.