httpserv.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. /*
  2. * Copyright (C) 2009, 2013 by egnite GmbH
  3. * Copyright (C) 2001-2004 by egnite Software GmbH
  4. *
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * 3. Neither the name of the copyright holders nor the names of
  17. * contributors may be used to endorse or promote products derived
  18. * from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  23. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  24. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  25. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  26. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  27. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  28. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  30. * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31. * SUCH DAMAGE.
  32. *
  33. * For additional information see http://www.ethernut.de/
  34. */
  35. /*!
  36. * $Id: httpserv.c 5544 2014-01-11 21:03:17Z olereinhardt $
  37. */
  38. /*!
  39. * \example httpd/httpserv.c
  40. *
  41. * This multithreaded webserver demo uses the old Nut/Net HTTP Library.
  42. * Note, that there is a more recent microHTTP API (uhttp) available.
  43. *
  44. * You will find the main() function at the bottom.
  45. */
  46. /* Configuration headers. */
  47. #include <cfg/os.h>
  48. #include <cfg/http.h>
  49. #include <dev/board.h>
  50. #include <dev/urom.h>
  51. #include <sys/version.h>
  52. #include <sys/heap.h>
  53. #include <sys/thread.h>
  54. #include <sys/timer.h>
  55. #include <sys/confnet.h>
  56. #include <sys/socket.h>
  57. #include <arpa/inet.h>
  58. #include <pro/httpd.h>
  59. #include <pro/dhcp.h>
  60. #include <pro/ssi.h>
  61. #include <pro/asp.h>
  62. #include <stdlib.h>
  63. #include <string.h>
  64. #include <io.h>
  65. #include <fcntl.h>
  66. /* Server thread stack size. */
  67. #ifndef HTTPD_SERVICE_STACK
  68. #ifndef NUT_THREAD_MAINSTACK
  69. #define NUT_THREAD_MAINSTACK 1024
  70. #endif
  71. #define HTTPD_SERVICE_STACK NUT_THREAD_MAINSTACK * NUT_THREAD_STACK_MULT + NUT_THREAD_STACK_ADD
  72. #endif
  73. /* ICCAVR Demo is limited. Try to use the bare minimum. */
  74. #if defined(__IMAGECRAFT__)
  75. #define EXCLUDE_ASP
  76. #define EXCLUDE_SSI
  77. #endif
  78. static char *html_mt = "text/html";
  79. /*
  80. * Halt on fatal errors.
  81. */
  82. static void Fatal(char *msg) NUT_NORETURN_FUNC;
  83. void Fatal(char *msg)
  84. {
  85. puts(msg);
  86. for (;;);
  87. }
  88. /*
  89. * Saves some duplicate code.
  90. */
  91. static void StartPage(FILE *stream, REQUEST * req, const char *title)
  92. {
  93. /* These useful API calls create a HTTP response for us. */
  94. NutHttpSendHeaderTop(stream, req, 200, "Ok");
  95. NutHttpSendHeaderBottom(stream, req, html_mt, -1);
  96. fprintf(stream, "<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY><H1>%s</H1>\r\n", title, title);
  97. }
  98. static void StartTable(FILE *stream, ...)
  99. {
  100. va_list ap;
  101. char *cp;
  102. fputs("<TABLE BORDER><TR>", stream);
  103. va_start(ap, stream);
  104. while ((cp = va_arg(ap, char *)) != NULL) {
  105. fprintf(stream, "<TH>%s</TH>", cp);
  106. }
  107. va_end(ap);
  108. fputs("</TR>\r\n", stream);
  109. }
  110. /*
  111. * CGI Sample: Show request parameters.
  112. *
  113. * See httpd.h for REQUEST structure.
  114. *
  115. * This routine must have been registered by NutRegisterCgi() and is
  116. * automatically called by NutHttpProcessRequest() when the client
  117. * request the URL 'cgi-bin/test.cgi'.
  118. */
  119. static int ShowQuery(FILE *stream, REQUEST * req)
  120. {
  121. static const char *method_name[4] = { "?", "GET", "POST", "HEAD" };
  122. /* Send HTML header. */
  123. StartPage(stream, req, "Parameters");
  124. /* Send request parameters. */
  125. fprintf(stream, "Method: %s<BR>\r\n", method_name[req->req_method & 3]);
  126. fprintf(stream, "Version: HTTP/%d.%d<BR>\r\n", req->req_version / 10, req->req_version % 10);
  127. fprintf(stream, "Content length: %ld<BR>\r\n", req->req_length);
  128. if (req->req_url)
  129. fprintf(stream, "URL: %s<BR>\r\n", req->req_url);
  130. if (req->req_query)
  131. fprintf(stream, "Argument: %s<BR>\r\n", req->req_query);
  132. if (req->req_type)
  133. fprintf(stream, "Content type: %s<BR>\r\n", req->req_type);
  134. if (req->req_cookie)
  135. fprintf(stream, "Cookie: %s<BR>\r\n", req->req_cookie);
  136. if (req->req_auth)
  137. fprintf(stream, "Auth info: %s<BR>\r\n", req->req_auth);
  138. if (req->req_agent)
  139. fprintf(stream, "User agent: %s<BR>\r\n", req->req_agent);
  140. /* Send HTML footer and flush output buffer. */
  141. fputs("</BODY></HTML>", stream);
  142. fflush(stream);
  143. return 0;
  144. }
  145. /*
  146. * CGI Sample: Show list of threads.
  147. *
  148. * This routine must have been registered by NutRegisterCgi() and is
  149. * automatically called by NutHttpProcessRequest() when the client
  150. * request the URL 'cgi-bin/threads.cgi'.
  151. */
  152. static int ShowThreads(FILE * stream, REQUEST * req)
  153. {
  154. static char *thread_states[4] = {
  155. "TRM",
  156. "<FONT COLOR=#CC0000>RUN</FONT>",
  157. "<FONT COLOR=#339966>RDY</FONT>",
  158. "SLP"
  159. };
  160. NUTTHREADINFO *tdp;
  161. NUTTHREADINFO *tlist;
  162. int i;
  163. int n;
  164. /* Take a snapshot of our thread list. */
  165. for (tdp = nutThreadList, n = 0; tdp; tdp = tdp->td_next, n++);
  166. tlist = calloc(n, sizeof(NUTTHREADINFO));
  167. if (tlist == NULL) {
  168. return -1;
  169. }
  170. for (tdp = nutThreadList, i = 0; tdp && i < n; tdp = tdp->td_next, i++) {
  171. memcpy(&tlist[i], tdp, sizeof(NUTTHREADINFO));
  172. }
  173. /* Send HTML header. */
  174. StartPage(stream, req, "Threads");
  175. StartTable(stream,
  176. "Name",
  177. "Priority",
  178. "Status",
  179. "Event<BR>Queue",
  180. "Timer",
  181. "Stack-<BR>pointer",
  182. "Free<BR>Stack",
  183. NULL);
  184. /* Send table with list of threads. */
  185. for (i = 0; i < n; i++) {
  186. fprintf(stream,
  187. "<TR><TD>%s</TD>" /* Name */
  188. "<TD>%u</TD>" /* Priority */
  189. "<TD>%s</TD>" /* Status */
  190. "<TD>%08X</TD>" /* Event queue */
  191. "<TD>%08X</TD>" /* Timer */
  192. "<TD>%08X</TD>" /* Stack pointer */
  193. "<TD>%lu %s</TD></TR>\r\n", /* Stack available */
  194. tlist[i].td_name,
  195. tlist[i].td_priority,
  196. thread_states[tlist[i].td_state],
  197. (uintptr_t) tlist[i].td_queue,
  198. (uintptr_t) tlist[i].td_timer,
  199. (uintptr_t) tlist[i].td_sp,
  200. (unsigned long) ((uintptr_t) tlist[i].td_sp - (uintptr_t) tlist[i].td_memory),
  201. *((uint32_t *) tlist[i].td_memory) != DEADBEEF ? "Corrupted" : "");
  202. }
  203. /* Release the thread list copy. */
  204. free(tlist);
  205. /* Send HTML footer and flush output buffer. */
  206. fputs("</TABLE></BODY></HTML>", stream);
  207. fflush(stream);
  208. return 0;
  209. }
  210. /*
  211. * CGI Sample: Show list of timers.
  212. *
  213. * This routine must have been registered by NutRegisterCgi() and is
  214. * automatically called by NutHttpProcessRequest() when the client
  215. * request the URL 'cgi-bin/timers.cgi'.
  216. */
  217. static int ShowTimers(FILE * stream, REQUEST * req)
  218. {
  219. NUTTIMERINFO *tnp;
  220. NUTTIMERINFO *tlist;
  221. uint32_t ticks_left;
  222. int i;
  223. int n;
  224. /* Take a snapshot of our timer list. */
  225. for (tnp = nutTimerList, n = 0; tnp; tnp = tnp->tn_next, n++);
  226. tlist = calloc(n, sizeof(NUTTIMERINFO));
  227. if (tlist == NULL) {
  228. return -1;
  229. }
  230. for (tnp = nutTimerList, i = 0; tnp && i < n; tnp = tnp->tn_next, i++) {
  231. memcpy(&tlist[i], tnp, sizeof(NUTTIMERINFO));
  232. }
  233. /* Send HTML header. */
  234. StartPage(stream, req, "Timers");
  235. StartTable(stream,
  236. "Count-<BR />down",
  237. "Tick<BR />Reload",
  238. "Callback<BR />Address",
  239. "Callback<BR />Argument",
  240. NULL);
  241. /* Send table with list of timers. */
  242. ticks_left = 0;
  243. for (i = 0; i < n; i++) {
  244. ticks_left += tnp->tn_ticks_left;
  245. fprintf(stream,
  246. "<TR><TD>%lu</TD>"
  247. "<TD>%lu</TD>"
  248. "<TD>%08X</TD>"
  249. "<TD>%08X</TD></TR>\r\n",
  250. ticks_left,
  251. tlist[i].tn_ticks,
  252. (uintptr_t) tlist[i].tn_callback,
  253. (uintptr_t) tlist[i].tn_arg);
  254. }
  255. /* Release the thread list copy. */
  256. free(tlist);
  257. /* Send HTML footer and flush output buffer. */
  258. fputs("</TABLE></BODY></HTML>", stream);
  259. fflush(stream);
  260. return 0;
  261. }
  262. /*
  263. * CGI Sample: Show list of sockets.
  264. *
  265. * This routine must have been registered by NutRegisterCgi() and is
  266. * automatically called by NutHttpProcessRequest() when the client
  267. * request the URL 'cgi-bin/sockets.cgi'.
  268. */
  269. static int ShowSockets(FILE * stream, REQUEST * req)
  270. {
  271. static const char *sock_states[12] = {
  272. "CLOSED",
  273. "LISTEN",
  274. "SYNSENT",
  275. "SYNRCVD",
  276. "<FONT COLOR=#CC0000>ESTABL</FONT>",
  277. "CLOSEWAIT",
  278. "FINWAIT1",
  279. "CLOSING",
  280. "LASTACK",
  281. "FINWAIT2",
  282. "TIMEWAIT",
  283. "DESTROY",
  284. "?"
  285. };
  286. extern TCPSOCKET *tcpSocketList;
  287. TCPSOCKET *ts;
  288. TCPSOCKET *tlist;
  289. int i;
  290. int n;
  291. /* Take a snapshot of our socket list. */
  292. for (ts = tcpSocketList, n = 0; ts; ts = ts->so_next, n++);
  293. tlist = calloc(n, sizeof(TCPSOCKET));
  294. if (tlist == NULL) {
  295. return -1;
  296. }
  297. for (ts = tcpSocketList, i = 0; ts && i < n; ts = ts->so_next, i++) {
  298. memcpy(&tlist[i], ts, sizeof(TCPSOCKET));
  299. }
  300. /* Send HTML header. */
  301. StartPage(stream, req, "TCP Sockets");
  302. StartTable(stream, "Local", "Remote", "Status", NULL);
  303. for (i = 0; i < n; i++) {
  304. fprintf(stream, "<TR><TD>%s:%u</TD>", inet_ntoa(tlist[i].so_local_addr), ntohs(tlist[i].so_local_port));
  305. fprintf(stream, "<TD>%s:%u</TD><TD>", inet_ntoa(tlist[i].so_remote_addr), ntohs(tlist[i].so_remote_port));
  306. fputs(sock_states[tlist[i].so_state < 11 ? tlist[i].so_state : 11], stream);
  307. fputs("</TD></TR>\r\n", stream);
  308. fflush(stream);
  309. }
  310. free(tlist);
  311. fputs("</TABLE></BODY></HTML>", stream);
  312. fflush(stream);
  313. return 0;
  314. }
  315. /*
  316. * CGI Sample: Proccessing a form.
  317. *
  318. * This routine must have been registered by NutRegisterCgi() and is
  319. * automatically called by NutHttpProcessRequest() when the client
  320. * request the URL 'cgi-bin/form.cgi'.
  321. *
  322. * Thanks to Tom Boettger, who provided this sample for ICCAVR.
  323. */
  324. int ShowForm(FILE * stream, REQUEST * req)
  325. {
  326. /* Send HTML header. */
  327. StartPage(stream, req, "Form Result");
  328. if (req->req_query) {
  329. int i;
  330. int count;
  331. /* Extract count parameters. */
  332. count = NutHttpGetParameterCount(req);
  333. /* Send the parameters back to the client. */
  334. for (i = 0; i < count; i++) {
  335. fprintf(stream, "%s: %s<BR>\r\n",
  336. NutHttpGetParameterName(req, i),
  337. NutHttpGetParameterValue(req, i));
  338. }
  339. }
  340. fputs("</BODY></HTML>", stream);
  341. fflush(stream);
  342. return 0;
  343. }
  344. #if !defined(EXCLUDE_ASP) && !defined(EXCLUDE_SSI)
  345. static void PrintTime(FILE *stream)
  346. {
  347. #if defined(HTTPD_EXCLUDE_DATE)
  348. fputs(__TIME__, stream);
  349. #else
  350. time_t now = time(NULL);
  351. struct _tm *lot = localtime(&now);
  352. fprintf(stream, "%02d:%02d:%02d", lot->tm_hour, lot->tm_min, lot->tm_sec);
  353. #endif
  354. }
  355. static void PrintDate(FILE *stream)
  356. {
  357. #if defined(HTTPD_EXCLUDE_DATE)
  358. fputs(__DATE__, stream);
  359. #else
  360. time_t now = time(NULL);
  361. struct _tm *lot = localtime(&now);
  362. fprintf(stream, "%02d.%02d.%04d", lot->tm_mday, lot->tm_mon + 1, lot->tm_year + 1900);
  363. #endif
  364. }
  365. #endif
  366. #ifndef EXCLUDE_ASP
  367. /*
  368. * ASPCallback
  369. *
  370. * This routine must have been registered by
  371. * NutRegisterAspCallback() and is automatically called by
  372. * NutHttpProcessFileRequest() when the server process a page
  373. * with an asp function.
  374. *
  375. * Return 0 on success, -1 otherwise.
  376. */
  377. static int ASPCallback(char *pASPFunction, FILE * stream)
  378. {
  379. int rc = 0;
  380. if (strcmp(pASPFunction, "usr_date") == 0) {
  381. PrintDate(stream);
  382. }
  383. else if (strcmp(pASPFunction, "usr_time") == 0) {
  384. PrintTime(stream);
  385. }
  386. else {
  387. rc = -1;
  388. }
  389. return rc;
  390. }
  391. #endif
  392. #ifndef EXCLUDE_SSI
  393. /*
  394. * CGI Sample: Dynamic output cgi included by ssi.shtml file
  395. *
  396. * This routine must have been registered by NutRegisterCgi() and is
  397. * automatically called by NutHttpProcessRequest() when the client
  398. * request the URL 'cgi-bin/form.cgi'.
  399. *
  400. * Thanks to Tom Boettger, who provided this sample for ICCAVR.
  401. */
  402. int SSIDemoCGI(FILE * stream, REQUEST * req)
  403. {
  404. fputs("CGI ssi-demo called with", stream);
  405. if (req->req_query) {
  406. char *name;
  407. char *value;
  408. int i;
  409. int count;
  410. count = NutHttpGetParameterCount(req);
  411. /* Extract count parameters. */
  412. fputs(" these parameters:\r\n<p>", stream);
  413. for (i = 0; i < count; i++) {
  414. name = NutHttpGetParameterName(req, i);
  415. value = NutHttpGetParameterValue(req, i);
  416. /* Send the parameters back to the client. */
  417. fprintf(stream, "%s: %s<BR>\r\n", name, value);
  418. }
  419. } else {
  420. /* Called without any parameter, show the current time */
  421. fputs("out any parameter.<br><br>Current time is: ", stream);
  422. PrintDate(stream);
  423. fputs(" -- ", stream);
  424. PrintTime(stream);
  425. fputs("<br>\r\n", stream);
  426. }
  427. fflush(stream);
  428. return 0;
  429. }
  430. #endif
  431. /*! \fn Service(void *arg)
  432. * \brief HTTP service thread.
  433. *
  434. * The endless loop in this thread waits for a client connect, processes
  435. * the HTTP request and disconnects. If one client has established a
  436. * connection, further connect attempts will be delayed in the TCP
  437. * stack (backlog).
  438. *
  439. * Typically browsers open more than one connection in order
  440. * to load images concurrently. So we run this routine by
  441. * several threads.
  442. *
  443. */
  444. THREAD(Service, arg)
  445. {
  446. TCPSOCKET *sock;
  447. FILE *stream;
  448. uint8_t id = (uint8_t) ((uintptr_t) arg);
  449. /*
  450. * Now loop endless for connections.
  451. */
  452. for (;;) {
  453. /*
  454. * Create a socket.
  455. */
  456. if ((sock = NutTcpCreateSocket()) == 0) {
  457. printf("[%u] Creating socket failed\n", id);
  458. NutSleep(5000);
  459. continue;
  460. }
  461. /*
  462. * Listen on port 80. This call will block until we get a connection
  463. * from a client.
  464. */
  465. NutTcpAccept(sock, 80);
  466. printf("[%u] Connected, %u bytes free\n", id, NutHeapAvailable());
  467. /*
  468. * Wait until at least 4 kByte of free RAM is available. This will
  469. * keep the client connected in low memory situations.
  470. */
  471. while (NutHeapAvailable() < 4096) {
  472. printf("[%u] Low mem\n", id);
  473. NutSleep(1000);
  474. }
  475. /*
  476. * Associate a stream with the socket so we can use standard I/O calls.
  477. */
  478. if ((stream = _fdopen((int) ((uintptr_t) sock), "r+b")) == 0) {
  479. printf("[%u] Creating stream failed\n", id);
  480. } else {
  481. /*
  482. * This API call saves us a lot of work. It will parse the
  483. * client's HTTP request, send any requested file from the
  484. * registered file system or handle CGI requests by calling
  485. * our registered CGI routine.
  486. */
  487. NutHttpProcessRequest(stream);
  488. /*
  489. * Destroy the virtual stream device.
  490. */
  491. fclose(stream);
  492. }
  493. /*
  494. * Close our socket.
  495. */
  496. NutTcpCloseSocket(sock);
  497. printf("[%u] Disconnected\n", id);
  498. }
  499. }
  500. /*!
  501. * \brief Main application routine.
  502. *
  503. * Nut/OS automatically calls this entry after initialization.
  504. */
  505. int main(void)
  506. {
  507. uint32_t baud = 115200;
  508. uint8_t i;
  509. /*
  510. * Initialize stdio console.
  511. */
  512. NutRegisterDevice(&DEV_CONSOLE, 0, 0);
  513. freopen(DEV_CONSOLE.dev_name, "w", stdout);
  514. _ioctl(_fileno(stdout), UART_SETSPEED, &baud);
  515. printf("\n\nNut/OS %s HTTP Daemon...", NutVersionString());
  516. /*
  517. * Initialize Ethernet controller.
  518. */
  519. if (NutRegisterDevice(&DEV_ETHER, 0, 0)) {
  520. Fatal("Registering device failed");
  521. }
  522. printf("Configure %s...", DEV_ETHER_NAME);
  523. if (NutDhcpIfConfig(DEV_ETHER_NAME, 0, 60000)) {
  524. Fatal("failed, run editconf first!");
  525. }
  526. printf("%s ready\n", inet_ntoa(confnet.cdn_ip_addr));
  527. /*
  528. * Register file system.
  529. */
  530. NutRegisterDevice(&devUrom, 0, 0);
  531. /*
  532. * Register our CGI functions.
  533. */
  534. NutRegisterCgiBinPath("cgi-bin/;user/cgi-bin/;admin/cgi-bin/");
  535. NutRegisterCgi("test.cgi", ShowQuery);
  536. NutRegisterCgi("threads.cgi", ShowThreads);
  537. NutRegisterCgi("timers.cgi", ShowTimers);
  538. NutRegisterCgi("sockets.cgi", ShowSockets);
  539. NutRegisterCgi("form.cgi", ShowForm);
  540. NutRegisterAuth("admin", "root:root");
  541. NutRegisterAuth("user", "user:user");
  542. /*
  543. * Register SSI and ASP handler
  544. */
  545. #ifndef EXCLUDE_SSI
  546. /*
  547. * Register a cgi included by the ssi demo. This will show how dynamic
  548. * content is included in a ssi page and how the request parameters for
  549. * a site are passed down to the included cgi.
  550. */
  551. NutRegisterCgi("ssi-demo.cgi", SSIDemoCGI);
  552. NutRegisterSsi();
  553. #endif
  554. #ifndef EXCLUDE_ASP
  555. NutRegisterAsp();
  556. NutRegisterAspCallback(ASPCallback);
  557. #endif
  558. /*
  559. * Start HTTP server threads, passing a thread number.
  560. */
  561. for (i = 1; i <= 4; i++) {
  562. NutThreadCreate("httpd", Service, (void *) (uintptr_t) i, HTTPD_SERVICE_STACK);
  563. }
  564. /*
  565. * We could do something useful here, like serving a watchdog.
  566. */
  567. for (;;) {
  568. NutSleep(60000);
  569. }
  570. return 0;
  571. }