| /**************************************************************************** |
| * apps/netutils/thttpd/thttpd.c |
| * Tiny HTTP Server |
| * |
| * Copyright (C) 2009, 2011 Gregory Nutt. All rights reserved. |
| * Author: Gregory Nutt <gnutt@nuttx.org> |
| * |
| * Derived from the file of the same name in the original THTTPD package: |
| * |
| * Copyright (C) 1995,1998,1999,2000,2001 by |
| * Jef Poskanzer <jef@mail.acme.com>. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <arpa/inet.h> |
| |
| #include <nuttx/compiler.h> |
| #include <nuttx/symtab.h> |
| #include "netutils/thttpd.h" |
| |
| #include "config.h" |
| #include "fdwatch.h" |
| #include "libhttpd.h" |
| #include "thttpd_alloc.h" |
| #include "thttpd_strings.h" |
| #include "timers.h" |
| |
| #ifdef CONFIG_THTTPD |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #ifndef MAXPATHLEN |
| # define MAXPATHLEN 64 |
| #endif |
| |
| /* The connection states */ |
| |
| #define CNST_FREE 0 |
| #define CNST_READING 1 |
| #define CNST_SENDING 2 |
| #define CNST_LINGERING 3 |
| |
| #define SPARE_FDS 2 |
| #define AVAILABLE_FDS (CONFIG_THTTPD_NFILE_DESCRIPTORS - SPARE_FDS) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct connect_s |
| { |
| struct connect_s *next; |
| int conn_state; |
| httpd_conn *hc; |
| time_t active_at; |
| Timer *wakeup_timer; |
| Timer *linger_timer; |
| off_t end_offset; /* The final offset+1 of the file to send */ |
| off_t offset; /* The current offset into the file to send */ |
| bool eof; /* Set true when length==0 read from file */ |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static httpd_server *hs; |
| static struct connect_s *free_connections; |
| static struct connect_s *connects; |
| static struct fdwatch_s *fw; |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static void shut_down(void); |
| static int handle_newconnect(struct timeval *tv, int listen_fd); |
| static void handle_read(struct connect_s *conn, struct timeval *tv); |
| static void handle_send(struct connect_s *conn, struct timeval *tv); |
| static void handle_linger(struct connect_s *conn, struct timeval *tv); |
| static void finish_connection(struct connect_s *conn, struct timeval *tv); |
| static void clear_connection(struct connect_s *conn, struct timeval *tv); |
| static void really_clear_connection(struct connect_s *conn); |
| static void idle(ClientData client_data, struct timeval *nowp); |
| static void linger_clear_connection(ClientData client_data, |
| struct timeval *nowp); |
| static void occasional(ClientData client_data, struct timeval *nowp); |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static void shut_down(void) |
| { |
| int cnum; |
| |
| for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) |
| { |
| if (connects[cnum].conn_state != CNST_FREE) |
| { |
| httpd_close_conn(connects[cnum].hc); |
| } |
| |
| if (connects[cnum].hc != NULL) |
| { |
| httpd_destroy_conn(connects[cnum].hc); |
| httpd_free(connects[cnum].hc); |
| connects[cnum].hc = NULL; |
| } |
| } |
| |
| if (hs) |
| { |
| httpd_server *ths = hs; |
| hs = NULL; |
| if (ths->listen_fd != -1) |
| { |
| fdwatch_del_fd(fw, ths->listen_fd); |
| } |
| |
| httpd_terminate(ths); |
| } |
| |
| tmr_destroy(); |
| httpd_free(connects); |
| } |
| |
| static int handle_newconnect(FAR struct timeval *tv, int listen_fd) |
| { |
| FAR struct connect_s *conn; |
| |
| /* This loops until the accept() fails, trying to start new connections as |
| * fast as possible so we don't overrun the listen queue. |
| */ |
| |
| ninfo("New connection(s) on listen_fd %d\n", listen_fd); |
| for (; ; ) |
| { |
| /* Get the next free connection from the free list */ |
| |
| conn = free_connections; |
| |
| /* Are there any free connections? */ |
| |
| if (!conn) |
| { |
| /* Out of connection slots. Run the timers, then the existing |
| * connections, and maybe we'll free up a slot by the time we get |
| * back here. |
| */ |
| |
| nerr("ERROR: No free connections\n"); |
| tmr_run(tv); |
| return -1; |
| } |
| |
| /* Make the httpd_conn if necessary */ |
| |
| if (!conn->hc) |
| { |
| conn->hc = NEW(httpd_conn, 1); |
| if (conn->hc == NULL) |
| { |
| nerr("ERROR: out of memory allocating an httpd_conn\n"); |
| exit(1); |
| } |
| |
| conn->hc->initialized = 0; |
| } |
| |
| /* Get the connection */ |
| |
| switch (httpd_get_conn(hs, listen_fd, conn->hc)) |
| { |
| /* Some error happened. Run the timers, then the existing |
| * connections. Maybe the error will clear. |
| */ |
| |
| case GC_FAIL: |
| tmr_run(tv); |
| return -1; |
| |
| /* No more connections to accept for now */ |
| |
| case GC_NO_MORE: |
| return 0; |
| |
| default: |
| break; |
| } |
| |
| ninfo("New connection fd %d\n", conn->hc->conn_fd); |
| |
| /* Remove the connection entry from the free list */ |
| |
| conn->conn_state = CNST_READING; |
| free_connections = conn->next; |
| conn->next = NULL; |
| |
| conn->active_at = tv->tv_sec; |
| conn->wakeup_timer = NULL; |
| conn->linger_timer = NULL; |
| conn->offset = 0; |
| |
| /* Set the connection file descriptor to no-delay mode */ |
| |
| httpd_set_ndelay(conn->hc->conn_fd); |
| fdwatch_add_fd(fw, conn->hc->conn_fd, conn); |
| } |
| } |
| |
| static void handle_read(struct connect_s *conn, struct timeval *tv) |
| { |
| httpd_conn *hc = conn->hc; |
| off_t actual; |
| int sz; |
| |
| /* Is there room in our buffer to read more bytes? */ |
| |
| if (hc->read_idx >= hc->read_size) |
| { |
| if (hc->read_size > CONFIG_THTTPD_MAXREALLOC) |
| { |
| BADREQUEST("MAXREALLOC"); |
| goto errout_with_400; |
| } |
| |
| httpd_realloc_str(&hc->read_buf, &hc->read_size, |
| hc->read_size + CONFIG_THTTPD_REALLOCINCR); |
| } |
| |
| /* Read some more bytes */ |
| |
| sz = read(hc->conn_fd, &(hc->read_buf[hc->read_idx]), |
| hc->read_size - hc->read_idx); |
| if (sz == 0) |
| { |
| BADREQUEST("EOF"); |
| goto errout_with_400; |
| } |
| |
| if (sz < 0) |
| { |
| /* Ignore EINTR and EAGAIN. Also ignore EWOULDBLOCK. At first glance |
| * you would think that connections returned by fdwatch as readable |
| * should never give an EWOULDBLOCK; however, this apparently can |
| * happen if a packet gets garbled. |
| */ |
| |
| if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) |
| { |
| return; |
| } |
| |
| nerr("ERROR: read(fd=%d) failed: %d\n", hc->conn_fd, errno); |
| BADREQUEST("read"); |
| goto errout_with_400; |
| } |
| |
| hc->read_idx += sz; |
| conn->active_at = tv->tv_sec; |
| |
| /* Do we have a complete request yet? */ |
| |
| switch (httpd_got_request(hc)) |
| { |
| case GR_NO_REQUEST: |
| return; |
| case GR_BAD_REQUEST: |
| BADREQUEST("httpd_got_request"); |
| goto errout_with_400; |
| } |
| |
| /* Yes. Try parsing and resolving it */ |
| |
| if (httpd_parse_request(hc) < 0) |
| { |
| goto errout_with_connection; |
| } |
| |
| /* Start the connection going */ |
| |
| if (httpd_start_request(hc, tv) < 0) |
| { |
| /* Something went wrong. Close down the connection */ |
| |
| goto errout_with_connection; |
| } |
| |
| /* Set up the file offsets to read */ |
| |
| conn->eof = false; |
| if (hc->got_range) |
| { |
| conn->offset = hc->range_start; |
| conn->end_offset = hc->range_end + 1; |
| } |
| else |
| { |
| conn->offset = 0; |
| if (hc->bytes_to_send < 0) |
| { |
| conn->end_offset = 0; |
| } |
| else |
| { |
| conn->end_offset = hc->bytes_to_send; |
| } |
| } |
| |
| /* Check if it's already handled */ |
| |
| if (hc->file_fd < 0) |
| { |
| /* No file descriptor means someone else is handling it */ |
| |
| conn->offset = hc->bytes_sent; |
| goto errout_with_connection; |
| } |
| |
| if (conn->offset >= conn->end_offset) |
| { |
| /* There's nothing to send */ |
| |
| goto errout_with_connection; |
| } |
| |
| /* Seek to the offset of the next byte to send */ |
| |
| actual = lseek(hc->file_fd, conn->offset, SEEK_SET); |
| if (actual != conn->offset) |
| { |
| nerr("ERROR: fseek to %jd failed: offset=%jd errno=%d\n", |
| (intmax_t)conn->offset, (intmax_t)actual, errno); |
| BADREQUEST("lseek"); |
| goto errout_with_400; |
| } |
| |
| /* We have a valid connection and a file to send to it */ |
| |
| conn->conn_state = CNST_SENDING; |
| fdwatch_del_fd(fw, hc->conn_fd); |
| return; |
| |
| errout_with_400: |
| BADREQUEST("errout"); |
| httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, ""); |
| |
| errout_with_connection: |
| finish_connection(conn, tv); |
| } |
| |
| static inline int read_buffer(struct connect_s *conn) |
| { |
| httpd_conn *hc = conn->hc; |
| ssize_t nread = 0; |
| |
| if (hc->buflen < CONFIG_THTTPD_IOBUFFERSIZE && !conn->eof) |
| { |
| nread = read(hc->file_fd, &hc->buffer[hc->buflen], |
| CONFIG_THTTPD_IOBUFFERSIZE - hc->buflen); |
| if (nread == 0) |
| { |
| /* Reading zero bytes means we are at the end of file */ |
| |
| conn->end_offset = conn->offset; |
| conn->eof = true; |
| } |
| else if (nread > 0) |
| { |
| hc->buflen += nread; |
| } |
| } |
| |
| return nread; |
| } |
| |
| static void handle_send(struct connect_s *conn, struct timeval *tv) |
| { |
| httpd_conn *hc = conn->hc; |
| int nwritten; |
| int nread; |
| |
| /* Read until the entire file is sent -- this could take awhile!! */ |
| |
| while (conn->offset < conn->end_offset) |
| { |
| ninfo("offset: %jd end_offset: %jd bytes_sent: %jd\n", |
| (intmax_t)conn->offset, |
| (intmax_t)conn->end_offset, |
| (intmax_t)conn->hc->bytes_sent); |
| |
| /* Fill the rest of the response buffer with file data */ |
| |
| nread = read_buffer(conn); |
| if (nread < 0) |
| { |
| nerr("ERROR: File read error: %d\n", errno); |
| goto errout_clear_connection; |
| } |
| |
| ninfo("Read %d bytes, buflen %d\n", nread, hc->buflen); |
| |
| /* Send the buffer */ |
| |
| if (hc->buflen > 0) |
| { |
| /* httpd_write does not return until all bytes have been sent |
| * (or an error occurs). |
| */ |
| |
| nwritten = httpd_write(hc->conn_fd, hc->buffer, hc->buflen); |
| if (nwritten < 0) |
| { |
| nerr("ERROR: Error sending %s: %d\n", |
| hc->encodedurl, errno); |
| goto errout_clear_connection; |
| } |
| |
| /* We wrote one full buffer of data (httpd_write does not |
| * return until the full buffer is written (or an error occurs). |
| */ |
| |
| conn->active_at = tv->tv_sec; |
| hc->buflen = 0; |
| |
| /* And update how much of the file we wrote */ |
| |
| conn->offset += nwritten; |
| conn->hc->bytes_sent += nwritten; |
| ninfo("Wrote %d bytes\n", nwritten); |
| } |
| } |
| |
| /* The file transfer is complete -- finish the connection */ |
| |
| ninfo("Finish connection\n"); |
| finish_connection(conn, tv); |
| return; |
| |
| errout_clear_connection: |
| ninfo("Clear connection\n"); |
| clear_connection(conn, tv); |
| } |
| |
| static void handle_linger(struct connect_s *conn, struct timeval *tv) |
| { |
| httpd_conn *hc = conn->hc; |
| int ret; |
| |
| /* In lingering-close mode we just read and ignore bytes. An error or EOF |
| * ends things, otherwise we go until a timeout |
| */ |
| |
| ret = read(conn->hc->conn_fd, hc->buffer, CONFIG_THTTPD_IOBUFFERSIZE); |
| if (ret < 0 && (errno == EINTR || errno == EAGAIN)) |
| { |
| return; |
| } |
| |
| if (ret <= 0) |
| { |
| really_clear_connection(conn); |
| } |
| } |
| |
| static void finish_connection(struct connect_s *conn, struct timeval *tv) |
| { |
| /* If we haven't actually sent the buffered response yet, do so now */ |
| |
| httpd_write_response(conn->hc); |
| |
| /* And clear */ |
| |
| clear_connection(conn, tv); |
| } |
| |
| static void clear_connection(struct connect_s *conn, struct timeval *tv) |
| { |
| ClientData client_data; |
| |
| if (conn->wakeup_timer != NULL) |
| { |
| tmr_cancel(conn->wakeup_timer); |
| conn->wakeup_timer = 0; |
| } |
| |
| /* This is our version of Apache's lingering_close() routine, which is |
| * their version of the often-broken SO_LINGER socket option. For why |
| * this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html |
| * What we do is delay the actual closing for a few seconds, while reading |
| * any bytes that come over the connection. However, we don't want to do |
| * this unless it's necessary, because it ties up a connection slot and |
| * file descriptor which means our maximum connection-handling rateis |
| * lower. So, elsewhere we set a flag when we detect the few |
| * circumstances that make a lingering close necessary. If the flag isn't |
| * set we do the real close now. |
| */ |
| |
| if (conn->conn_state == CNST_LINGERING) |
| { |
| /* If we were already lingering, shut down for real */ |
| |
| tmr_cancel(conn->linger_timer); |
| conn->linger_timer = NULL; |
| conn->hc->should_linger = false; |
| } |
| else if (conn->hc->should_linger) |
| { |
| fdwatch_del_fd(fw, conn->hc->conn_fd); |
| conn->conn_state = CNST_LINGERING; |
| fdwatch_add_fd(fw, conn->hc->conn_fd, conn); |
| client_data.p = conn; |
| |
| conn->linger_timer = tmr_create(tv, linger_clear_connection, |
| client_data, |
| CONFIG_THTTPD_LINGER_MSEC, 0); |
| if (conn->linger_timer != NULL) |
| { |
| return; |
| } |
| |
| nerr("ERROR: tmr_create(linger_clear_connection) failed\n"); |
| } |
| |
| /* Either we are done lingering, we shouldn't linger, or we failed to |
| * setup the linger |
| */ |
| |
| really_clear_connection(conn); |
| } |
| |
| static void really_clear_connection(struct connect_s *conn) |
| { |
| fdwatch_del_fd(fw, conn->hc->conn_fd); |
| httpd_close_conn(conn->hc); |
| if (conn->linger_timer != NULL) |
| { |
| tmr_cancel(conn->linger_timer); |
| conn->linger_timer = 0; |
| } |
| |
| /* Put the connection structure back on the free list */ |
| |
| conn->conn_state = CNST_FREE; |
| conn->next = free_connections; |
| free_connections = conn; |
| } |
| |
| static void idle(ClientData client_data, struct timeval *nowp) |
| { |
| int cnum; |
| struct connect_s *conn; |
| |
| for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) |
| { |
| conn = &connects[cnum]; |
| switch (conn->conn_state) |
| { |
| case CNST_READING: |
| if (nowp->tv_sec - conn->active_at >= |
| CONFIG_THTTPD_IDLE_READ_LIMIT_SEC) |
| { |
| nerr("ERROR: %s connection timed out reading\n", |
| httpd_ntoa(&conn->hc->client_addr)); |
| httpd_send_err(conn->hc, 408, httpd_err408title, "", |
| httpd_err408form, ""); |
| finish_connection(conn, nowp); |
| } |
| break; |
| |
| case CNST_SENDING: |
| if (nowp->tv_sec - conn->active_at >= |
| CONFIG_THTTPD_IDLE_SEND_LIMIT_SEC) |
| { |
| nerr("ERROR: %s connection timed out sending\n", |
| httpd_ntoa(&conn->hc->client_addr)); |
| clear_connection(conn, nowp); |
| } |
| break; |
| } |
| } |
| } |
| |
| static void linger_clear_connection(ClientData client_data, |
| struct timeval *nowp) |
| { |
| struct connect_s *conn; |
| |
| ninfo("Clear connection\n"); |
| conn = (struct connect_s *)client_data.p; |
| conn->linger_timer = NULL; |
| really_clear_connection(conn); |
| } |
| |
| static void occasional(ClientData client_data, struct timeval *nowp) |
| { |
| tmr_cleanup(); |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Function: thttpd_main |
| * |
| * Description: |
| * This function is the entrypoint into the THTTPD server. It does not |
| * return. It may be called, the normal mechanism for starting the server |
| * is: |
| * |
| * 1) Set is g_thttpdsymtab and g_thttpdnsymbols. The user is required |
| * to provide a symbol table to use for binding CGI programs (if CGI |
| * is enabled. See examples/nxflat and examples/thttpd for examples of |
| * how such a symbol table may be created. |
| * 2) Call task_create() to start thttpd_main() |
| * |
| ****************************************************************************/ |
| |
| int thttpd_main(int argc, char **argv) |
| { |
| int num_ready; |
| int cnum; |
| FAR struct connect_s *conn; |
| FAR httpd_conn *hc; |
| httpd_sockaddr sa; |
| struct timeval tv; |
| #ifdef CONFIG_THTTPD_DIR |
| int ret; |
| #endif |
| |
| ninfo("THTTPD started\n"); |
| |
| /* Setup host address */ |
| |
| #ifdef CONFIG_NET_IPv6 |
| # error "IPv6 support not yet implemented" |
| #else |
| sa.sin_family = AF_INET; |
| sa.sin_port = HTONS(CONFIG_THTTPD_PORT); |
| sa.sin_addr.s_addr = HTONL(CONFIG_THTTPD_IPADDR); |
| #endif |
| |
| /* Initialize the fdwatch package to handle all of the configured |
| * socket descriptors |
| */ |
| |
| fw = fdwatch_initialize(CONFIG_THTTPD_NFILE_DESCRIPTORS); |
| if (!fw) |
| { |
| nerr("ERROR: fdwatch initialization failure\n"); |
| exit(1); |
| } |
| |
| /* Switch directories again if requested */ |
| |
| #ifdef CONFIG_THTTPD_DATADIR |
| if (chdir(CONFIG_THTTPD_DATADIR) < 0) |
| { |
| ninfo("chdir to %s: %d\n", |
| CONFIG_THTTPD_DATADIR, errno); |
| exit(1); |
| } |
| #endif |
| |
| /* Initialize the timer package */ |
| |
| tmr_init(); |
| |
| /* Initialize the HTTP layer */ |
| |
| ninfo("Calling httpd_initialize()\n"); |
| hs = httpd_initialize(&sa); |
| if (!hs) |
| { |
| nerr("ERROR: httpd_initialize() failed\n"); |
| exit(1); |
| } |
| |
| /* Set up the occasional timer */ |
| |
| if (tmr_create(NULL, occasional, JunkClientData, |
| CONFIG_THTTPD_OCCASIONAL_MSEC * 1000L, 1) == NULL) |
| { |
| nerr("ERROR: tmr_create(occasional) failed\n"); |
| exit(1); |
| } |
| |
| /* Set up the idle timer */ |
| |
| if (tmr_create(NULL, idle, JunkClientData, 5 * 1000L, 1) == NULL) |
| { |
| nerr("ERROR: tmr_create(idle) failed\n"); |
| exit(1); |
| } |
| |
| /* Initialize our connections table */ |
| |
| connects = NEW(struct connect_s, AVAILABLE_FDS); |
| if (connects == NULL) |
| { |
| nerr("ERROR: Out of memory allocating a struct connect_s\n"); |
| exit(1); |
| } |
| |
| for (cnum = 0; cnum < AVAILABLE_FDS; ++cnum) |
| { |
| connects[cnum].conn_state = CNST_FREE; |
| connects[cnum].next = &connects[cnum + 1]; |
| connects[cnum].hc = NULL; |
| } |
| |
| connects[AVAILABLE_FDS - 1].next = NULL; /* End of link list */ |
| free_connections = connects; /* Beginning of the link list */ |
| |
| if (hs != NULL) |
| { |
| if (hs->listen_fd != -1) |
| { |
| fdwatch_add_fd(fw, hs->listen_fd, NULL); |
| } |
| } |
| |
| /* Main loop */ |
| |
| ninfo("Entering the main loop\n"); |
| gettimeofday(&tv, NULL); |
| for (; ; ) |
| { |
| /* Do the fd watch */ |
| |
| num_ready = fdwatch(fw, tmr_mstimeout(&tv)); |
| if (num_ready < 0) |
| { |
| if (errno == EINTR || errno == EAGAIN) |
| { |
| /* Not errors... try again */ |
| |
| continue; |
| } |
| |
| nerr("ERROR: fdwatch failed: %d\n", errno); |
| exit(1); |
| } |
| |
| gettimeofday(&tv, NULL); |
| |
| if (num_ready == 0) |
| { |
| /* No fd's are ready - run the timers */ |
| |
| tmr_run(&tv); |
| continue; |
| } |
| |
| /* Is it a new connection? */ |
| |
| if (fdwatch_check_fd(fw, hs->listen_fd)) |
| { |
| if (!handle_newconnect(&tv, hs->listen_fd)) |
| { |
| /* Go around the loop and do another fdwatch, rather than |
| * dropping through and processing existing connections. New |
| * connections always get priority. |
| */ |
| |
| continue; |
| } |
| } |
| |
| /* Find the connections that need servicing */ |
| |
| while ((conn = (struct connect_s *)fdwatch_get_next_client_data(fw)) |
| != (struct connect_s *)-1) |
| { |
| if (conn) |
| { |
| hc = conn->hc; |
| if (fdwatch_check_fd(fw, hc->conn_fd)) |
| { |
| ninfo("Handle conn_state %d\n", conn->conn_state); |
| switch (conn->conn_state) |
| { |
| case CNST_READING: |
| { |
| handle_read(conn, &tv); |
| |
| /* If a GET request was received and a file is |
| * ready to be sent, then fall through to send |
| * the file. |
| */ |
| |
| if (conn->conn_state != CNST_SENDING) |
| { |
| break; |
| } |
| } |
| |
| case CNST_SENDING: |
| { |
| /* Send a file -- this really should be performed |
| * on a separate thread to keep the serve from |
| * locking up during the write. |
| */ |
| |
| handle_send(conn, &tv); |
| } |
| break; |
| |
| case CNST_LINGERING: |
| { |
| /* Linger close the connection */ |
| |
| handle_linger(conn, &tv); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| tmr_run(&tv); |
| } |
| |
| /* The main loop terminated */ |
| |
| shut_down(); |
| ninfo("Exiting\n"); |
| exit(0); |
| } |
| |
| #endif /* CONFIG_THTTPD */ |