httpd.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. #define MODULE "httpd"
  2. #include "common.h"
  3. #include "fw.h"
  4. #include "httpd.h"
  5. #include "config.h"
  6. #include <incbin.h>
  7. #include <unzipLIB.h>
  8. static httpd_handle_t httpd;
  9. /* Looping version of httpd_send(); this is a hidden function in the server */
  10. static esp_err_t httpd_send_all(httpd_req_t *req, const void *buf, size_t len)
  11. {
  12. const char *p = buf;
  13. while (len) {
  14. int sent = httpd_send(req, p, len);
  15. if (sent <= 0)
  16. return ESP_ERR_HTTPD_RESP_SEND;
  17. p += sent;
  18. len -= sent;
  19. }
  20. return ESP_OK;
  21. }
  22. /* Create a file pointer from an http request */
  23. static ssize_t httpd_io_read(void *cookie, char *buf, size_t n)
  24. {
  25. int rv = httpd_req_recv(cookie, buf, n);
  26. return rv < 0 ? -1 : rv;
  27. }
  28. static ssize_t httpd_io_write(void *cookie, const char *buf, size_t n)
  29. {
  30. return httpd_resp_send_chunk(cookie, buf, n) ? 0 : n;
  31. }
  32. static int httpd_io_close_write(void *cookie)
  33. {
  34. return httpd_resp_send_chunk(cookie, NULL, 0) ? -1 : 0;
  35. }
  36. static FILE *httpd_fopen_read(httpd_req_t *req)
  37. {
  38. static const cookie_io_functions_t http_io_read_funcs = {
  39. .read = httpd_io_read,
  40. .write = NULL, /* Not writeable */
  41. .seek = NULL, /* Not seekable */
  42. .close = NULL,
  43. };
  44. return fopencookie((void *)req, "r", http_io_read_funcs);
  45. }
  46. static FILE *httpd_fopen_write(httpd_req_t *req)
  47. {
  48. static const cookie_io_functions_t http_io_write_funcs = {
  49. .read = httpd_io_read,
  50. .write = httpd_io_write,
  51. .seek = NULL, /* Not seekable */
  52. .close = httpd_io_close_write
  53. };
  54. return fopencookie((void *)req, "r+", http_io_write_funcs);
  55. }
  56. #define TIMEBUF_LEN 32
  57. static const char *http_date(const struct tm *when)
  58. {
  59. static char timebuf[32];
  60. strftime(timebuf, sizeof timebuf,
  61. "%a, %d %b %Y %H:%M:%S GMT", when);
  62. return timebuf;
  63. }
  64. static const char *http_now(void)
  65. {
  66. time_t t = time(NULL);
  67. return http_date(gmtime(&t));
  68. }
  69. static struct tm *set_weekday(struct tm *tm)
  70. {
  71. /*
  72. * This is a variation on Zeller's congruence with a table lookup
  73. * for the month. The table contains the number of days since March 1,
  74. * mod 7 (for Jan and Feb, from March 1 of the *previous year*.)
  75. *
  76. * Sample test cases:
  77. * Wed Mar 1 0000
  78. * Thu Jan 1 1970
  79. * Wed Apr 27 2022
  80. * Mon Feb 28 2000
  81. * Wed Mar 1 2000
  82. * Sun Feb 28 2100
  83. * Mon Mar 1 2100
  84. */
  85. static const uint8_t md[12] = { 5, 1, 0, 3, 5, 1, 3, 6, 2, 4, 0, 2 };
  86. unsigned int c, y, m, d;
  87. y = tm->tm_year + 1900;
  88. m = tm->tm_mon;
  89. d = tm->tm_mday;
  90. if (m < 2)
  91. y--; /* Jan, Feb */
  92. c = y/100;
  93. /*
  94. * 2 represents the base date of Tue Feb 29 0000
  95. *
  96. * 0 = Sun, 6 = Sat
  97. */
  98. tm->tm_wday = (d + md[m] + y + (y >> 2) - c + (c >> 2) + 2) % 7;
  99. return tm;
  100. }
  101. static const char *http_dos_date(uint32_t dos_date)
  102. {
  103. struct tm tm;
  104. tm.tm_sec = (dos_date << 1) & 63;
  105. tm.tm_min = (dos_date >> 5) & 63;
  106. tm.tm_hour = (dos_date >> 11) & 31;
  107. tm.tm_mday = (dos_date >> 16) & 31;
  108. tm.tm_mon = ((dos_date >> 21) & 15) - 1;
  109. tm.tm_year = (dos_date >> 25) + 80;
  110. tm.tm_isdst = 0; /* Times are stored in GMT */
  111. return http_date(set_weekday(&tm));
  112. }
  113. static const char text_plain[] = "text/plain; charset=\"UTF-8\"";
  114. enum hsp_flags {
  115. HSP_CLOSE = 1,
  116. HSP_CRLF = 2,
  117. HSP_CLOSE_SOCKET = 4
  118. };
  119. static void httpd_print_request(const httpd_req_t *req)
  120. {
  121. printf("[HTTP] %s %s\n", http_method_str(req->method), req->uri);
  122. }
  123. static esp_err_t httpd_send_plain(httpd_req_t *req,
  124. unsigned int rcode,
  125. const char *body, size_t blen,
  126. enum hsp_flags flags, unsigned int refresh)
  127. {
  128. char *header = NULL;
  129. esp_err_t err;
  130. int hlen;
  131. const char *now = http_now();
  132. if (rcode > 499)
  133. flags |= HSP_CLOSE;
  134. const char *closer = flags & HSP_CLOSE ? "Connection: close\r\n" : "";
  135. bool redirect = rcode >= 300 && rcode <= 399;
  136. static const char refresher[] = "Refresh: %u;url=/";
  137. char refresh_str[sizeof(refresher)-2 + sizeof(unsigned int)*3];
  138. refresh_str[0] = '\0';
  139. if (refresh)
  140. snprintf(refresh_str, sizeof refresh_str, refresher, refresh);
  141. if (redirect) {
  142. size_t blenadj = sizeof("3xx Redirect \r"); /* \0 -> \n so don't include it */
  143. flags |= HSP_CRLF;
  144. hlen = asprintf(&header,
  145. "HTTP/1.1 %u\r\n"
  146. "Content-Type: %s\r\n"
  147. "Content-Length: %zu\r\n"
  148. "Date %s\r\n"
  149. "Location: %.*s\r\n"
  150. "%s%s"
  151. "\r\n"
  152. "%3u Redirect ",
  153. rcode, text_plain, blen + blenadj,
  154. now, (int)blen, body, closer, refresh_str, rcode);
  155. } else {
  156. size_t blenadj = (flags & HSP_CRLF) ? 2 : 0;
  157. hlen = asprintf(&header,
  158. "HTTP/1.1 %u\r\n"
  159. "Content-Type: %s\r\n"
  160. "Content-Length: %zu\r\n"
  161. "Cache-Control: no-store\r\n"
  162. "Date: %s\r\n"
  163. "%s%s"
  164. "\r\n",
  165. rcode, text_plain, blen + blenadj, now,
  166. closer, refresh_str);
  167. }
  168. if (!header)
  169. return ESP_ERR_NO_MEM;
  170. err = httpd_send_all(req, header, hlen);
  171. if (!err && blen) {
  172. err = httpd_send_all(req, body, blen);
  173. }
  174. if (!err && (flags & HSP_CRLF)) {
  175. err = httpd_send_all(req, "\r\n", 2);
  176. }
  177. if (header)
  178. free(header);
  179. /* Sending ESP_FAIL causes the socket to be immediately closed */
  180. return err ? err : (flags & HSP_CLOSE_SOCKET) ? ESP_FAIL : ESP_OK;
  181. }
  182. #define HTTP_ERR(r,e,s) httpd_send_plain((r), (e), s, sizeof(s)-1, HSP_CRLF, 0)
  183. static esp_err_t httpd_err_enoent(httpd_req_t *req)
  184. {
  185. return HTTP_ERR(req, 404, "URI not found");
  186. }
  187. static esp_err_t httpd_send_ok(httpd_req_t *req)
  188. {
  189. return HTTP_ERR(req, 200, "OK");
  190. }
  191. static esp_err_t httpd_err_enomem(httpd_req_t *req)
  192. {
  193. return HTTP_ERR(req, 503, "Out of memory");
  194. }
  195. static esp_err_t httpd_update_done(httpd_req_t *req, const char *what, int err)
  196. {
  197. char *response = NULL;
  198. int len;
  199. unsigned int reboot_time = reboot_delayed();
  200. if (err) {
  201. len = asprintf(&response,
  202. "%s update failed: %s\r\n"
  203. "Rebooting in %u seconds\r\n",
  204. what, firmware_errstr(err), reboot_time);
  205. } else {
  206. len = asprintf(&response,
  207. "%s update complete\r\n"
  208. "Rebooting in %u seconds\r\n",
  209. what, reboot_time);
  210. }
  211. if (!response)
  212. len = 0;
  213. esp_err_t rv = httpd_send_plain(req, err ? 400 : 200, response, len,
  214. HSP_CLOSE|HSP_CLOSE_SOCKET|HSP_CRLF,
  215. reboot_time+5);
  216. if (response)
  217. free(response);
  218. return rv;
  219. }
  220. static esp_err_t httpd_firmware_update(httpd_req_t *req)
  221. {
  222. int rv;
  223. /* XXX: use httpd_fopen_read() here */
  224. rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
  225. return httpd_update_done(req, "Firmware", rv);
  226. }
  227. static esp_err_t httpd_set_config(httpd_req_t *req, const char *query)
  228. {
  229. FILE *f;
  230. size_t qlen;
  231. int rv1 = 0;
  232. if (query) {
  233. rv1 = set_config_url_string(query);
  234. }
  235. int rv2 = 0;
  236. f = NULL;
  237. if (req->content_len) {
  238. f = httpd_fopen_read(req);
  239. if (!f)
  240. return HTTP_ERR(req, 500, "Unable to get request handle");
  241. }
  242. rv2 = read_config(f, true);
  243. if (f)
  244. fclose(f);
  245. return httpd_update_done(req, "Configuration", rv1 ? rv1 : rv2);
  246. }
  247. static esp_err_t httpd_set_lang(httpd_req_t *req, const char *query)
  248. {
  249. if (query && *query) {
  250. setenv_config("LANG", query);
  251. read_config(NULL, true); /* Save configuration */
  252. }
  253. return httpd_send_plain(req, 302, "/", 1, 0, 0);
  254. }
  255. #define STRING_MATCHES(str, len, what) \
  256. (((len) == sizeof(what)-1) && !memcmp((str), (what), sizeof(what)-1))
  257. static esp_err_t httpd_sys_post_handler(httpd_req_t *req)
  258. {
  259. httpd_print_request(req);
  260. if (!httpd_req_get_hdr_value_len(req, "Content-Length"))
  261. return HTTP_ERR(req, 411, "Length required");
  262. if (strncmp(req->uri, "/sys/", 5))
  263. return httpd_err_enoent(req); /* This should never happen */
  264. const char *file = req->uri + 5;
  265. size_t filelen = strcspn(file, "?");
  266. const char *query = NULL;
  267. if (file[filelen] == '?')
  268. query = &file[filelen] + 1;
  269. if (STRING_MATCHES(file, filelen, "fwupdate"))
  270. return httpd_firmware_update(req);
  271. if (STRING_MATCHES(file, filelen, "setconfig"))
  272. return httpd_set_config(req, query);
  273. if (STRING_MATCHES(file, filelen, "lang"))
  274. return httpd_set_lang(req, query);
  275. return httpd_err_enoent(req);
  276. }
  277. static esp_err_t httpd_get_config(httpd_req_t *req)
  278. {
  279. FILE *f = httpd_fopen_write(req);
  280. if (!f)
  281. return HTTP_ERR(req, 500, "Unable to get request handle");
  282. httpd_resp_set_type(req, text_plain);
  283. httpd_resp_set_hdr(req, "Cache-Control", "no-store");
  284. int rv = write_config(f);
  285. fclose(f);
  286. return rv ? ESP_FAIL : ESP_OK;
  287. }
  288. static esp_err_t httpd_sys_get_handler(httpd_req_t *req)
  289. {
  290. httpd_print_request(req);
  291. if (!strcmp(req->uri, "/sys/getconfig"))
  292. return httpd_get_config(req);
  293. return httpd_err_enoent(req);
  294. }
  295. INCBIN_EXTERN(wwwzip);
  296. struct mime_type {
  297. const char *ext;
  298. uint16_t ext_len;
  299. uint16_t flags;
  300. const char *mime;
  301. };
  302. #define MT_CHARSET 1 /* Add charset to Content-Type */
  303. static const struct mime_type mime_types[] = {
  304. { ".html", 5, MT_CHARSET, "text/html" },
  305. { ".xhtml", 6, MT_CHARSET, "text/html" },
  306. { ".css", 4, MT_CHARSET, "text/css" },
  307. { ".webp", 5, 0, "image/webp" },
  308. { ".jpg", 4, 0, "image/jpeg" },
  309. { ".png", 4, 0, "image/png" },
  310. { ".ico", 4, 0, "image/png" }, /* favicon.ico */
  311. { ".svg", 4, MT_CHARSET, "image/svg+xml" },
  312. { ".otf", 4, 0, "font/otf" },
  313. { ".ttf", 4, 0, "font/ttf" },
  314. { ".woff", 5, 0, "font/woff" },
  315. { ".woff2", 6, 0, "font/woff2" },
  316. { ".pdf", 4, 0, "application/pdf" },
  317. { ".js", 3, MT_CHARSET, "text/javascript" },
  318. { ".mjs", 4, MT_CHARSET, "text/javascript" },
  319. { ".json", 5, MT_CHARSET, "application/json" },
  320. { ".xml", 4, MT_CHARSET, "text/xml" },
  321. { ".bin", 4, 0, "application/octet-stream" },
  322. { ".fw", 3, 0, "application/octet-stream" },
  323. { NULL, 0, MT_CHARSET, "text/plain" } /* default */
  324. };
  325. static esp_err_t httpd_static_handler(httpd_req_t *req)
  326. {
  327. static const char index_filename[] = "index.html";
  328. static const char fallback_lang[] = "en";
  329. size_t buffer_size = UNZ_BUFSIZE;
  330. const char *uri, *enduri;
  331. bool add_index;
  332. char *buffer = NULL;
  333. ZIPFILE *zip = NULL;
  334. unzFile unz = NULL;
  335. bool file_open = false;
  336. int err = 0;
  337. size_t len;
  338. httpd_print_request(req);
  339. uri = req->uri;
  340. while (*uri == '/')
  341. uri++; /* Skip leading slashes */
  342. const char *first_slash = NULL, *last_slash = NULL;
  343. for (enduri = uri; *enduri; enduri++) {
  344. if (*enduri == '/') {
  345. last_slash = enduri;
  346. if (!first_slash)
  347. first_slash = enduri;
  348. }
  349. }
  350. if (enduri == uri) {
  351. add_index = true;
  352. } else if (last_slash == enduri-1) {
  353. add_index = true;
  354. enduri--; /* Drop terminal slash */
  355. if (first_slash == last_slash)
  356. first_slash = NULL;
  357. } else {
  358. add_index = false; /* Try the plain filename first */
  359. }
  360. const char *lang = getenv_def("LANG", "");
  361. const size_t lang_size = lang ? strlen(lang)+1 : 0;
  362. const size_t lang_space = (lang_size < sizeof fallback_lang
  363. ? sizeof fallback_lang : lang_size) + 1;
  364. const size_t filename_buffer_size =
  365. (enduri - uri) + lang_space + 2 + sizeof index_filename;
  366. if (buffer_size < filename_buffer_size)
  367. buffer_size = filename_buffer_size;
  368. buffer = malloc(buffer_size);
  369. zip = malloc(sizeof *zip);
  370. if (!buffer || !zip) {
  371. err = httpd_err_enomem(req);
  372. goto out;
  373. }
  374. char * const filebase = buffer + lang_space;
  375. char * const endbase = mempcpy(filebase, uri, enduri - uri);
  376. filebase[-1] = '/';
  377. unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
  378. zip, NULL, NULL, NULL, NULL);
  379. if (!unz) {
  380. MSG("[HTTP] unzOpen failed!\n");
  381. err = HTTP_ERR(req, 500, "Cannot open content archive");
  382. goto out;
  383. }
  384. char *filename, *endfile;
  385. unsigned int m;
  386. bool found = false;
  387. for (m = add_index; m < 6; m += (add_index+1)) {
  388. if (m & 1) {
  389. char *sx = endbase - (endbase[-1] == '/');
  390. *sx++ = '/';
  391. endfile = mempcpy(sx, index_filename, sizeof index_filename) - 1;
  392. } else {
  393. endfile = endbase;
  394. }
  395. *endfile = '\0';
  396. switch (m >> 1) {
  397. case 1:
  398. if (!lang) {
  399. filename = NULL;
  400. } else {
  401. filename = filebase - lang_size;
  402. memcpy(filename, lang, lang_size-1);
  403. }
  404. break;
  405. case 2:
  406. filename = filebase - sizeof fallback_lang;
  407. memcpy(filename, fallback_lang, sizeof fallback_lang - 1);
  408. break;
  409. default:
  410. filename = filebase;
  411. break;
  412. }
  413. if (!filename)
  414. continue;
  415. filename[-1] = '/';
  416. MSG("trying to open: %s\n", filename);
  417. if (unzLocateFile(unz, filename, 1) == UNZ_OK) {
  418. found = true;
  419. break;
  420. }
  421. }
  422. size_t filelen = endfile - filename;
  423. if (!found) {
  424. err = httpd_err_enoent(req);
  425. goto out;
  426. } else if (m) {
  427. err = httpd_send_plain(req, 302 - (m == 1), filename-1, filelen+1, 0, 0);
  428. goto out;
  429. }
  430. /* Note: p points to the end of the filename string */
  431. const struct mime_type *mime_type = mime_types;
  432. /* The default entry with length 0 will always match */
  433. while (mime_type->ext_len) {
  434. len = mime_type->ext_len;
  435. if (len < filelen && !memcmp(endfile - len, mime_type->ext, len))
  436. break;
  437. mime_type++;
  438. }
  439. unz_file_info fileinfo;
  440. memset(&fileinfo, 0, sizeof fileinfo);
  441. unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
  442. /*
  443. * Hopefully the combination of date and CRC
  444. * is strong enough to quality for a "strong" ETag
  445. */
  446. char etag[16+3+1];
  447. snprintf(etag, sizeof etag, "\"%08x:%08x\"",
  448. fileinfo.dosDate, fileinfo.crc);
  449. bool skip_body = req->method == HTTP_HEAD || !fileinfo.uncompressed_size;
  450. bool skip_meta = false;
  451. const char *response = "200 OK";
  452. if (httpd_req_get_hdr_value_str(req, "If-None-Match",
  453. buffer, buffer_size) == ESP_OK &&
  454. strstr(buffer, etag)) {
  455. skip_body = skip_meta = true;
  456. response = "304 Not Modified";
  457. }
  458. len = snprintf(buffer, buffer_size-2,
  459. "HTTP/1.1 %s\r\n"
  460. "Date: %s\r\n"
  461. "Cache-Control: max-age=10\r\n"
  462. "ETag: %s\r\n",
  463. response,
  464. http_now(),
  465. etag);
  466. if (len < buffer_size-2 && !skip_meta) {
  467. const char *mime_extra =
  468. mime_type->flags & MT_CHARSET ? "; charset=\"UTF-8\"" : "";
  469. len += snprintf(buffer + len, buffer_size-2 - len,
  470. "Content-Type: %s%s\r\n"
  471. "Content-Length: %u\r\n"
  472. "Allow: GET, HEAD\r\n"
  473. "Connection: close\r\n"
  474. "Last-Modified: %s\r\n",
  475. mime_type->mime, mime_extra,
  476. fileinfo.uncompressed_size,
  477. http_dos_date(fileinfo.dosDate));
  478. }
  479. if (len >= buffer_size-2) {
  480. err = HTTP_ERR(req, 500, "buffer_size too small");
  481. goto out;
  482. }
  483. buffer[len++] = '\r';
  484. buffer[len++] = '\n';
  485. err = httpd_send_all(req, buffer, len);
  486. if (skip_body || err) {
  487. /* No need to spend time uncompressing the file content */
  488. goto out;
  489. }
  490. if (unzOpenCurrentFile(unz) != UNZ_OK) {
  491. err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
  492. goto out;
  493. }
  494. file_open = true;
  495. len = fileinfo.uncompressed_size;
  496. while (len) {
  497. size_t chunk = len;
  498. if (chunk > buffer_size)
  499. chunk = buffer_size;
  500. if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
  501. err = ESP_ERR_HTTPD_RESULT_TRUNC;
  502. goto out;
  503. }
  504. err = httpd_send_all(req, buffer, chunk);
  505. if (err)
  506. goto out;
  507. len -= chunk;
  508. }
  509. err = ESP_OK; /* All good! */
  510. out:
  511. if (file_open)
  512. unzCloseCurrentFile(unz);
  513. if (unz)
  514. unzClose(unz);
  515. if (zip)
  516. free(zip);
  517. if (buffer)
  518. free(buffer);
  519. return err;
  520. }
  521. /*
  522. * Match a URL against a path prefix. To keep httpd from refusing to
  523. * register subpaths, the root template does not include the leading
  524. * '/', but uri is required to have it. Do not include a trailing /
  525. * in prefix; it is implied.
  526. */
  527. static bool httpd_uri_match_prefix(const char *template, const char *uri,
  528. size_t len)
  529. {
  530. #if 0
  531. printf("[HTTP] matching URI \"%.*s\" against template \"%s\"\n",
  532. len, uri, template);
  533. #endif
  534. if (!len-- || *uri++ != '/')
  535. return false;
  536. /* Previous template character (leading '/' implied) */
  537. unsigned char tp = '/';
  538. while (1) {
  539. unsigned char t = *template++;
  540. unsigned char u;
  541. if (!len-- || !(u = *uri++)) {
  542. return !t;
  543. } else if (!t) {
  544. return tp == '/' || u == '/';
  545. } else if (t != u) {
  546. return false;
  547. }
  548. tp = t;
  549. }
  550. }
  551. /* Do not include leading or trailing /; most specific prefix first */
  552. static const httpd_uri_t uri_handlers[] = {
  553. {
  554. .uri = "sys",
  555. .method = HTTP_GET,
  556. .handler = httpd_sys_get_handler,
  557. .user_ctx = NULL
  558. },
  559. {
  560. .uri = "",
  561. .method = HTTP_GET,
  562. .handler = httpd_static_handler,
  563. .user_ctx = NULL
  564. },
  565. {
  566. .uri = "",
  567. .method = HTTP_HEAD,
  568. .handler = httpd_static_handler,
  569. .user_ctx = NULL
  570. },
  571. {
  572. .uri = "sys",
  573. .method = HTTP_POST,
  574. .handler = httpd_sys_post_handler,
  575. .user_ctx = NULL
  576. },
  577. };
  578. void my_httpd_stop(void)
  579. {
  580. if (httpd) {
  581. esp_unregister_shutdown_handler(my_httpd_stop);
  582. httpd_stop(httpd);
  583. httpd = NULL;
  584. }
  585. }
  586. void my_httpd_start(void)
  587. {
  588. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  589. httpd_handle_t server;
  590. if (httpd)
  591. return;
  592. config.task_priority = 2;
  593. config.max_open_sockets = 8;
  594. printf("[HTTP] Default stack size: %zu\n", config.stack_size);
  595. config.stack_size <<= 2;
  596. printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
  597. config.uri_match_fn = httpd_uri_match_prefix;
  598. if (httpd_start(&server, &config) != ESP_OK)
  599. return;
  600. esp_register_shutdown_handler(my_httpd_stop);
  601. httpd = server;
  602. for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++) {
  603. const httpd_uri_t * const handler = &uri_handlers[i];
  604. if (httpd_register_uri_handler(httpd, handler))
  605. printf("[HTTP] failed to register URI handler: %s %s\n",
  606. http_method_str(handler->method), handler->uri);
  607. }
  608. printf("[HTTP] httpd started\n");
  609. }