MicroHTTP Library: Basic Functions
This page gives you an overview of the application programming interface (API) provided by the MicroHTTP library.
Initialization
While Nut/OS tries to avoid initialization calls as far as possible, other operating systems and TCP/IP stacks may require them. Applications should therefore call
int StreamInit(void);
before using any other function of the library. The Nut/OS version of this function simply returns 0 to indicate, that everything is just fine. And Nut/OS applications will run just fine without this call. However, this may change in later versions and it is highly recommended to include this call in any webserver application, even if you do not intend to run it on another operating system than Nut/OS.
As explained previously, the old library expected the application to establish the TCP connection. This has been changed in the MicroHTTP library to maintain portability. All this TCP stack related stuff is now handled in
int StreamClientAccept(HTTP_CLIENT_HANDLER handler, const char *params);
The first argument, the handler for incoming HTTP request, can be set to the standard handler HttpdClientHandler that is provided by the library. The argument params can be used to pass special arguments to the server routine, like the TCP port number. If a NULL pointer is passed instead, defaults will be used, for example, TCP port 80.
Note, that this function will only return on fatal errors, e.g. when the system is running out of memory. Once called, it will handle all HTTP requests from connecting clients. There is just one problem: It will send all data with Content-Type: text/plain. Most webbrowsers will then display HTML source code instead of rendered content. Thus, before calling the function above, you should first call
int MediaTypeInitDefaults(void);
This will setup a minimum set of mime types. For Nut/OS, they are configurable with the Configurator. On a PC, where plenty of memory is available, all major media types will be enabled by default. At the time of this writing, these are
- text/xml
- text/plain
- image/svg+xml
- text/html
- image/png
- application/pdf
- image/jpeg
- application/x-javascript
- application/x-java-archive
- text/html
- image/gif
- text/css
- image/bmp
As a result, a minimal webserver can be implemented with three lines of code:
StreamInit(); MediaTypeInitDefaults(); StreamClientAccept(HttpdClientHandler, NULL);
This simply webserver will handle all GET requests for files. On Nut/OS, it will use the previously configured file system, which is UROM by default.
I will later explain how to implement all the fancy stuff like processing HTML forms, receiving file uploads or creating dynamic content. To be able to do this, the application must implement its own handlers, which need to deal with the TCP/IP stream of the established connection. The next chapter will give an introduction.
TCP/IP Streams
Standard HTTP is based on TCP/IP streams. The server is listening on a TCP socket, to which the client (webbrowser) connects. As soon as the connection is established, both parties send and receive data via a stream socket.
Nut/OS allows to attach stream sockets to standard C I/O-streams (FILE streams). This is a nice feature, because you can use C stdio functions like fprintf or fgets to send and receive data to and from the remote host. Actually, the old HTTP library didn't use any network specific API. Instead it expects, that the applications establishes the connection, attaches a FILE stream to the socket connection and passes a stream file pointer to the HTTP library functions.
While being convenient for the developer, it is less optimal for the target system. For example, reading a header line up to the next carriage-return/linefeed requires to call fgetc() for each character, which in turn calls a number of lower level functions. Quite a lot of overhead. As long as only a few header lines need to be processed by the embedded webserver application on a GET request, this may be acceptable. Performance problems were raised with the implementation of the POST method, specifically when posting large chunks of binary data. File uploading became unacceptable slow.
To avoid this bottleneck, the new library implements its own input functions.
int StreamReadUntilChars(HTTP_STREAM *sp, const char *delim, const char *ignore, char *buf, int siz); int StreamReadUntilString(HTTP_STREAM *sp, const char *delim, char *buf, int siz);
The first function reads data from a stream until any of the characters in the string delim appears. It further allows to skip all the characters contained in the string ignore. For example, reading a header line can be done by
got = StreamReadUntilChars(stream, "\n", "\r", buffer, avail);
Note that HTTP header lines are terminated by a carriage return follow by a linefeed character. With the call above, all linefeeds are ignored and the read stops at the carriage return, which will be replaced by string terminator. As a result, the buffer will contain the pure header line without any line ending character. The parameter avail tells the function, how many bytes to read at maximum. With header lines, this will be typically limited to the buffer size. It is somewhat more interesting, when the function is used to retrieve the data of a POST request. The application will have parsed the Content-Length header of the POST request and therefore knows, how many bytes the body will contain. To keep track of this figure, the function returns the number of bytes consumed, which is typically more than the number of bytes stored in the buffer.
The second function is similar, but reads from the stream until a specified string appears. This comes quite handy when reading multipart MIME contents, as they appear in file uploads. The following call may be used to read all data until the next boundary string, which had been given by the Content-Type header.
got = StreamReadUntilString(stream, delim, data, avail);
In the current implementation, the stream parameter of type HTTP_STREAM is no longer a FILE pointer, as it has been in the old library. Therefore, it cannot be used with C stdio functions. For reading, this is no big deal, because the new functions are much more convenient for handling HTTP streams. But it's a pity for writing to the stream, because the C standard functions were just fine and you are probably most familiar with them and don't want to learn, how to use new ones. Luckily, a set of output functions is provided, which is similar to the standard. They just have slightly different names and accept a HTTP_STREAM instead of a FILE stream pointer.
int s_write(const void *buf, size_t size, size_t count, HTTP_STREAM *sp); int s_puts(const char *str, HTTP_STREAM *sp); int s_printf(HTTP_STREAM *sp, const char *fmt, ...); int s_flush(HTTP_STREAM *sp);
One additional function, which is not part of the C stdio, is
int s_vputs(HTTP_STREAM *sp, ...);
This function writes a variable number of strings, which is quite useful with HTTP, where a mixture of constant and variable strings are often sent in one line. For example
time_t now = time(NULL); s_vputs(stream, "Date: ", Rfc1123TimeString(gmtime(&now)), " GMT\r\n", NULL);
sends a complete Date header line. While the same could have been done with
s_printf(stream, "Date: %s GMT\r\n", Rfc1123TimeString(gmtime(&now)));
the function s_vputs() is faster and much more simple, because no format string needs to be parsed. In any case, never forget to add a final NULL pointer to the list of arguments. Otherwise s_vputs() may send a lot of garbage to the server and may even crash your application.
Next Step
You should now have the basis for processing HTML forms.