websocket.c (12994B)
1 /* Feel free to use this example code in any way 2 you see fit (Public Domain) */ 3 4 #include <sys/types.h> 5 #ifndef _WIN32 6 #include <sys/select.h> 7 #include <sys/socket.h> 8 #include <fcntl.h> 9 #else 10 #include <winsock2.h> 11 #endif 12 #include <microhttpd.h> 13 #include <microhttpd_ws.h> 14 #include <time.h> 15 #include <string.h> 16 #include <stdlib.h> 17 #include <stdio.h> 18 #include <errno.h> 19 20 #define PORT 80 21 22 #define PAGE \ 23 "<!DOCTYPE html>\n" \ 24 "<html>\n" \ 25 "<head>\n" \ 26 "<meta charset=\"UTF-8\">\n" \ 27 "<title>Websocket Demo</title>\n" \ 28 "<script>\n" \ 29 "\n" \ 30 "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \ 31 " + ':/" "/' +\n" \ 32 " window.location.host + '/chat';\n" \ 33 "let socket = null;\n" \ 34 "\n" \ 35 "window.onload = function(event) {\n" \ 36 " socket = new WebSocket(url);\n" \ 37 " socket.onopen = function(event) {\n" \ 38 " document.write('The websocket connection has been " \ 39 "established.<br>');\n" \ 40 "\n" \ 41 " /" "/ Send some text\n" \ 42 " socket.send('Hello from JavaScript!');\n" \ 43 " }\n" \ 44 "\n" \ 45 " socket.onclose = function(event) {\n" \ 46 " document.write('The websocket connection has been closed.<br>');\n" \ 47 " }\n" \ 48 "\n" \ 49 " socket.onerror = function(event) {\n" \ 50 " document.write('An error occurred during the websocket " \ 51 "communication.<br>');\n" \ 52 " }\n" \ 53 "\n" \ 54 " socket.onmessage = function(event) {\n" \ 55 " document.write('Websocket message received: ' + " \ 56 "event.data + '<br>');\n" \ 57 " }\n" \ 58 "}\n" \ 59 "\n" \ 60 "</script>\n" \ 61 "</head>\n" \ 62 "<body>\n" \ 63 "</body>\n" \ 64 "</html>" 65 66 #define PAGE_NOT_FOUND \ 67 "404 Not Found" 68 69 #define PAGE_INVALID_WEBSOCKET_REQUEST \ 70 "Invalid WebSocket request!" 71 72 static void 73 send_all (MHD_socket fd, 74 const char *buf, 75 size_t len); 76 77 static void 78 make_blocking (MHD_socket fd); 79 80 static void 81 upgrade_handler (void *cls, 82 struct MHD_Connection *connection, 83 void *req_cls, 84 const char *extra_in, 85 size_t extra_in_size, 86 MHD_socket fd, 87 struct MHD_UpgradeResponseHandle *urh) 88 { 89 /* make the socket blocking (operating-system-dependent code) */ 90 make_blocking (fd); 91 92 /* create a websocket stream for this connection */ 93 struct MHD_WebSocketStream *ws; 94 int result = MHD_websocket_stream_init (&ws, 95 0, 96 0); 97 if (0 != result) 98 { 99 /* Couldn't create the websocket stream. 100 * So we close the socket and leave 101 */ 102 MHD_upgrade_action (urh, 103 MHD_UPGRADE_ACTION_CLOSE); 104 return; 105 } 106 107 /* Let's wait for incoming data */ 108 const size_t buf_len = 256; 109 char buf[buf_len]; 110 ssize_t got; 111 while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws)) 112 { 113 got = recv (fd, 114 buf, 115 buf_len, 116 0); 117 if (0 >= got) 118 { 119 /* the TCP/IP socket has been closed */ 120 break; 121 } 122 123 /* parse the entire received data */ 124 size_t buf_offset = 0; 125 while (buf_offset < (size_t) got) 126 { 127 size_t new_offset = 0; 128 char *frame_data = NULL; 129 size_t frame_len = 0; 130 int status = MHD_websocket_decode (ws, 131 buf + buf_offset, 132 ((size_t) got) - buf_offset, 133 &new_offset, 134 &frame_data, 135 &frame_len); 136 if (0 > status) 137 { 138 /* an error occurred and the connection must be closed */ 139 if (NULL != frame_data) 140 { 141 MHD_websocket_free (ws, frame_data); 142 } 143 break; 144 } 145 else 146 { 147 buf_offset += new_offset; 148 if (0 < status) 149 { 150 /* the frame is complete */ 151 switch (status) 152 { 153 case MHD_WEBSOCKET_STATUS_TEXT_FRAME: 154 /* The client has sent some text. 155 * We will display it and answer with a text frame. 156 */ 157 if (NULL != frame_data) 158 { 159 printf ("Received message: %s\n", frame_data); 160 MHD_websocket_free (ws, frame_data); 161 frame_data = NULL; 162 } 163 result = MHD_websocket_encode_text (ws, 164 "Hello", 165 5, /* length of "Hello" */ 166 0, 167 &frame_data, 168 &frame_len, 169 NULL); 170 if (0 == result) 171 { 172 send_all (fd, 173 frame_data, 174 frame_len); 175 } 176 break; 177 178 case MHD_WEBSOCKET_STATUS_CLOSE_FRAME: 179 /* if we receive a close frame, we will respond with one */ 180 MHD_websocket_free (ws, 181 frame_data); 182 frame_data = NULL; 183 184 result = MHD_websocket_encode_close (ws, 185 0, 186 NULL, 187 0, 188 &frame_data, 189 &frame_len); 190 if (0 == result) 191 { 192 send_all (fd, 193 frame_data, 194 frame_len); 195 } 196 break; 197 198 case MHD_WEBSOCKET_STATUS_PING_FRAME: 199 /* if we receive a ping frame, we will respond */ 200 /* with the corresponding pong frame */ 201 { 202 char *pong = NULL; 203 size_t pong_len = 0; 204 result = MHD_websocket_encode_pong (ws, 205 frame_data, 206 frame_len, 207 &pong, 208 &pong_len); 209 if (0 == result) 210 { 211 send_all (fd, 212 pong, 213 pong_len); 214 } 215 MHD_websocket_free (ws, 216 pong); 217 } 218 break; 219 220 default: 221 /* Other frame types are ignored 222 * in this minimal example. 223 * This is valid, because they become 224 * automatically skipped if we receive them unexpectedly 225 */ 226 break; 227 } 228 } 229 if (NULL != frame_data) 230 { 231 MHD_websocket_free (ws, frame_data); 232 } 233 } 234 } 235 } 236 237 /* free the websocket stream */ 238 MHD_websocket_stream_free (ws); 239 240 /* close the socket when it is not needed anymore */ 241 MHD_upgrade_action (urh, 242 MHD_UPGRADE_ACTION_CLOSE); 243 } 244 245 246 /* This helper function is used for the case that 247 * we need to resend some data 248 */ 249 static void 250 send_all (MHD_socket fd, 251 const char *buf, 252 size_t len) 253 { 254 ssize_t ret; 255 size_t off; 256 257 for (off = 0; off < len; off += ret) 258 { 259 ret = send (fd, 260 &buf[off], 261 (int) (len - off), 262 0); 263 if (0 > ret) 264 { 265 if (EAGAIN == errno) 266 { 267 ret = 0; 268 continue; 269 } 270 break; 271 } 272 if (0 == ret) 273 break; 274 } 275 } 276 277 278 /* This helper function contains operating-system-dependent code and 279 * is used to make a socket blocking. 280 */ 281 static void 282 make_blocking (MHD_socket fd) 283 { 284 #ifndef _WIN32 285 int flags; 286 287 flags = fcntl (fd, F_GETFL); 288 if (-1 == flags) 289 abort (); 290 if ((flags & ~O_NONBLOCK) != flags) 291 if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) 292 abort (); 293 #else /* _WIN32 */ 294 unsigned long flags = 0; 295 296 if (0 != ioctlsocket (fd, (int) FIONBIO, &flags)) 297 abort (); 298 #endif /* _WIN32 */ 299 } 300 301 302 static enum MHD_Result 303 access_handler (void *cls, 304 struct MHD_Connection *connection, 305 const char *url, 306 const char *method, 307 const char *version, 308 const char *upload_data, 309 size_t *upload_data_size, 310 void **req_cls) 311 { 312 static int aptr; 313 struct MHD_Response *response; 314 int ret; 315 316 (void) cls; /* Unused. Silent compiler warning. */ 317 (void) upload_data; /* Unused. Silent compiler warning. */ 318 (void) upload_data_size; /* Unused. Silent compiler warning. */ 319 320 if (0 != strcmp (method, "GET")) 321 return MHD_NO; /* unexpected method */ 322 if (&aptr != *req_cls) 323 { 324 /* do never respond on first call */ 325 *req_cls = &aptr; 326 return MHD_YES; 327 } 328 *req_cls = NULL; /* reset when done */ 329 330 if (0 == strcmp (url, "/")) 331 { 332 /* Default page for visiting the server */ 333 struct MHD_Response *response; 334 response = MHD_create_response_from_buffer_static (strlen (PAGE), 335 PAGE); 336 ret = MHD_queue_response (connection, 337 MHD_HTTP_OK, 338 response); 339 MHD_destroy_response (response); 340 } 341 else if (0 == strcmp (url, "/chat")) 342 { 343 char is_valid = 1; 344 const char *value = NULL; 345 char sec_websocket_accept[29]; 346 347 if (0 != MHD_websocket_check_http_version (version)) 348 { 349 is_valid = 0; 350 } 351 value = MHD_lookup_connection_value (connection, 352 MHD_HEADER_KIND, 353 MHD_HTTP_HEADER_CONNECTION); 354 if (0 != MHD_websocket_check_connection_header (value)) 355 { 356 is_valid = 0; 357 } 358 value = MHD_lookup_connection_value (connection, 359 MHD_HEADER_KIND, 360 MHD_HTTP_HEADER_UPGRADE); 361 if (0 != MHD_websocket_check_upgrade_header (value)) 362 { 363 is_valid = 0; 364 } 365 value = MHD_lookup_connection_value (connection, 366 MHD_HEADER_KIND, 367 MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); 368 if (0 != MHD_websocket_check_version_header (value)) 369 { 370 is_valid = 0; 371 } 372 value = MHD_lookup_connection_value (connection, 373 MHD_HEADER_KIND, 374 MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); 375 if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept)) 376 { 377 is_valid = 0; 378 } 379 380 if (1 == is_valid) 381 { 382 /* upgrade the connection */ 383 response = MHD_create_response_for_upgrade (&upgrade_handler, 384 NULL); 385 MHD_add_response_header (response, 386 MHD_HTTP_HEADER_UPGRADE, 387 "websocket"); 388 MHD_add_response_header (response, 389 MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, 390 sec_websocket_accept); 391 ret = MHD_queue_response (connection, 392 MHD_HTTP_SWITCHING_PROTOCOLS, 393 response); 394 MHD_destroy_response (response); 395 } 396 else 397 { 398 /* return error page */ 399 struct MHD_Response *response; 400 response = 401 MHD_create_response_from_buffer_static (strlen ( 402 PAGE_INVALID_WEBSOCKET_REQUEST), 403 PAGE_INVALID_WEBSOCKET_REQUEST); 404 ret = MHD_queue_response (connection, 405 MHD_HTTP_BAD_REQUEST, 406 response); 407 MHD_destroy_response (response); 408 } 409 } 410 else 411 { 412 struct MHD_Response *response; 413 response = 414 MHD_create_response_from_buffer_static (strlen (PAGE_NOT_FOUND), 415 PAGE_NOT_FOUND); 416 ret = MHD_queue_response (connection, 417 MHD_HTTP_NOT_FOUND, 418 response); 419 MHD_destroy_response (response); 420 } 421 422 return ret; 423 } 424 425 426 int 427 main (int argc, 428 char *const *argv) 429 { 430 (void) argc; /* Unused. Silent compiler warning. */ 431 (void) argv; /* Unused. Silent compiler warning. */ 432 struct MHD_Daemon *daemon; 433 434 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD 435 | MHD_USE_THREAD_PER_CONNECTION 436 | MHD_ALLOW_UPGRADE 437 | MHD_USE_ERROR_LOG, 438 PORT, NULL, NULL, 439 &access_handler, NULL, 440 MHD_OPTION_END); 441 442 if (NULL == daemon) 443 return 1; 444 (void) getc (stdin); 445 446 MHD_stop_daemon (daemon); 447 448 return 0; 449 }