libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

websocket.c (13250B)


      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 }