embedded_c.c 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. /*
  2. * Copyright (c) 2013-2020 the CivetWeb developers
  3. * Copyright (c) 2013 No Face Press, LLC
  4. * License http://opensource.org/licenses/mit-license.php MIT License
  5. */
  6. #ifdef NO_SSL
  7. #define TEST_WITHOUT_SSL
  8. #undef USE_SSL_DH
  9. #endif
  10. /* Simple example program on how to use CivetWeb embedded into a C program. */
  11. #ifdef _WIN32
  12. #include <windows.h>
  13. #else
  14. #include <unistd.h>
  15. #endif
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include <time.h>
  19. #include "civetweb.h"
  20. #define DOCUMENT_ROOT "."
  21. #ifdef TEST_WITHOUT_SSL
  22. #ifdef USE_IPV6
  23. #define PORT "[::]:8888,8884"
  24. #else
  25. #define PORT "8888,8884"
  26. #endif
  27. #else
  28. #ifdef USE_IPV6
  29. #define PORT "[::]:8888r,[::]:8843s,8884"
  30. #else
  31. #define PORT "8888r,8843s,8884"
  32. #endif
  33. #endif
  34. #define EXAMPLE_URI "/example"
  35. #define EXIT_URI "/exit"
  36. volatile int exitNow = 0;
  37. int
  38. ExampleHandler(struct mg_connection *conn, void *cbdata)
  39. {
  40. mg_printf(conn,
  41. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  42. "close\r\n\r\n");
  43. mg_printf(conn, "<html><body>");
  44. mg_printf(conn, "<h2>This is an example text from a C handler</h2>");
  45. mg_printf(
  46. conn,
  47. "<p>To see a page from the A handler <a href=\"A\">click A</a></p>");
  48. mg_printf(conn,
  49. "<p>To see a page from the A handler <a href=\"A/A\">click "
  50. "A/A</a></p>");
  51. mg_printf(conn,
  52. "<p>To see a page from the A/B handler <a "
  53. "href=\"A/B\">click A/B</a></p>");
  54. mg_printf(conn,
  55. "<p>To see a page from the B handler (0) <a "
  56. "href=\"B\">click B</a></p>");
  57. mg_printf(conn,
  58. "<p>To see a page from the B handler (1) <a "
  59. "href=\"B/A\">click B/A</a></p>");
  60. mg_printf(conn,
  61. "<p>To see a page from the B handler (2) <a "
  62. "href=\"B/B\">click B/B</a></p>");
  63. mg_printf(conn,
  64. "<p>To see a page from the *.foo handler <a "
  65. "href=\"xy.foo\">click xy.foo</a></p>");
  66. mg_printf(conn,
  67. "<p>To see a page from the close handler <a "
  68. "href=\"close\">click close</a></p>");
  69. mg_printf(conn,
  70. "<p>To see a page from the FileHandler handler <a "
  71. "href=\"form\">click form</a> (the starting point of the "
  72. "<b>form</b> test)</p>");
  73. mg_printf(conn,
  74. "<p>To see a page from the CookieHandler handler <a "
  75. "href=\"cookie\">click cookie</a></p>");
  76. mg_printf(conn,
  77. "<p>To see a page from the PostResponser handler <a "
  78. "href=\"postresponse\">click post response</a></p>");
  79. mg_printf(conn,
  80. "<p>To see an example for parsing files on the fly <a "
  81. "href=\"on_the_fly_form\">click form</a> (form for "
  82. "uploading files)</p>");
  83. #ifdef USE_WEBSOCKET
  84. mg_printf(conn,
  85. "<p>To test the websocket handler <a href=\"/websocket\">click "
  86. "websocket</a></p>");
  87. #endif
  88. mg_printf(conn,
  89. "<p>To test the authentication handler <a href=\"/auth\">click "
  90. "auth</a></p>");
  91. mg_printf(conn, "<p>To exit <a href=\"%s\">click exit</a></p>", EXIT_URI);
  92. mg_printf(conn, "</body></html>\n");
  93. return 1;
  94. }
  95. int
  96. ExitHandler(struct mg_connection *conn, void *cbdata)
  97. {
  98. mg_printf(conn,
  99. "HTTP/1.1 200 OK\r\nContent-Type: "
  100. "text/plain\r\nConnection: close\r\n\r\n");
  101. mg_printf(conn, "Server will shut down.\n");
  102. mg_printf(conn, "Bye!\n");
  103. exitNow = 1;
  104. return 1;
  105. }
  106. int
  107. AHandler(struct mg_connection *conn, void *cbdata)
  108. {
  109. mg_printf(conn,
  110. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  111. "close\r\n\r\n");
  112. mg_printf(conn, "<html><body>");
  113. mg_printf(conn, "<h2>This is the A handler.</h2>");
  114. mg_printf(conn, "</body></html>\n");
  115. return 1;
  116. }
  117. int
  118. ABHandler(struct mg_connection *conn, void *cbdata)
  119. {
  120. const struct mg_request_info *ri = mg_get_request_info(conn);
  121. mg_printf(conn,
  122. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  123. "close\r\n\r\n");
  124. mg_printf(conn, "<html><body>");
  125. mg_printf(conn, "<h2>This is the AB handler.</h2>");
  126. mg_printf(conn, "<ul>\n");
  127. mg_printf(conn, "<li>request_method = %s</li>\n", ri->request_method);
  128. mg_printf(conn, "<li>request_uri = %s</li>\n", ri->request_uri);
  129. mg_printf(conn, "<li>local_uri = %s</li>\n", ri->local_uri);
  130. mg_printf(conn, "<li>http_version = %s</li>\n", ri->http_version);
  131. mg_printf(conn, "<li>query_string = %s</li>\n", ri->query_string);
  132. mg_printf(conn, "<li>remote_user = %s</li>\n", ri->remote_user);
  133. mg_printf(conn, "<li>remote_addr = %s</li>\n", ri->remote_addr);
  134. mg_printf(conn, "<li>remote_port = %u</li>\n", ri->remote_port);
  135. mg_printf(conn, "<li>is_ssl = %i</li>\n", ri->is_ssl);
  136. mg_printf(conn, "<li>num_headers = %i</li>\n", ri->num_headers);
  137. if (ri->num_headers > 0) {
  138. int i;
  139. mg_printf(conn, "<ol>\n");
  140. for (i = 0; i < ri->num_headers; i++) {
  141. mg_printf(conn,
  142. "<li>%s = %s</li>\n",
  143. ri->http_headers[i].name,
  144. ri->http_headers[i].value);
  145. }
  146. mg_printf(conn, "</ol>\n");
  147. }
  148. mg_printf(conn, "</ul>\n");
  149. mg_printf(conn, "</body></html>\n");
  150. return 1;
  151. }
  152. int
  153. BXHandler(struct mg_connection *conn, void *cbdata)
  154. {
  155. /* Handler may access the request info using mg_get_request_info */
  156. const struct mg_request_info *req_info = mg_get_request_info(conn);
  157. const char *text = (const char *)cbdata;
  158. mg_printf(conn,
  159. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  160. "close\r\n\r\n");
  161. mg_printf(conn, "<html><body>");
  162. mg_printf(conn, "<h2>This is the BX handler with argument %s.</h2>", text);
  163. mg_printf(conn, "<p>The actual uri is %s</p>", req_info->local_uri);
  164. mg_printf(conn, "</body></html>\n");
  165. return 1;
  166. }
  167. int
  168. FooHandler(struct mg_connection *conn, void *cbdata)
  169. {
  170. /* Handler may access the request info using mg_get_request_info */
  171. const struct mg_request_info *req_info = mg_get_request_info(conn);
  172. mg_printf(conn,
  173. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  174. "close\r\n\r\n");
  175. mg_printf(conn, "<html><body>");
  176. mg_printf(conn, "<h2>This is the Foo handler!!!</h2>");
  177. mg_printf(conn,
  178. "<p>The request was:<br><pre>%s %s HTTP/%s</pre></p>",
  179. req_info->request_method,
  180. req_info->local_uri,
  181. req_info->http_version);
  182. mg_printf(conn, "</body></html>\n");
  183. return 1;
  184. }
  185. int
  186. CloseHandler(struct mg_connection *conn, void *cbdata)
  187. {
  188. /* Handler may access the request info using mg_get_request_info */
  189. const struct mg_request_info *req_info = mg_get_request_info(conn);
  190. mg_printf(conn,
  191. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  192. "close\r\n\r\n");
  193. mg_printf(conn, "<html><body>");
  194. mg_printf(conn,
  195. "<h2>This handler will close the connection in a second</h2>");
  196. #ifdef _WIN32
  197. Sleep(1000);
  198. #else
  199. sleep(1);
  200. #endif
  201. mg_printf(conn, "bye");
  202. printf("CloseHandler: close connection\n");
  203. mg_close_connection(conn);
  204. printf("CloseHandler: wait 10 sec\n");
  205. #ifdef _WIN32
  206. Sleep(10000);
  207. #else
  208. sleep(10);
  209. #endif
  210. printf("CloseHandler: return from function\n");
  211. return 1;
  212. }
  213. #if !defined(NO_FILESYSTEMS)
  214. int
  215. FileHandler(struct mg_connection *conn, void *cbdata)
  216. {
  217. /* In this handler, we ignore the req_info and send the file "fileName". */
  218. const char *fileName = (const char *)cbdata;
  219. mg_send_file(conn, fileName);
  220. return 1;
  221. }
  222. #endif /* NO_FILESYSTEMS */
  223. #define MD5_STATIC static
  224. #include "../src/md5.inl"
  225. /* Stringify binary data. Output buffer must be twice as big as input,
  226. * because each byte takes 2 bytes in string representation */
  227. static void
  228. bin2str(char *to, const unsigned char *p, size_t len)
  229. {
  230. static const char *hex = "0123456789abcdef";
  231. for (; len--; p++) {
  232. *to++ = hex[p[0] >> 4];
  233. *to++ = hex[p[0] & 0x0f];
  234. }
  235. *to = '\0';
  236. }
  237. int
  238. field_found(const char *key,
  239. const char *filename,
  240. char *path,
  241. size_t pathlen,
  242. void *user_data)
  243. {
  244. struct mg_connection *conn = (struct mg_connection *)user_data;
  245. mg_printf(conn, "\r\n\r\n%s:\r\n", key);
  246. if (filename && *filename) {
  247. #ifdef _WIN32
  248. _snprintf(path, pathlen, "D:\\tmp\\%s", filename);
  249. #else
  250. snprintf(path, pathlen, "/tmp/%s", filename);
  251. #endif
  252. return MG_FORM_FIELD_STORAGE_STORE;
  253. }
  254. return MG_FORM_FIELD_STORAGE_GET;
  255. }
  256. int
  257. field_get(const char *key, const char *value, size_t valuelen, void *user_data)
  258. {
  259. struct mg_connection *conn = (struct mg_connection *)user_data;
  260. if ((key != NULL) && (key[0] == '\0')) {
  261. /* Incorrect form data detected */
  262. return MG_FORM_FIELD_HANDLE_ABORT;
  263. }
  264. if ((valuelen > 0) && (value == NULL)) {
  265. /* Unreachable, since this call will not be generated by civetweb. */
  266. return MG_FORM_FIELD_HANDLE_ABORT;
  267. }
  268. if (key) {
  269. mg_printf(conn, "key = %s\n", key);
  270. }
  271. mg_printf(conn, "valuelen = %lu\n", valuelen);
  272. if (valuelen > 0) {
  273. /* mg_write(conn, value, valuelen); */
  274. md5_byte_t hash[16];
  275. md5_state_t ctx;
  276. char outputbuf[33];
  277. md5_init(&ctx);
  278. md5_append(&ctx, (const md5_byte_t *)value, valuelen);
  279. md5_finish(&ctx, hash);
  280. bin2str(outputbuf, hash, sizeof(hash));
  281. mg_printf(conn, "value md5 hash = %s\n", outputbuf);
  282. }
  283. #if 0 /* for debugging */
  284. if (!strcmp(key, "File")) {
  285. FILE *f = fopen("test.txt", "wb");
  286. if (f) {
  287. fwrite(value, 1, valuelen, f);
  288. fclose(f);
  289. }
  290. }
  291. #endif
  292. return 0;
  293. }
  294. int
  295. field_stored(const char *path, long long file_size, void *user_data)
  296. {
  297. struct mg_connection *conn = (struct mg_connection *)user_data;
  298. mg_printf(conn,
  299. "stored as %s (%lu bytes)\r\n\r\n",
  300. path,
  301. (unsigned long)file_size);
  302. return 0;
  303. }
  304. int
  305. FormHandler(struct mg_connection *conn, void *cbdata)
  306. {
  307. /* Handler may access the request info using mg_get_request_info */
  308. const struct mg_request_info *req_info = mg_get_request_info(conn);
  309. int ret;
  310. struct mg_form_data_handler fdh = {field_found, field_get, field_stored, 0};
  311. /* It would be possible to check the request info here before calling
  312. * mg_handle_form_request. */
  313. (void)req_info;
  314. mg_printf(conn,
  315. "HTTP/1.1 200 OK\r\nContent-Type: "
  316. "text/plain\r\nConnection: close\r\n\r\n");
  317. fdh.user_data = (void *)conn;
  318. /* Call the form handler */
  319. mg_printf(conn, "Form data:");
  320. ret = mg_handle_form_request(conn, &fdh);
  321. mg_printf(conn, "\r\n%i fields found", ret);
  322. return 1;
  323. }
  324. int
  325. FileUploadForm(struct mg_connection *conn, void *cbdata)
  326. {
  327. mg_printf(conn,
  328. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  329. "close\r\n\r\n");
  330. mg_printf(conn, "<!DOCTYPE html>\n");
  331. mg_printf(conn, "<html>\n<head>\n");
  332. mg_printf(conn, "<meta charset=\"UTF-8\">\n");
  333. mg_printf(conn, "<title>File upload</title>\n");
  334. mg_printf(conn, "</head>\n<body>\n");
  335. mg_printf(conn,
  336. "<form action=\"%s\" method=\"POST\" "
  337. "enctype=\"multipart/form-data\">\n",
  338. (const char *)cbdata);
  339. mg_printf(conn, "<input type=\"file\" name=\"filesin\" multiple>\n");
  340. mg_printf(conn, "<input type=\"submit\" value=\"Submit\">\n");
  341. mg_printf(conn, "</form>\n</body>\n</html>\n");
  342. return 1;
  343. }
  344. struct tfile_checksum {
  345. char name[128];
  346. unsigned long long length;
  347. md5_state_t chksum;
  348. };
  349. #define MAX_FILES (10)
  350. struct tfiles_checksums {
  351. int index;
  352. struct tfile_checksum file[MAX_FILES];
  353. };
  354. int
  355. field_disp_read_on_the_fly(const char *key,
  356. const char *filename,
  357. char *path,
  358. size_t pathlen,
  359. void *user_data)
  360. {
  361. struct tfiles_checksums *context = (struct tfiles_checksums *)user_data;
  362. (void)key;
  363. (void)path;
  364. (void)pathlen;
  365. if (context->index < MAX_FILES) {
  366. context->index++;
  367. strncpy(context->file[context->index - 1].name, filename, 128);
  368. context->file[context->index - 1].name[127] = 0;
  369. context->file[context->index - 1].length = 0;
  370. md5_init(&(context->file[context->index - 1].chksum));
  371. return MG_FORM_FIELD_STORAGE_GET;
  372. }
  373. return MG_FORM_FIELD_STORAGE_ABORT;
  374. }
  375. int
  376. field_get_checksum(const char *key,
  377. const char *value,
  378. size_t valuelen,
  379. void *user_data)
  380. {
  381. struct tfiles_checksums *context = (struct tfiles_checksums *)user_data;
  382. (void)key;
  383. context->file[context->index - 1].length += valuelen;
  384. md5_append(&(context->file[context->index - 1].chksum),
  385. (const md5_byte_t *)value,
  386. valuelen);
  387. return 0;
  388. }
  389. int
  390. CheckSumHandler(struct mg_connection *conn, void *cbdata)
  391. {
  392. /* Handler may access the request info using mg_get_request_info */
  393. const struct mg_request_info *req_info = mg_get_request_info(conn);
  394. int i, j, ret;
  395. struct tfiles_checksums chksums;
  396. md5_byte_t digest[16];
  397. struct mg_form_data_handler fdh = {field_disp_read_on_the_fly,
  398. field_get_checksum,
  399. 0,
  400. (void *)&chksums};
  401. /* It would be possible to check the request info here before calling
  402. * mg_handle_form_request. */
  403. (void)req_info;
  404. memset(&chksums, 0, sizeof(chksums));
  405. mg_printf(conn,
  406. "HTTP/1.1 200 OK\r\n"
  407. "Content-Type: text/plain\r\n"
  408. "Connection: close\r\n\r\n");
  409. /* Call the form handler */
  410. mg_printf(conn, "File checksums:");
  411. ret = mg_handle_form_request(conn, &fdh);
  412. for (i = 0; i < chksums.index; i++) {
  413. md5_finish(&(chksums.file[i].chksum), digest);
  414. /* Visual Studio 2010+ support llu */
  415. mg_printf(conn,
  416. "\r\n%s %llu ",
  417. chksums.file[i].name,
  418. chksums.file[i].length);
  419. for (j = 0; j < 16; j++) {
  420. mg_printf(conn, "%02x", (unsigned int)digest[j]);
  421. }
  422. }
  423. mg_printf(conn, "\r\n%i files\r\n", ret);
  424. return 1;
  425. }
  426. int
  427. CookieHandler(struct mg_connection *conn, void *cbdata)
  428. {
  429. /* Handler may access the request info using mg_get_request_info */
  430. const struct mg_request_info *req_info = mg_get_request_info(conn);
  431. const char *cookie = mg_get_header(conn, "Cookie");
  432. char first_str[64], count_str[64];
  433. int count;
  434. (void)mg_get_cookie(cookie, "first", first_str, sizeof(first_str));
  435. (void)mg_get_cookie(cookie, "count", count_str, sizeof(count_str));
  436. mg_printf(conn, "HTTP/1.1 200 OK\r\nConnection: close\r\n");
  437. if (first_str[0] == 0) {
  438. time_t t = time(0);
  439. struct tm *ptm = localtime(&t);
  440. mg_printf(conn,
  441. "Set-Cookie: first=%04i-%02i-%02iT%02i:%02i:%02i\r\n",
  442. ptm->tm_year + 1900,
  443. ptm->tm_mon + 1,
  444. ptm->tm_mday,
  445. ptm->tm_hour,
  446. ptm->tm_min,
  447. ptm->tm_sec);
  448. }
  449. count = (count_str[0] == 0) ? 0 : atoi(count_str);
  450. mg_printf(conn, "Set-Cookie: count=%i\r\n", count + 1);
  451. mg_printf(conn, "Content-Type: text/html\r\n\r\n");
  452. mg_printf(conn, "<html><body>");
  453. mg_printf(conn, "<h2>This is the CookieHandler.</h2>");
  454. mg_printf(conn, "<p>The actual uri is %s</p>", req_info->local_uri);
  455. if (first_str[0] == 0) {
  456. mg_printf(conn, "<p>This is the first time, you opened this page</p>");
  457. } else {
  458. mg_printf(conn, "<p>You opened this page %i times before.</p>", count);
  459. mg_printf(conn, "<p>You first opened this page on %s.</p>", first_str);
  460. }
  461. mg_printf(conn, "</body></html>\n");
  462. return 1;
  463. }
  464. int
  465. PostResponser(struct mg_connection *conn, void *cbdata)
  466. {
  467. long long r_total = 0;
  468. int r, s;
  469. char buf[2048];
  470. const struct mg_request_info *ri = mg_get_request_info(conn);
  471. if (0 != strcmp(ri->request_method, "POST")) {
  472. /* Not a POST request */
  473. char buf[1024];
  474. int ret = mg_get_request_link(conn, buf, sizeof(buf));
  475. mg_printf(conn,
  476. "HTTP/1.1 405 Method Not Allowed\r\nConnection: close\r\n");
  477. mg_printf(conn, "Content-Type: text/plain\r\n\r\n");
  478. mg_printf(conn,
  479. "%s method not allowed in the POST handler\n",
  480. ri->request_method);
  481. if (ret >= 0) {
  482. mg_printf(conn,
  483. "use a web tool to send a POST request to %s\n",
  484. buf);
  485. }
  486. return 1;
  487. }
  488. if (ri->content_length >= 0) {
  489. /* We know the content length in advance */
  490. } else {
  491. /* We must read until we find the end (chunked encoding
  492. * or connection close), indicated my mg_read returning 0 */
  493. }
  494. mg_printf(conn,
  495. "HTTP/1.1 200 OK\r\nConnection: "
  496. "close\r\nTransfer-Encoding: chunked\r\n");
  497. mg_printf(conn, "Content-Type: text/plain\r\n\r\n");
  498. r = mg_read(conn, buf, sizeof(buf));
  499. while (r > 0) {
  500. r_total += r;
  501. s = mg_send_chunk(conn, buf, r);
  502. if (r != s) {
  503. /* Send error */
  504. break;
  505. }
  506. r = mg_read(conn, buf, sizeof(buf));
  507. }
  508. mg_printf(conn, "0\r\n");
  509. return 1;
  510. }
  511. #if !defined(NO_FILESYSTEMS)
  512. int
  513. AuthStartHandler(struct mg_connection *conn, void *cbdata)
  514. {
  515. static unsigned long long firstload = 0;
  516. const char *passfile = "password_example_file.txt";
  517. const char *realm = "password_example";
  518. const char *user = "user";
  519. char passwd[64];
  520. if (firstload == 0) {
  521. /* Set a random password (4 digit number - bad idea from a security
  522. * point of view, but this is an API demo, not a security tutorial),
  523. * and store it in some directory within the document root (extremely
  524. * bad idea, but this is still not a security tutorial).
  525. * The reason we create a new password every time the server starts
  526. * is just for demonstration - we don't want the browser to store the
  527. * password, so when we repeat the test we start with a new password.
  528. */
  529. firstload = (unsigned long long)time(NULL);
  530. sprintf(passwd, "%04u", (unsigned int)(firstload % 10000));
  531. mg_modify_passwords_file(passfile, realm, user, passwd);
  532. /* Just tell the user the new password generated for this test. */
  533. mg_printf(conn,
  534. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  535. "close\r\n\r\n");
  536. mg_printf(conn, "<!DOCTYPE html>\n");
  537. mg_printf(conn, "<html>\n<head>\n");
  538. mg_printf(conn, "<meta charset=\"UTF-8\">\n");
  539. mg_printf(conn, "<title>Auth handlerexample</title>\n");
  540. mg_printf(conn, "</head>\n");
  541. mg_printf(conn, "<body>\n");
  542. mg_printf(conn,
  543. "<p>The first time you visit this page, it's free!</p>\n");
  544. mg_printf(conn,
  545. "<p>Next time, use username \"%s\" and password \"%s\"</p>\n",
  546. user,
  547. passwd);
  548. mg_printf(conn, "</body>\n</html>\n");
  549. return 1;
  550. }
  551. if (mg_check_digest_access_authentication(conn, realm, passfile) <= 0) {
  552. /* No valid authorization */
  553. mg_send_digest_access_authentication_request(conn, realm);
  554. return 1;
  555. }
  556. mg_printf(conn,
  557. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  558. "close\r\n\r\n");
  559. mg_printf(conn, "<!DOCTYPE html>\n");
  560. mg_printf(conn, "<html>\n<head>\n");
  561. mg_printf(conn, "<meta charset=\"UTF-8\">\n");
  562. mg_printf(conn, "<title>Auth handlerexample</title>\n");
  563. mg_printf(conn, "</head>\n");
  564. mg_printf(conn, "<body>\n");
  565. mg_printf(conn, "<p>This is the password protected contents</p>\n");
  566. mg_printf(conn, "</body>\n</html>\n");
  567. return 1;
  568. }
  569. #endif /* NO_FILESYSTEMS */
  570. int
  571. WebSocketStartHandler(struct mg_connection *conn, void *cbdata)
  572. {
  573. mg_printf(conn,
  574. "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: "
  575. "close\r\n\r\n");
  576. mg_printf(conn, "<!DOCTYPE html>\n");
  577. mg_printf(conn, "<html>\n<head>\n");
  578. mg_printf(conn, "<meta charset=\"UTF-8\">\n");
  579. mg_printf(conn, "<title>Embedded websocket example</title>\n");
  580. #ifdef USE_WEBSOCKET
  581. /* mg_printf(conn, "<script type=\"text/javascript\"><![CDATA[\n"); ...
  582. * xhtml style */
  583. mg_printf(conn, "<script>\n");
  584. mg_printf(
  585. conn,
  586. "function load() {\n"
  587. " var wsproto = (location.protocol === 'https:') ? 'wss:' : 'ws:';\n"
  588. " connection = new WebSocket(wsproto + '//' + window.location.host + "
  589. "'/websocket');\n"
  590. " websock_text_field = "
  591. "document.getElementById('websock_text_field');\n"
  592. " connection.onmessage = function (e) {\n"
  593. " websock_text_field.innerHTML=e.data;\n"
  594. " }\n"
  595. " connection.onerror = function (error) {\n"
  596. " alert('WebSocket error');\n"
  597. " connection.close();\n"
  598. " }\n"
  599. "}\n");
  600. /* mg_printf(conn, "]]></script>\n"); ... xhtml style */
  601. mg_printf(conn, "</script>\n");
  602. mg_printf(conn, "</head>\n<body onload=\"load()\">\n");
  603. mg_printf(
  604. conn,
  605. "<div id='websock_text_field'>No websocket connection yet</div>\n");
  606. #else
  607. mg_printf(conn, "</head>\n<body>\n");
  608. mg_printf(conn, "Example not compiled with USE_WEBSOCKET\n");
  609. #endif
  610. mg_printf(conn, "</body>\n</html>\n");
  611. return 1;
  612. }
  613. #ifdef USE_WEBSOCKET
  614. /* MAX_WS_CLIENTS defines how many clients can connect to a websocket at the
  615. * same time. The value 5 is very small and used here only for demonstration;
  616. * it can be easily tested to connect more than MAX_WS_CLIENTS clients.
  617. * A real server should use a much higher number, or better use a dynamic list
  618. * of currently connected websocket clients. */
  619. #define MAX_WS_CLIENTS (5)
  620. struct t_ws_client {
  621. /* Handle to the connection, used for mg_read/mg_write */
  622. struct mg_connection *conn;
  623. /*
  624. WebSocketConnectHandler sets state to 1 ("connected")
  625. the connect handler can accept or reject a connection, but it cannot
  626. send or receive any data at this state
  627. WebSocketReadyHandler sets state to 2 ("ready")
  628. reading and writing is possible now
  629. WebSocketCloseHandler sets state to 0
  630. the websocket is about to be closed, reading and writing is no longer
  631. possible this callback can be used to cleanup allocated resources
  632. InformWebsockets is called cyclic every second, and sends some data
  633. (a counter value) to all websockets in state 2
  634. */
  635. int state;
  636. } static ws_clients[MAX_WS_CLIENTS];
  637. #define ASSERT(x) \
  638. { \
  639. if (!(x)) { \
  640. fprintf(stderr, \
  641. "Assertion failed in line %u\n", \
  642. (unsigned)__LINE__); \
  643. } \
  644. }
  645. int
  646. WebSocketConnectHandler(const struct mg_connection *conn, void *cbdata)
  647. {
  648. struct mg_context *ctx = mg_get_context(conn);
  649. int reject = 1;
  650. int i;
  651. mg_lock_context(ctx);
  652. for (i = 0; i < MAX_WS_CLIENTS; i++) {
  653. if (ws_clients[i].conn == NULL) {
  654. ws_clients[i].conn = (struct mg_connection *)conn;
  655. ws_clients[i].state = 1;
  656. mg_set_user_connection_data(ws_clients[i].conn,
  657. (void *)(ws_clients + i));
  658. reject = 0;
  659. break;
  660. }
  661. }
  662. mg_unlock_context(ctx);
  663. fprintf(stdout,
  664. "Websocket client %s\r\n\r\n",
  665. (reject ? "rejected" : "accepted"));
  666. return reject;
  667. }
  668. void
  669. WebSocketReadyHandler(struct mg_connection *conn, void *cbdata)
  670. {
  671. const char *text = "Hello from the websocket ready handler";
  672. struct t_ws_client *client = mg_get_user_connection_data(conn);
  673. mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, text, strlen(text));
  674. fprintf(stdout, "Greeting message sent to websocket client\r\n\r\n");
  675. ASSERT(client->conn == conn);
  676. ASSERT(client->state == 1);
  677. client->state = 2;
  678. }
  679. int
  680. WebsocketDataHandler(struct mg_connection *conn,
  681. int bits,
  682. char *data,
  683. size_t len,
  684. void *cbdata)
  685. {
  686. struct t_ws_client *client = mg_get_user_connection_data(conn);
  687. ASSERT(client->conn == conn);
  688. ASSERT(client->state >= 1);
  689. fprintf(stdout, "Websocket got %lu bytes of ", (unsigned long)len);
  690. switch (((unsigned char)bits) & 0x0F) {
  691. case MG_WEBSOCKET_OPCODE_CONTINUATION:
  692. fprintf(stdout, "continuation");
  693. break;
  694. case MG_WEBSOCKET_OPCODE_TEXT:
  695. fprintf(stdout, "text");
  696. break;
  697. case MG_WEBSOCKET_OPCODE_BINARY:
  698. fprintf(stdout, "binary");
  699. break;
  700. case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
  701. fprintf(stdout, "close");
  702. break;
  703. case MG_WEBSOCKET_OPCODE_PING:
  704. fprintf(stdout, "ping");
  705. break;
  706. case MG_WEBSOCKET_OPCODE_PONG:
  707. fprintf(stdout, "pong");
  708. break;
  709. default:
  710. fprintf(stdout, "unknown(%1xh)", ((unsigned char)bits) & 0x0F);
  711. break;
  712. }
  713. fprintf(stdout, " data:\r\n");
  714. fwrite(data, len, 1, stdout);
  715. fprintf(stdout, "\r\n\r\n");
  716. return 1;
  717. }
  718. void
  719. WebSocketCloseHandler(const struct mg_connection *conn, void *cbdata)
  720. {
  721. struct mg_context *ctx = mg_get_context(conn);
  722. struct t_ws_client *client = mg_get_user_connection_data(conn);
  723. ASSERT(client->conn == conn);
  724. ASSERT(client->state >= 1);
  725. mg_lock_context(ctx);
  726. while (client->state == 3) {
  727. /* "inform" state, wait a while */
  728. mg_unlock_context(ctx);
  729. #ifdef _WIN32
  730. Sleep(1);
  731. #else
  732. usleep(1000);
  733. #endif
  734. mg_lock_context(ctx);
  735. }
  736. client->state = 0;
  737. client->conn = NULL;
  738. mg_unlock_context(ctx);
  739. fprintf(stdout,
  740. "Client dropped from the set of webserver connections\r\n\r\n");
  741. }
  742. void
  743. InformWebsockets(struct mg_context *ctx)
  744. {
  745. static unsigned long cnt = 0;
  746. char text[32];
  747. size_t textlen;
  748. int i;
  749. sprintf(text, "%lu", ++cnt);
  750. textlen = strlen(text);
  751. for (i = 0; i < MAX_WS_CLIENTS; i++) {
  752. int inform = 0;
  753. mg_lock_context(ctx);
  754. if (ws_clients[i].state == 2) {
  755. /* move to "inform" state */
  756. ws_clients[i].state = 3;
  757. inform = 1;
  758. }
  759. mg_unlock_context(ctx);
  760. if (inform) {
  761. mg_websocket_write(ws_clients[i].conn,
  762. MG_WEBSOCKET_OPCODE_TEXT,
  763. text,
  764. textlen);
  765. mg_lock_context(ctx);
  766. ws_clients[i].state = 2;
  767. mg_unlock_context(ctx);
  768. }
  769. }
  770. }
  771. #endif
  772. #ifdef USE_SSL_DH
  773. #include "openssl/dh.h"
  774. #include "openssl/ec.h"
  775. #include "openssl/ecdsa.h"
  776. #include "openssl/evp.h"
  777. #include "openssl/ssl.h"
  778. DH *
  779. get_dh2236()
  780. {
  781. static unsigned char dh2236_p[] = {
  782. 0x0E, 0x97, 0x6E, 0x6A, 0x88, 0x84, 0xD2, 0xD7, 0x55, 0x6A, 0x17, 0xB7,
  783. 0x81, 0x9A, 0x98, 0xBC, 0x7E, 0xD1, 0x6A, 0x44, 0xB1, 0x18, 0xE6, 0x25,
  784. 0x3A, 0x62, 0x35, 0xF0, 0x41, 0x91, 0xE2, 0x16, 0x43, 0x9D, 0x8F, 0x7D,
  785. 0x5D, 0xDA, 0x85, 0x47, 0x25, 0xC4, 0xBA, 0x68, 0x0A, 0x87, 0xDC, 0x2C,
  786. 0x33, 0xF9, 0x75, 0x65, 0x17, 0xCB, 0x8B, 0x80, 0xFE, 0xE0, 0xA8, 0xAF,
  787. 0xC7, 0x9E, 0x82, 0xBE, 0x6F, 0x1F, 0x00, 0x04, 0xBD, 0x69, 0x50, 0x8D,
  788. 0x9C, 0x3C, 0x41, 0x69, 0x21, 0x4E, 0x86, 0xC8, 0x2B, 0xCC, 0x07, 0x4D,
  789. 0xCF, 0xE4, 0xA2, 0x90, 0x8F, 0x66, 0xA9, 0xEF, 0xF7, 0xFC, 0x6F, 0x5F,
  790. 0x06, 0x22, 0x00, 0xCB, 0xCB, 0xC3, 0x98, 0x3F, 0x06, 0xB9, 0xEC, 0x48,
  791. 0x3B, 0x70, 0x6E, 0x94, 0xE9, 0x16, 0xE1, 0xB7, 0x63, 0x2E, 0xAB, 0xB2,
  792. 0xF3, 0x84, 0xB5, 0x3D, 0xD7, 0x74, 0xF1, 0x6A, 0xD1, 0xEF, 0xE8, 0x04,
  793. 0x18, 0x76, 0xD2, 0xD6, 0xB0, 0xB7, 0x71, 0xB6, 0x12, 0x8F, 0xD1, 0x33,
  794. 0xAB, 0x49, 0xAB, 0x09, 0x97, 0x35, 0x9D, 0x4B, 0xBB, 0x54, 0x22, 0x6E,
  795. 0x1A, 0x33, 0x18, 0x02, 0x8A, 0xF4, 0x7C, 0x0A, 0xCE, 0x89, 0x75, 0x2D,
  796. 0x10, 0x68, 0x25, 0xA9, 0x6E, 0xCD, 0x97, 0x49, 0xED, 0xAE, 0xE6, 0xA7,
  797. 0xB0, 0x07, 0x26, 0x25, 0x60, 0x15, 0x2B, 0x65, 0x88, 0x17, 0xF2, 0x5D,
  798. 0x2C, 0xF6, 0x2A, 0x7A, 0x8C, 0xAD, 0xB6, 0x0A, 0xA2, 0x57, 0xB0, 0xC1,
  799. 0x0E, 0x5C, 0xA8, 0xA1, 0x96, 0x58, 0x9A, 0x2B, 0xD4, 0xC0, 0x8A, 0xCF,
  800. 0x91, 0x25, 0x94, 0xB4, 0x14, 0xA7, 0xE4, 0xE2, 0x1B, 0x64, 0x5F, 0xD2,
  801. 0xCA, 0x70, 0x46, 0xD0, 0x2C, 0x95, 0x6B, 0x9A, 0xFB, 0x83, 0xF9, 0x76,
  802. 0xE6, 0xD4, 0xA4, 0xA1, 0x2B, 0x2F, 0xF5, 0x1D, 0xE4, 0x06, 0xAF, 0x7D,
  803. 0x22, 0xF3, 0x04, 0x30, 0x2E, 0x4C, 0x64, 0x12, 0x5B, 0xB0, 0x55, 0x3E,
  804. 0xC0, 0x5E, 0x56, 0xCB, 0x99, 0xBC, 0xA8, 0xD9, 0x23, 0xF5, 0x57, 0x40,
  805. 0xF0, 0x52, 0x85, 0x9B,
  806. };
  807. static unsigned char dh2236_g[] = {
  808. 0x02,
  809. };
  810. DH *dh;
  811. if ((dh = DH_new()) == NULL)
  812. return (NULL);
  813. dh->p = BN_bin2bn(dh2236_p, sizeof(dh2236_p), NULL);
  814. dh->g = BN_bin2bn(dh2236_g, sizeof(dh2236_g), NULL);
  815. if ((dh->p == NULL) || (dh->g == NULL)) {
  816. DH_free(dh);
  817. return (NULL);
  818. }
  819. return (dh);
  820. }
  821. #endif
  822. #ifndef TEST_WITHOUT_SSL
  823. int
  824. init_ssl(void *ssl_ctx, void *user_data)
  825. {
  826. /* Add application specific SSL initialization */
  827. struct ssl_ctx_st *ctx = (struct ssl_ctx_st *)ssl_ctx;
  828. #ifdef USE_SSL_DH
  829. /* example from https://github.com/civetweb/civetweb/issues/347 */
  830. DH *dh = get_dh2236();
  831. if (!dh)
  832. return -1;
  833. if (1 != SSL_CTX_set_tmp_dh(ctx, dh))
  834. return -1;
  835. DH_free(dh);
  836. EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
  837. if (!ecdh)
  838. return -1;
  839. if (1 != SSL_CTX_set_tmp_ecdh(ctx, ecdh))
  840. return -1;
  841. EC_KEY_free(ecdh);
  842. printf("ECDH ciphers initialized\n");
  843. #endif
  844. return 0;
  845. }
  846. #endif
  847. int
  848. log_message(const struct mg_connection *conn, const char *message)
  849. {
  850. puts(message);
  851. return 1;
  852. }
  853. int
  854. main(int argc, char *argv[])
  855. {
  856. const char *options[] = {
  857. #if !defined(NO_FILES)
  858. "document_root",
  859. DOCUMENT_ROOT,
  860. #endif
  861. "listening_ports",
  862. PORT,
  863. "request_timeout_ms",
  864. "10000",
  865. "error_log_file",
  866. "error.log",
  867. #ifdef USE_WEBSOCKET
  868. "websocket_timeout_ms",
  869. "3600000",
  870. #endif
  871. #ifndef TEST_WITHOUT_SSL
  872. "ssl_certificate",
  873. "../../resources/cert/server.pem",
  874. "ssl_protocol_version",
  875. "3",
  876. "ssl_cipher_list",
  877. #ifdef USE_SSL_DH
  878. "ECDHE-RSA-AES256-GCM-SHA384:DES-CBC3-SHA:AES128-SHA:AES128-GCM-SHA256",
  879. #else
  880. "DES-CBC3-SHA:AES128-SHA:AES128-GCM-SHA256",
  881. #endif
  882. #endif
  883. "enable_auth_domain_check",
  884. "no",
  885. 0
  886. };
  887. struct mg_callbacks callbacks;
  888. struct mg_context *ctx;
  889. struct mg_server_port ports[32];
  890. int port_cnt, n;
  891. int err = 0;
  892. /* Check if libcivetweb has been built with all required features. */
  893. #ifdef USE_IPV6
  894. if (!mg_check_feature(8)) {
  895. fprintf(stderr,
  896. "Error: Embedded example built with IPv6 support, "
  897. "but civetweb library build without.\n");
  898. err = 1;
  899. }
  900. #endif
  901. #ifdef USE_WEBSOCKET
  902. if (!mg_check_feature(16)) {
  903. fprintf(stderr,
  904. "Error: Embedded example built with websocket support, "
  905. "but civetweb library build without.\n");
  906. err = 1;
  907. }
  908. #endif
  909. #ifndef TEST_WITHOUT_SSL
  910. if (!mg_check_feature(2)) {
  911. fprintf(stderr,
  912. "Error: Embedded example built with SSL support, "
  913. "but civetweb library build without.\n");
  914. err = 1;
  915. }
  916. #endif
  917. if (err) {
  918. fprintf(stderr, "Cannot start CivetWeb - inconsistent build.\n");
  919. return EXIT_FAILURE;
  920. }
  921. /* Start CivetWeb web server */
  922. memset(&callbacks, 0, sizeof(callbacks));
  923. #ifndef TEST_WITHOUT_SSL
  924. callbacks.init_ssl = init_ssl;
  925. #endif
  926. callbacks.log_message = log_message;
  927. ctx = mg_start(&callbacks, 0, options);
  928. /* Check return value: */
  929. if (ctx == NULL) {
  930. fprintf(stderr, "Cannot start CivetWeb - mg_start failed.\n");
  931. return EXIT_FAILURE;
  932. }
  933. /* Add handler EXAMPLE_URI, to explain the example */
  934. mg_set_request_handler(ctx, EXAMPLE_URI, ExampleHandler, 0);
  935. mg_set_request_handler(ctx, EXIT_URI, ExitHandler, 0);
  936. /* Add handler for /A* and special handler for /A/B */
  937. mg_set_request_handler(ctx, "/A", AHandler, 0);
  938. mg_set_request_handler(ctx, "/A/B", ABHandler, 0);
  939. /* Add handler for /B, /B/A, /B/B but not for /B* */
  940. mg_set_request_handler(ctx, "/B$", BXHandler, (void *)"alpha");
  941. mg_set_request_handler(ctx, "/B/A$", BXHandler, (void *)"beta");
  942. mg_set_request_handler(ctx, "/B/B$", BXHandler, (void *)"gamma");
  943. /* Add handler for all files with .foo extension */
  944. mg_set_request_handler(ctx, "**.foo$", FooHandler, 0);
  945. /* Add handler for /close extension */
  946. mg_set_request_handler(ctx, "/close", CloseHandler, 0);
  947. #if !defined(NO_FILESYSTEMS)
  948. /* Add handler for /form (serve a file outside the document root) */
  949. mg_set_request_handler(ctx,
  950. "/form",
  951. FileHandler,
  952. (void *)"../../test/form.html");
  953. #endif /* NO_FILESYSTEMS */
  954. /* Add handler for form data */
  955. mg_set_request_handler(ctx,
  956. "/handle_form.embedded_c.example.callback",
  957. FormHandler,
  958. (void *)0);
  959. /* Add a file upload handler for parsing files on the fly */
  960. mg_set_request_handler(ctx,
  961. "/on_the_fly_form",
  962. FileUploadForm,
  963. (void *)"/on_the_fly_form.md5.callback");
  964. mg_set_request_handler(ctx,
  965. "/on_the_fly_form.md5.callback",
  966. CheckSumHandler,
  967. (void *)0);
  968. /* Add handler for /cookie example */
  969. mg_set_request_handler(ctx, "/cookie", CookieHandler, 0);
  970. /* Add handler for /postresponse example */
  971. mg_set_request_handler(ctx, "/postresponse", PostResponser, 0);
  972. /* Add HTTP site to open a websocket connection */
  973. mg_set_request_handler(ctx, "/websocket", WebSocketStartHandler, 0);
  974. #if !defined(NO_FILESYSTEMS)
  975. /* Add HTTP site with auth */
  976. mg_set_request_handler(ctx, "/auth", AuthStartHandler, 0);
  977. #endif /* NO_FILESYSTEMS */
  978. #ifdef USE_WEBSOCKET
  979. /* WS site for the websocket connection */
  980. mg_set_websocket_handler(ctx,
  981. "/websocket",
  982. WebSocketConnectHandler,
  983. WebSocketReadyHandler,
  984. WebsocketDataHandler,
  985. WebSocketCloseHandler,
  986. 0);
  987. #endif
  988. /* List all listening ports */
  989. memset(ports, 0, sizeof(ports));
  990. port_cnt = mg_get_server_ports(ctx, 32, ports);
  991. printf("\n%i listening ports:\n\n", port_cnt);
  992. for (n = 0; n < port_cnt && n < 32; n++) {
  993. const char *proto = ports[n].is_ssl ? "https" : "http";
  994. const char *host;
  995. if ((ports[n].protocol & 1) == 1) {
  996. /* IPv4 */
  997. host = "127.0.0.1";
  998. printf("Browse files at %s://%s:%i/\n", proto, host, ports[n].port);
  999. printf("Run example at %s://%s:%i%s\n",
  1000. proto,
  1001. host,
  1002. ports[n].port,
  1003. EXAMPLE_URI);
  1004. printf(
  1005. "Exit at %s://%s:%i%s\n", proto, host, ports[n].port, EXIT_URI);
  1006. printf("\n");
  1007. }
  1008. if ((ports[n].protocol & 2) == 2) {
  1009. /* IPv6 */
  1010. host = "[::1]";
  1011. printf("Browse files at %s://%s:%i/\n", proto, host, ports[n].port);
  1012. printf("Run example at %s://%s:%i%s\n",
  1013. proto,
  1014. host,
  1015. ports[n].port,
  1016. EXAMPLE_URI);
  1017. printf(
  1018. "Exit at %s://%s:%i%s\n", proto, host, ports[n].port, EXIT_URI);
  1019. printf("\n");
  1020. }
  1021. }
  1022. /* Wait until the server should be closed */
  1023. while (!exitNow) {
  1024. #ifdef _WIN32
  1025. Sleep(1000);
  1026. #else
  1027. sleep(1);
  1028. #endif
  1029. #ifdef USE_WEBSOCKET
  1030. InformWebsockets(ctx);
  1031. #endif
  1032. }
  1033. /* Stop the server */
  1034. mg_stop(ctx);
  1035. printf("Server stopped.\n");
  1036. printf("Bye!\n");
  1037. return EXIT_SUCCESS;
  1038. }