Ethernut Home Hardware Firmware Tools Download Community
 
 
Search | Legals | Deutsch

Enhanced Web Server

The web server sample included in Nut/OS 4.4 does a fairly good job for simple applications. However, when large files are requested or many connections need to be handled, it may soon reach its limits. This document describes, how to enhance the sample code.

News:

Persistent Connections

The sample code in http/httpserv.c starts four server threads:

/*
 * Start four server threads.
 */
for (i = 1; i <= 4; i++) {
    char thname[] = "httpd0";

    thname[5] = '0' + i;
    NutThreadCreate(thname, Service, (void *) (uptr_t) i, NUT_THREAD_MAINSTACK);
}

The HTTP Server routines accept one request per connection. If, for example, a requested HTML document contains several images, a new connection will be opened for each of them. The situation becomes worse when more than one client needs to be served: The server will soon run out of listening threads. New connections will be rejected, because the Nut/OS TCP stack doesn't provide a listening backlog. Increasing the number of server threads will probably help.

/*
 * Start twelve server threads.
 */
for (i = 1; i <= 12; i++) {
    char thname[] = "httpd0";

    thname[5] = 'A' + i;
    NutThreadCreate(thname, Service, (void *) (uptr_t) i, NUT_THREAD_MAINSTACK);
}

The disadvantage is, that each thread consumes memory, specifically data RAM, which is usually a scarce resource.

One problem is, that closing a connection will not immediately release the socket for new connections. If a client closes one connection and immediately tries to connect again, it may be too fast for the socket being available again. HTTP 1.0 allows to re-use existing connections for following requests. Re-using an existing connection avoids the turn around time required to move a socket from connection to listen state. To implement this on Nut/OS, several changes are required for pro/httpd.c.

Dynamic Thread Creation

Instead of initially running a large number of server threads, we can start a new thread each time when a new connection has been established. This way, the total number of connections is limited by available memory only.

Needless to say, that a thread must be terminated after the connection has been closed. Nut/OS threads can be terminated by calling NutThreadExit(). See the following simplified server thread on how this can be implemented.

THREAD(Service, arg)
{
    TCPSOCKET *sock;
    
    /* Create a new socket. */
    sock = NutTcpCreateSocket();
    
    /* Wait for connect. */
    NutTcpAccept(sock, 80);
    
    /* Connected. Start new thread. */
    NutThreadCreate("httpd", Service, NULL, 1024);
    
    /* Process HTTP request. */
    HttpProcessRequest(sock);
    
    /* Close socket to disconnect. */
    NutTcpCloseSocket(sock);

    /* Exit thread. */
    NutThreadExit();
}                

Of course, some additional code is required to limit the number of concurrently running threads, handle errors or resource shortages, etc.

Blocked Connections

TCP clients may not always close a connection. This may be due to a software bug or simply because the line was cut before the client was able to send a FIN segment. A receiving server will normally not be able to detect the latter, but continue to wait for new data.

To avoid blocked TCP connections, Nut/OS allows to specify a socket receive timeout option. If set, the connection will be automatically closed when the timeout time elapsed without new incoming data.

Our web server should set a receive timeout immediately after the socket has been successfully created. The following code shows how to set the timeout to 500 milliseconds.

TCPSOCKET *sock;                
u_long tmo = 500;

/*
 * Create a socket.
 */
if ((sock = NutTcpCreateSocket()) == 0)
    /* Error handling. */

if (NutTcpSetSockOpt(sock, SO_RCVTIMEO, &tmo, sizeof(tmo)))
    printf("Sockopt rx timeout failed\n");

Servers which need to support many browsers concurrently in a fast local network will probably do better with shorter timeouts.

HTTP Error Code 304

Supporting error 304 responses may significantly speed up HTTP requests, because the contents will be sent only, if it has changed since the client's last request.

Again, adding this feature requires changing the core routines in pro/httpd.c. Furthermore, the system running Nut/OS must provide a valid system time, either using a battery back-upped hardware clock or querying an SNTP server during start-up (see the sample in app/logtime/).

TCP Buffer Sizes

By default, the maximum TCP segment size (MSS) is set to 536. Adding 20 bytes for the IP header and another 20 bytes for the TCP header, this will result in a transfer unit of 576, which normally guarantees unfragmented transfers. Note, that Nut/OS doesn't support IP fragmentation.

In most networks a maximum transfer unit of 1500 can be used without the risk of fragmentation. Thus, we can set the MSS to 1460.

static u_short mss = 1460;

if (NutTcpSetSockOpt(sock, TCP_MAXSEG, &mss, sizeof(mss)))
    printf("Sockopt MSS failed\n");

This will speed up the transfer of large files.

Increasing the TCP window size is another option, but will not significantly help with HTTP unless large amounts of data are sent to the server.

static u_short tcpbufsiz = 8760;

if (NutTcpSetSockOpt(sock, SO_RCVBUF, &tcpbufsiz, sizeof(tcpbufsiz)))
    printf("Sockopt rxbuf failed\n");

Of course, both options will consume additional data memory. Make sure, that

#include <netinet/tcp.h>

is included in your source file when using the code above.

Cleaning Up The Code

The initial HTTP code had been created as a quick and dirty sample, just to proof that the TCP stack is working with some web browsers. Not to mention, that the original author (me!) is anything else but an HTTP expert.

During its lifetime, more than 7 years at the time of this writing, it has been one of the most often used template for many Nut/OS applications. Many developers contributed several enhancements, but tries to avoid a major re-design in order not to break existing code. As a result, the code had become larger and slower. For example, it didn't work any longer with the ICCAVR Demo, which is limited to 64kBytes code size.

The version presented here includes several clean-ups. However, the user shall be prepared, that new bugs may have been introduced. Upgraded applications should be carefully tested.

All HTML data had been reduced to a bare minimum and the Shockwave Flash Demo had been removed. As a result, the minimal server occupies 48kBytes of program memory.

Since its very early release, the three list CGIs work unreliable and may have crashed the server. The problem was, that data had been transmitted to the browser while walking along the linked lists. TCP transmissions may block the running thread and, when it was woken up again, the linked list may have changed and the current pointer may have pointed to invalid memory areas. This had been fixed by first collecting the list items and then do the TCP output.

Implementation

httpd-enhanced-20071109.zip
This package contains the following source files:

Intentionally we do not replace existing library files, but add any updated or new module to our application. This way, the linker will skip the old library code. If you want the add the new library code to another application, simple add httpd.c, httpopt.c and rfctime.c to the list of sources in your Makefile (or your ICCAVR project).

Unfortunately, the header file httpd.h in the source tree needs to be replaced, because some of the data structures had been modified, which are also referenced by other library parts. I'd suggest to rename the existing one to nut/include/pro/httpd44.h before moving the new header to this directory. Do not forget to move rfctime.h to this directory as well. When done, rebuild the Nut/OS libraries, either by using the Configurator or by executing 'make clean' and 'make install' on the command line.

Incompatibilities

Although care had been take to keep the new library routines compatible to previous releases, some incompatibilities were unavoidable.

If some of your routines may use of the REQUEST structure, be aware, that its size has grown. Several new items had been added to its end.

If all features are enabled, the total code size of the sample server increased by roughly 10 percent. Note, however, that new modules like SNTP may already have been included in your application, in which case the additional code may be much less than 10 percent.

The type of the req_length item in the REQUEST structure had been changed from int to long.

The server will now check for additional default index files, specifically

index.html
index.htm
default.html
default.htm
index.shtml
index.xhtml
index.asp
default.asp

The routine NutHttpSendHeaderBot() had been marked deprecated. Specially if you want to make use of persistent connections, you should call NutHttpSendHeaderBottom() instead, which needs a pointer to the REQUEST structure as an additional parameter.

Also note the two new functions NutHttpGetOptionFlags() and NutHttpSetOptionFlags(), which must be called by the application in order to enable file date and time handling (HTTP error 304 support).

Last not least, the server will now respond with HTTP/1.1 instead of HTTP/1.0. As a result, clients may behave different.

If you're upgrading from the initial version of the enhaced HTTP server, please note a change with caching. While the first release checks the date of files with attached mime handlers (SSI, ASP etc.), the current version will ignore any file date, if a mime handler for that file had been registered.

Known Problems And Limitations

Persist connections do not always close immediately after having requested all files. They are instead closed by the server's socket timeout. I have no idea if this is normal or if I missed something important to let the browser close the connection.

The UROM file system doesn't provide file attributes like last modification date and time. In this case the server will use the compile time of the library file httpd.c. As long as the file is built with your application, this doesn't hurt. It may provide problems when later adding this module to the Nut/OS library.

In a previous version a memory hole had been detected, which is now fixed.



Good luck,
Harald Kipp
Castrop-Rauxel, 9th of November 2007