nuttx-apps/netutils/webserver/httpd.c
Xiang Xiao 857158451b Unify the void cast usage
1.Remove void cast for function because many place ignore the returned value witout cast
2.Replace void cast for variable with UNUSED macro

Change-Id: Ie644129a563244a6397036789c4c3ea83c4e9b09
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2020-01-02 23:21:01 +08:00

1064 lines
27 KiB
C

/****************************************************************************
* netutils/webserver/httpd.c
* httpd Web server
*
* Copyright (C) 2007-2009, 2011-2012 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* This is a leverage of similar logic from uIP:
*
* Author: Adam Dunkels <adam@sics.se>
* Copyright (c) 2004, Adam Dunkels.
* All rights reserved.
*
* The uIP web server is a very simplistic implementation of an HTTP
* server. It can serve web pages and files from a read-only ROM
* filesystem, and provides a very small scripting language.
*
* 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.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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/socket.h>
#include <sys/time.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#ifndef CONFIG_NETUTILS_HTTPD_SINGLECONNECT
# include <pthread.h>
#endif
#include <arpa/inet.h>
#include "netutils/netlib.h"
#include "netutils/httpd.h"
#include "httpd.h"
#include "httpd_cgi.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if !defined(CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE) && \
defined(CONFIG_NETUTILS_HTTPD_SENDFILE)
# error "Script support and CONFIG_NETUTILS_HTTPD_SENDFILE are mutually exclusive"
#endif
#if defined(CONFIG_NETUTILS_HTTPD_SENDFILE) && \
defined(CONFIG_NETUTILS_HTTPD_MMAP)
# error "CONFIG_NETUTILS_HTTPD_SENDFILE and CONFIG_NETUTILS_HTTPD_MMAP are mutually exclusive"
#endif
#define ISO_nl 0x0a
#define ISO_space 0x20
#define ISO_bang 0x21
#define ISO_percent 0x25
#define ISO_period 0x2e
#define ISO_slash 0x2f
#define ISO_colon 0x3a
#ifndef CONFIG_NETUTILS_HTTPD_PATH
# define CONFIG_NETUTILS_HTTPD_PATH "/mnt"
#endif
#ifndef CONFIG_NETUTILS_HTTPD_ERRPATH
# define CONFIG_NETUTILS_HTTPD_ERRPATH ""
#endif
/* The correct way to disable receive timeout errors is by setting the
* timeout to zero.
*/
#ifndef CONFIG_NETUTILS_HTTPD_TIMEOUT
# define CONFIG_NETUTILS_HTTPD_TIMEOUT 0
#endif
/* If timeouts are not enabled, then keep-alive is disabled. This is to
* prevent a rogue HTTP client from blocking the httpd indefinitely.
*/
#if !defined(CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE)
# if CONFIG_NETUTILS_HTTPD_TIMEOUT == 0
# define CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
# endif
#endif
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
# ifndef CONFIG_NETUTILS_HTTPD_INDEX
# ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
# define CONFIG_NETUTILS_HTTPD_INDEX "index.shtml"
# else
# define CONFIG_NETUTILS_HTTPD_INDEX "index.html"
# endif
# endif
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
static int httpd_open(const char *name, struct httpd_fs_file *file)
{
#if defined(CONFIG_NETUTILS_HTTPD_CLASSIC)
return httpd_fs_open(name, file);
#elif defined(CONFIG_NETUTILS_HTTPD_MMAP)
return httpd_mmap_open(name, file);
#elif defined(CONFIG_NETUTILS_HTTPD_SENDFILE)
return httpd_sendfile_open(name, file);
#else
# error "No file handling method"
#endif
}
static int httpd_openindex(struct httpd_state *pstate)
{
int ret;
size_t z;
z = strlen(pstate->ht_filename);
if (z > 0 && pstate->ht_filename[z - 1] == '/')
{
pstate->ht_filename[--z] = '\0';
}
ret = httpd_open(pstate->ht_filename, &pstate->ht_file);
#if defined(CONFIG_NETUTILS_HTTPD_SENDFILE) || \
defined(CONFIG_NETUTILS_HTTPD_MMAP)
# if defined(CONFIG_NETUTILS_HTTPD_INDEX)
if (ret == ERROR && errno == EISDIR)
{
snprintf(pstate->ht_filename + z,
sizeof pstate->ht_filename - z, "/%s",
CONFIG_NETUTILS_HTTPD_INDEX);
ret = httpd_open(pstate->ht_filename, &pstate->ht_file);
}
# endif
#endif
return ret;
}
static int httpd_close(struct httpd_fs_file *file)
{
#if defined(CONFIG_NETUTILS_HTTPD_CLASSIC)
return OK;
#elif defined(CONFIG_NETUTILS_HTTPD_MMAP)
return httpd_mmap_close(file);
#elif defined(CONFIG_NETUTILS_HTTPD_SENDFILE)
return httpd_sendfile_close(file);
#else
# error "No file handling method"
#endif
}
/****************************************************************************
* Name: httpd_send_datachunk
*
* Description:
* Sends a chunk of HTML data using either chunked or non-chunked encoding.
*
* Input Parameters:
* sockfd Socket to which to send the data.
* data Data to send
* len Length of data to send
* chunked If True, sends an HTTP Chunked-Encoding prolog before the data
* block, and a HTTP Chunked-Encoding epilog ("\r\n") after the
* data block. If False, just sends the data.
*
* Returned Value:
* On success, returns >=0. On failure, returns a negative number indicating
* the failure code.
*
****************************************************************************/
int httpd_send_datachunk(int sockfd, void *data, int len, bool chunked)
{
int ret = 0;
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
char chunked_info[HTTPD_MAX_CHUNKEDLEN];
#endif
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
/* Chunk prolog */
if (chunked)
{
int chunked_info_len = snprintf(chunked_info, HTTPD_MAX_CHUNKEDLEN,
"%X\r\n", len);
ret = send(sockfd, chunked_info, chunked_info_len, 0);
DEBUGASSERT(ret == chunked_info_len);
}
#endif
if (ret >= 0)
{
if (len == 0)
{
/* Lower layer does not tolerate buf = NULL even if len = 0
* so just pass a dummy pointer.
*/
data = &len;
}
ret = send(sockfd, data, len, 0);
DEBUGASSERT(ret == len);
}
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
/* Chunk epilog */
if (ret >= 0)
{
if (chunked)
{
ret = send(sockfd, "\r\n", 2, 0);
DEBUGASSERT(ret == 2);
}
}
#endif
return ret;
}
#ifdef CONFIG_NETUTILS_HTTPD_DUMPBUFFER
static void httpd_dumpbuffer(FAR const char *msg, FAR const char *buffer,
unsigned int nbytes)
{
/* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to be
* defined or the following does nothing.
*/
ninfodumpbuffer(msg, (FAR const uint8_t *)buffer, nbytes);
}
#else
# define httpd_dumpbuffer(msg,buffer,nbytes)
#endif
#ifdef CONFIG_NETUTILS_HTTPD_DUMPPSTATE
static void httpd_dumppstate(struct httpd_state *pstate, const char *msg)
{
#if defined(CONFIG_DEBUG_FEATURES) && defined(CONFIG_DEBUG_INFO) && defined(CONFIG_DEBUG_NET)
ninfo("[%d] pstate(%p): [%s]\n", pstate->ht_sockfd, pstate, msg);
ninfo(" filename: [%s]\n", pstate->ht_filename);
ninfo(" htfile len: %d\n", pstate->ht_file.len);
ninfo(" sockfd: %d\n", pstate->ht_sockfd);
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
ninfo(" scriptptr: %p\n", pstate->ht_scriptptr);
ninfo(" scriptlen: %d\n", pstate->ht_scriptlen);
#endif
ninfo(" sndlen: %d\n", pstate->ht_sndlen);
#endif
}
#else
# define httpd_dumppstate(pstate, msg)
#endif
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
static void next_scriptstate(struct httpd_state *pstate)
{
char *p;
p = strchr(pstate->ht_scriptptr, ISO_nl) + 1;
pstate->ht_scriptlen -= (unsigned short)(p - pstate->ht_scriptptr);
pstate->ht_scriptptr = p;
}
#endif
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
static int handle_script(struct httpd_state *pstate)
{
int len;
char *ptr;
int status;
bool chunked_http_tx = 0;
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
chunked_http_tx = pstate->ht_chunked;
#endif
while (pstate->ht_file.len > 0)
{
/* Check if we should start executing a script */
if (*pstate->ht_file.data == ISO_percent &&
*(pstate->ht_file.data + 1) == ISO_bang)
{
pstate->ht_scriptptr = pstate->ht_file.data + 3;
pstate->ht_scriptlen = pstate->ht_file.len - 3;
if (*(pstate->ht_scriptptr - 1) == ISO_colon)
{
if (httpd_open(pstate->ht_scriptptr + 1,
&pstate->ht_file) != OK)
{
return ERROR;
}
status = httpd_send_datachunk(pstate->ht_sockfd,
pstate->ht_file.data,
pstate->ht_file.len,
chunked_http_tx);
DEBUGASSERT(status >= 0);
UNUSED(status);
httpd_close(&pstate->ht_file);
}
else
{
httpd_cgifunction f;
f = httpd_cgi(pstate->ht_scriptptr);
if (f != NULL)
{
f(pstate, pstate->ht_scriptptr);
}
}
next_scriptstate(pstate);
/* The script is over, so we reset the pointers and continue
* sending the rest of the file
*/
pstate->ht_file.data = pstate->ht_scriptptr;
pstate->ht_file.len = pstate->ht_scriptlen;
}
else
{
/* See if we find the start of script marker in the block of HTML
* to be sent
*/
if (pstate->ht_file.len > HTTPD_IOBUFFER_SIZE)
{
len = HTTPD_IOBUFFER_SIZE;
}
else
{
len = pstate->ht_file.len;
}
if (*pstate->ht_file.data == ISO_percent)
{
ptr = strchr(pstate->ht_file.data + 1, ISO_percent);
}
else
{
ptr = strchr(pstate->ht_file.data, ISO_percent);
}
if (ptr != NULL && ptr != pstate->ht_file.data)
{
len = (int)(ptr - pstate->ht_file.data);
if (len >= HTTPD_IOBUFFER_SIZE)
{
len = HTTPD_IOBUFFER_SIZE;
}
}
status = httpd_send_datachunk(pstate->ht_sockfd,
pstate->ht_file.data,
len, chunked_http_tx);
DEBUGASSERT(status >= 0);
UNUSED(status);
pstate->ht_file.data += len;
pstate->ht_file.len -= len;
}
}
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
/* Chunked encoding terminator */
status = httpd_send_datachunk(pstate->ht_sockfd, 0, 0, chunked_http_tx);
DEBUGASSERT(status >= 0);
UNUSED(status);
#endif
return OK;
}
#endif
static int send_chunk(struct httpd_state *pstate, const char *buf, int len)
{
int ret;
do
{
httpd_dumpbuffer("Outgoing chunk", buf, len);
ret = send(pstate->ht_sockfd, buf, len, 0);
if (ret < 0)
{
return ERROR;
}
buf += ret;
len -= ret;
}
while (len > 0);
return OK;
}
static int send_headers(struct httpd_state *pstate, int status, int len)
{
const char *mime;
const char *ptr;
char contentlen[HTTPD_MAX_CONTENTLEN] =
{
0
};
char header[HTTPD_MAX_HEADERLEN];
int hdrlen;
int i;
static const struct
{
const char *ext;
const char *mime;
} a[] =
{
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
{
"shtml", "text/html"
},
#endif
{
"html", "text/html"
},
{
"css", "text/css"
},
{
"txt", "text/plain"
},
{
"js", "text/javascript"
},
{
"png", "image/png"
},
{
"gif", "image/gif"
},
{
"jpeg", "image/jpeg"
},
{
"jpg", "image/jpeg"
},
{ "mp3", "audio/mpeg"
}
};
ptr = strrchr(pstate->ht_filename, ISO_period);
if (ptr == NULL)
{
mime = "application/octet-stream";
}
else
{
mime = "text/plain";
for (i = 0; i < sizeof a / sizeof *a; i++)
{
if (strncmp(a[i].ext, ptr + 1, strlen(a[i].ext)) == 0)
{
mime = a[i].mime;
break;
}
}
}
#ifdef CONFIG_NETUTILS_HTTPD_DIRLIST
if (false == httpd_is_file(pstate->ht_filename))
{
/* we assume that it's a directory */
mime = "text/html";
}
#endif
if (len >= 0)
{
snprintf(contentlen, HTTPD_MAX_CONTENTLEN,
"Content-Length: %d\r\n", len);
}
else
{
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
/* Length unknown ahead of time */
pstate->ht_keepalive = false;
#endif
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
/* Turn on chunked encoding */
snprintf(contentlen, HTTPD_MAX_CONTENTLEN,
"Transfer-Encoding: chunked\r\n");
pstate->ht_chunked = true;
#endif
}
if (status == 413)
{
/* TODO: here we "SHOULD" include a Retry-After header */
}
/* Construct the header.
*
* REVISIT: Wouldn't asprintf be a better option than a large stack
* array?
*/
hdrlen = snprintf(header, HTTPD_MAX_HEADERLEN,
"HTTP/1.0 %d %s\r\n"
#ifndef CONFIG_NETUTILS_HTTPD_SERVERHEADER_DISABLE
"Server: uIP/NuttX http://nuttx.org/\r\n"
#endif
"Connection: %s\r\n"
"Content-type: %s\r\n"
"%s"
"\r\n",
status,
status >= 400 ? "Error" : "OK",
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
pstate->ht_keepalive ? "keep-alive" : "close",
#else
"close",
#endif
mime,
contentlen
);
return send_chunk(pstate, header, hdrlen);
}
static int httpd_senderror(struct httpd_state *pstate, int status)
{
int ret;
char msg[10 + 1];
ninfo("[%d] sending error '%d'\n", pstate->ht_sockfd, status);
if (status < 400 || status >= 600)
{
status = 500;
}
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
if (status != 404)
{
pstate->ht_keepalive = false;
}
#endif
snprintf(pstate->ht_filename, sizeof pstate->ht_filename,
"%s/%d.html", CONFIG_NETUTILS_HTTPD_ERRPATH, status);
ret = httpd_openindex(pstate);
if (send_headers(pstate, status,
ret == OK ? pstate->ht_file.len : sizeof msg - 1) != OK)
{
return ERROR;
}
if (ret != OK)
{
snprintf(msg, sizeof msg, "Error %d\n", status);
ret = send_chunk(pstate, msg, sizeof msg - 1);
}
else
{
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
ret = send_chunk(pstate, pstate->ht_file.data, pstate->ht_file.len);
#else
#ifdef CONFIG_NETUTILS_HTTPD_SENDFILE
ret = httpd_sendfile_send(pstate->ht_sockfd, &pstate->ht_file);
#endif
#endif
httpd_close(&pstate->ht_file);
}
return ret;
}
static int httpd_sendfile(struct httpd_state *pstate)
{
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
char *ptr;
#endif
int ret = ERROR;
pstate->ht_sndlen = 0;
ninfo("[%d] sending file '%s'\n", pstate->ht_sockfd, pstate->ht_filename);
#ifdef CONFIG_NETUTILS_HTTPD_CGIPATH
{
httpd_cgifunction f;
f = httpd_cgi(pstate->ht_filename);
if (f != NULL)
{
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
pstate->ht_keepalive = false;
#endif
f(pstate, pstate->ht_filename);
return OK;
}
}
#endif
if (httpd_openindex(pstate) != OK)
{
nwarn("WARNING: [%d] '%s' not found\n",
pstate->ht_sockfd, pstate->ht_filename);
return httpd_senderror(pstate, 404);
}
#ifndef CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE
ptr = strchr(pstate->ht_filename, ISO_period);
if (ptr != NULL &&
strncmp(ptr, ".shtml", strlen(".shtml")) == 0)
{
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
pstate->ht_keepalive = false;
#endif
if (send_headers(pstate, 200, -1) != OK)
{
goto done;
}
ret = handle_script(pstate);
goto done;
}
#endif
#ifdef CONFIG_NETUTILS_HTTPD_DIRLIST
if (send_headers(pstate, 200, -1) != OK)
{
goto done;
}
#else
if (send_headers(pstate, pstate->ht_file.len == 0 ? 204 : 200,
pstate->ht_file.len) != OK)
{
goto done;
}
#endif
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
ret = send_chunk(pstate, pstate->ht_file.data, pstate->ht_file.len);
#else
#ifdef CONFIG_NETUTILS_HTTPD_SENDFILE
ret = httpd_sendfile_send(pstate->ht_sockfd, &pstate->ht_file);
#endif
#endif
done:
httpd_close(&pstate->ht_file);
return ret;
}
static inline int httpd_parse(struct httpd_state *pstate)
{
char *o;
enum
{
STATE_METHOD,
STATE_HEADER,
STATE_BODY
} state;
state = STATE_METHOD;
o = pstate->ht_buffer;
do
{
char *start;
char *end;
if (o == pstate->ht_buffer + sizeof pstate->ht_buffer)
{
nerr("ERROR: [%d] ht_buffer overflow\n");
return 413;
}
{
ssize_t r;
r = recv(pstate->ht_sockfd, o,
sizeof pstate->ht_buffer - (o - pstate->ht_buffer), 0);
if (r == 0)
{
nwarn("WARNING: [%d] connection lost\n", pstate->ht_sockfd);
return ERROR;
}
#if CONFIG_NETUTILS_HTTPD_TIMEOUT > 0
if (r == -1 && errno == EWOULDBLOCK)
{
nwarn("WARNING: [%d] recv timeout\n");
return 408;
}
#endif
if (r == -1)
{
nerr("ERROR: [%d] recv failed: %d\n",
pstate->ht_sockfd, errno);
return 400;
}
o += r;
}
/* Here o marks the end of the total block currently awaiting
* processing. There may be multiple lines in a block; next we deal
* with each in turn.
*/
for (start = pstate->ht_buffer;
(end = memchr(start, '\r', o - start)), end != NULL;
start = end)
{
*end = '\0';
end++;
/* Here start and end are a single line within the current block */
httpd_dumpbuffer("Incoming HTTP line", start, end - start);
if (*end != '\n')
{
nwarn("WARNING: [%d] expected CRLF\n");
return 400;
}
end++;
switch (state)
{
char *v;
case STATE_METHOD:
if (0 != strncmp(start, "GET ", 4))
{
nwarn("WARNING: [%d] method not supported\n");
return 501;
}
start += 4;
v = start + strcspn(start, " ");
if (0 != strcmp(v, " HTTP/1.0") && 0 != strcmp(v, " HTTP/1.1"))
{
nwarn("WARNING: [%d] HTTP version not supported\n");
return 505;
}
/* TODO: url decoding */
if (v - start >= sizeof pstate->ht_filename)
{
nerr("ERROR: [%d] ht_filename overflow\n");
return 414;
}
*v = '\0';
strcpy(pstate->ht_filename, start);
state = STATE_HEADER;
break;
case STATE_HEADER:
if (*start == '\0')
{
state = STATE_BODY;
break;
}
v = start + strcspn(start, ":");
if (*v != '\0')
{
*v = '\0', v++;
v += strspn(v, ": ");
}
if (*start == '\0' || *v == '\0')
{
nwarn("WARNING: [%d] header parse error\n");
return 400;
}
ninfo("[%d] Request header %s: %s\n",
pstate->ht_sockfd, start, v);
if (0 == strcasecmp(start, "Content-Length") && 0 != atoi(v))
{
nwarn("WARNING: [%d] non-zero request length\n");
return 413;
}
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
else if (0 == strcasecmp(start, "Connection") &&
0 == strcasecmp(v, "keep-alive"))
{
pstate->ht_keepalive = true;
}
#endif
break;
case STATE_BODY:
/* Not implemented */
break;
}
}
/* Shuffle down for the next block */
memmove(pstate->ht_buffer, start, o - start);
o -= (start - pstate->ht_buffer);
}
while (state != STATE_BODY);
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
if (0 == strcmp(pstate->ht_filename, "/"))
{
strncpy(pstate->ht_filename, "/" CONFIG_NETUTILS_HTTPD_INDEX,
strlen("/" CONFIG_NETUTILS_HTTPD_INDEX));
}
#endif
ninfo("[%d] Filename: %s\n", pstate->ht_sockfd, pstate->ht_filename);
return 200;
}
/****************************************************************************
* Name: httpd_handler
*
* Description:
* Each time a new connection to port 80 is made, a new thread is created
* that begins at this entry point. There should be exactly one argument
* and it should be the socket descriptor (+1).
*
****************************************************************************/
static void *httpd_handler(void *arg)
{
struct httpd_state *pstate =
(struct httpd_state *)malloc(sizeof(struct httpd_state));
int sockfd = (int)arg;
ninfo("[%d] Started\n", sockfd);
/* Verify that the state structure was successfully allocated */
if (pstate)
{
int status;
/* Re-initialize the thread state structure */
memset(pstate, 0, sizeof(struct httpd_state));
pstate->ht_sockfd = sockfd;
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
do
{
pstate->ht_keepalive = false;
#endif
/* Then handle the next httpd command */
status = httpd_parse(pstate);
if (status >= 400)
{
httpd_senderror(pstate, status);
}
else
{
httpd_sendfile(pstate);
}
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
}
while (pstate->ht_keepalive);
#endif
/* End of command processing -- Clean up and exit */
free(pstate);
}
/* Exit the task */
ninfo("[%d] Exiting\n", sockfd);
close(sockfd);
return NULL;
}
#ifdef CONFIG_NETUTILS_HTTPD_SINGLECONNECT
static void single_server(uint16_t portno, pthread_startroutine_t handler,
int stacksize)
{
struct sockaddr_in myaddr;
socklen_t addrlen;
int listensd;
int acceptsd;
#ifdef CONFIG_NET_SOLINGER
struct linger ling;
#endif
#if CONFIG_NETUTILS_HTTPD_TIMEOUT > 0
struct timeval tv;
#endif
listensd = netlib_listenon(portno);
if (listensd < 0)
{
return;
}
/* Begin serving connections */
for (; ; )
{
addrlen = sizeof(struct sockaddr_in);
acceptsd = accept(listensd, (FAR struct sockaddr *)&myaddr, &addrlen);
if (acceptsd < 0)
{
nerr("ERROR: accept failure: %d\n", errno);
break;
}
ninfo("Connection accepted -- serving sd=%d\n", acceptsd);
/* Configure to "linger" until all data is sent when the socket is closed */
#ifdef CONFIG_NET_SOLINGER
ling.l_onoff = 1;
ling.l_linger = 30; /* timeout is seconds */
if (setsockopt(acceptsd, SOL_SOCKET, SO_LINGER, &ling,
sizeof(struct linger)) < 0)
{
close(acceptsd);
nerr("ERROR: setsockopt SO_LINGER failure: %d\n", errno);
break;
}
#endif
#if CONFIG_NETUTILS_HTTPD_TIMEOUT > 0
/* Set up a receive timeout */
tv.tv_sec = CONFIG_NETUTILS_HTTPD_TIMEOUT;
tv.tv_usec = 0;
if (setsockopt(acceptsd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(struct timeval)) < 0)
{
close(acceptsd);
nerr("ERROR: setsockopt SO_RCVTIMEO failure: %d\n", errno);
break;
}
#endif
/* Handle the request. This blocks until complete. */
httpd_handler((FAR void *)acceptsd);
}
/* Close the sockets */
close(acceptsd);
close(listensd);
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: httpd_listen
*
* Description:
* This is the main processing thread for the webserver. It never returns
* unless an error occurs
*
****************************************************************************/
int httpd_listen(void)
{
/* Execute httpd_handler on each connection to port 80 */
#ifdef CONFIG_NETUTILS_HTTPD_SINGLECONNECT
single_server(HTONS(80), httpd_handler, CONFIG_NETUTILS_HTTPDSTACKSIZE);
#else
netlib_server(HTONS(80), httpd_handler, CONFIG_NETUTILS_HTTPDSTACKSIZE);
#endif
/* the server accept loop only returns on errors */
return ERROR;
}
/****************************************************************************
* Name: httpd_init
*
* Description:
* This function initializes the web server and should be called at system
* boot-up.
*
****************************************************************************/
void httpd_init(void)
{
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
httpd_fs_init();
#endif
}