netutils/webserver: Contributed by "Anonymous" via attachment to Bitbucket issue #135: "webserver broken when scripting support enabled - support for chunked encoding fixes it"

The webserver/httpd app is currently broken when script support is enabled (CONFIG_NETUTILS_HTTPD_SCRIPT_DISABLE). The root cause has been tracked down to the "Content-length" not being available ahead of time in this case (length of -1 passed to send_headers() ). On the other hand, the server closing the socket does not result in FIN being sent to the browser either (FIN not supported by NuttX yet).

Simple solution: Add support for HTTP Chunked Encoding to webserver/httpd (attached patch).

The attached patch is simple. It adds a configuration option to enable chunked encoding. When enabled, the implementation will auto-detect the cases where content length is not available ahead of time, and will automatically engage chunked encoding transfers.

Without this patch, the browser/client hangs forever, as it is expecting more data. With this patch, the browser displays the content.
This commit is contained in:
Gregory Nutt 2018-11-23 10:13:34 -06:00
parent b52aed8878
commit d3c36663d0
3 changed files with 158 additions and 28 deletions

View File

@ -99,6 +99,7 @@
#define HTTPD_MAX_CONTENTLEN 32
#define HTTPD_MAX_HEADERLEN 180
#define HTTPD_MAX_CHUNKEDLEN 16
/****************************************************************************
* Public types
@ -119,6 +120,9 @@ struct httpd_state
char ht_filename[HTTPD_MAX_FILENAME]; /* filename from GET command */
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
bool ht_keepalive; /* Connection: keep-alive */
#endif
#if defined(CONFIG_NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING)
bool ht_chunked; /* Server uses chunked encoding for tx */
#endif
struct httpd_fs_file ht_file; /* Fake file data to send */
int ht_sockfd; /* The socket descriptor from accept() */

View File

@ -33,6 +33,15 @@ config NETUTILS_HTTPD_SCRIPT_DISABLE
---help---
This option, if selected, will elide the %! scripting
config NETUTILS_HTTPD_ENABLE_CHUNKED_ENCODING
bool "Enable HTTP chunked encoding"
default y if !NETUTILS_HTTPD_SCRIPT_DISABLE
default n if NETUTILS_HTTPD_SCRIPT_DISABLE
---help---
This option, if selected, will cause transmissions of blocks of
initially unknown size (e.g. dynamic content creation) to be sent
using "Transfer-Encoding: chunked".
config NETUTILS_HTTPD_MAXPATH
int "Maximum size of a path"
default 64

View File

@ -167,8 +167,9 @@ static int httpd_openindex(struct httpd_state *pstate)
# if defined(CONFIG_NETUTILS_HTTPD_INDEX)
if (ret == ERROR && errno == EISDIR)
{
(void) snprintf(pstate->ht_filename + z, sizeof pstate->ht_filename - z, "/%s",
CONFIG_NETUTILS_HTTPD_INDEX);
(void) snprintf(pstate->ht_filename + z,
sizeof pstate->ht_filename - z, "/%s",
CONFIG_NETUTILS_HTTPD_INDEX);
ret = httpd_open(pstate->ht_filename, &pstate->ht_file);
}
@ -191,14 +192,86 @@ static int httpd_close(struct httpd_fs_file *file)
#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.
*
****************************************************************************/
static 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)
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);
ninfodumpbuffer(msg, (FAR const uint8_t *)buffer, nbytes);
}
#else
# define httpd_dumpbuffer(msg,buffer,nbytes)
@ -238,23 +311,35 @@ 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)
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)
if (httpd_open(pstate->ht_scriptptr + 1,
&pstate->ht_file) != OK)
{
return ERROR;
}
send(pstate->ht_sockfd, pstate->ht_file.data, pstate->ht_file.len, 0);
status = httpd_send_datachunk(pstate->ht_sockfd,
pstate->ht_file.data,
pstate->ht_file.len,
chunked_http_tx);
DEBUGASSERT(status >= 0);
(void)httpd_close(&pstate->ht_file);
}
@ -311,12 +396,23 @@ static int handle_script(struct httpd_state *pstate)
}
}
send(pstate->ht_sockfd, pstate->ht_file.data, len, 0);
status = httpd_send_datachunk(pstate->ht_sockfd,
pstate->ht_file.data,
len, chunked_http_tx);
DEBUGASSERT(status >= 0);
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);
#endif
return OK;
}
#endif
@ -346,7 +442,7 @@ static int send_headers(struct httpd_state *pstate, int status, int len)
{
const char *mime;
const char *ptr;
char contentlen[HTTPD_MAX_CONTENTLEN];
char contentlen[HTTPD_MAX_CONTENTLEN] = { 0 };
char header[HTTPD_MAX_HEADERLEN];
int hdrlen;
int i;
@ -395,12 +491,21 @@ static int send_headers(struct httpd_state *pstate, int status, int len)
(void)snprintf(contentlen, HTTPD_MAX_CONTENTLEN,
"Content-Length: %d\r\n", len);
}
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
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 */
(void)snprintf(contentlen, HTTPD_MAX_CONTENTLEN,
"Transfer-Encoding: chunked\r\n");
pstate->ht_chunked = true;
#endif
}
if (status == 413)
{
@ -430,7 +535,8 @@ static int send_headers(struct httpd_state *pstate, int status, int len)
"close",
#endif
mime,
len >= 0 ? contentlen : "");
contentlen
);
return send_chunk(pstate, header, hdrlen);
}
@ -460,7 +566,8 @@ static int httpd_senderror(struct httpd_state *pstate, int status)
ret = httpd_openindex(pstate);
if (send_headers(pstate, status, ret == OK ? pstate->ht_file.len : sizeof msg - 1) != OK)
if (send_headers(pstate, status,
ret == OK ? pstate->ht_file.len : sizeof msg - 1) != OK)
{
return ERROR;
}
@ -540,7 +647,8 @@ static int httpd_sendfile(struct httpd_state *pstate)
}
#endif
if (send_headers(pstate, pstate->ht_file.len == 0 ? 204 : 200, pstate->ht_file.len) != OK)
if (send_headers(pstate, pstate->ht_file.len == 0 ? 204 : 200,
pstate->ht_file.len) != OK)
{
goto done;
}
@ -611,8 +719,9 @@ static inline int httpd_parse(struct httpd_state *pstate)
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.
/* 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;
@ -687,7 +796,8 @@ static inline int httpd_parse(struct httpd_state *pstate)
return 400;
}
ninfo("[%d] Request header %s: %s\n", pstate->ht_sockfd, start, v);
ninfo("[%d] Request header %s: %s\n",
pstate->ht_sockfd, start, v);
if (0 == strcasecmp(start, "Content-Length") && 0 != atoi(v))
{
@ -695,7 +805,8 @@ static inline int httpd_parse(struct httpd_state *pstate)
return 413;
}
#ifndef CONFIG_NETUTILS_HTTPD_KEEPALIVE_DISABLE
else if (0 == strcasecmp(start, "Connection") && 0 == strcasecmp(v, "keep-alive"))
else if (0 == strcasecmp(start, "Connection") &&
0 == strcasecmp(v, "keep-alive"))
{
pstate->ht_keepalive = true;
}
@ -704,6 +815,7 @@ static inline int httpd_parse(struct httpd_state *pstate)
case STATE_BODY:
/* Not implemented */
break;
}
}
@ -718,7 +830,8 @@ static inline int httpd_parse(struct httpd_state *pstate)
#ifdef CONFIG_NETUTILS_HTTPD_CLASSIC
if (0 == strcmp(pstate->ht_filename, "/"))
{
strncpy(pstate->ht_filename, "/" CONFIG_NETUTILS_HTTPD_INDEX, strlen("/" CONFIG_NETUTILS_HTTPD_INDEX));
strncpy(pstate->ht_filename, "/" CONFIG_NETUTILS_HTTPD_INDEX,
strlen("/" CONFIG_NETUTILS_HTTPD_INDEX));
}
#endif
@ -739,7 +852,8 @@ static inline int httpd_parse(struct httpd_state *pstate)
static void *httpd_handler(void *arg)
{
struct httpd_state *pstate = (struct httpd_state *)malloc(sizeof(struct httpd_state));
struct httpd_state *pstate =
(struct httpd_state *)malloc(sizeof(struct httpd_state));
int sockfd = (int)arg;
ninfo("[%d] Started\n", sockfd);
@ -790,7 +904,8 @@ static void *httpd_handler(void *arg)
}
#ifdef CONFIG_NETUTILS_HTTPD_SINGLECONNECT
static void single_server(uint16_t portno, pthread_startroutine_t handler, int stacksize)
static void single_server(uint16_t portno, pthread_startroutine_t handler,
int stacksize)
{
struct sockaddr_in myaddr;
socklen_t addrlen;
@ -811,10 +926,10 @@ static void single_server(uint16_t portno, pthread_startroutine_t handler, int s
/* Begin serving connections */
for (;;)
for (; ; )
{
addrlen = sizeof(struct sockaddr_in);
acceptsd = accept(listensd, (struct sockaddr*)&myaddr, &addrlen);
acceptsd = accept(listensd, (FAR struct sockaddr *)&myaddr, &addrlen);
if (acceptsd < 0)
{
@ -829,11 +944,12 @@ static void single_server(uint16_t portno, pthread_startroutine_t handler, int s
#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)
if (setsockopt(acceptsd, SOL_SOCKET, SO_LINGER, &ling,
sizeof(struct linger)) < 0)
{
close(acceptsd);
nerr("ERROR: setsockopt SO_LINGER failure: %d\n", errno);
break;;
break;
}
#endif
@ -842,17 +958,18 @@ static void single_server(uint16_t portno, pthread_startroutine_t handler, int s
tv.tv_sec = CONFIG_NETUTILS_HTTPD_TIMEOUT;
tv.tv_usec = 0;
if (setsockopt(acceptsd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)) < 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;;
break;
}
#endif
/* Handle the request. This blocks until complete. */
(void)httpd_handler((void*)acceptsd);
(void)httpd_handler((FAR void *)acceptsd);
}
/* Close the sockets */