Bitty HTTP

Development » Hello World

1Hello World

This example will cover a basic hello world style web site. It will go over initializing the web server, setting up your main loop, call backs from the web server for pages, and sending the requested page.

Files

main.c

We start the main.c file with includes.

#include "main.h" #include "WebServer.h" #include <stdint.h> #include <stdio.h> #include <unistd.h> // usleep()

We include the basic sockets, web server, main it's self, and some standard includes.

We also include unistd.h because we are going to use a usleep() in the main loop instead of waiting for signals or file handles. On a computer this is very waste full but in an embedded system we don't normally wait and just run full out. We use the usleep() in the example because it is a simple one line, later we will get into waiting on a socket handle using a select().

Next we add the main() function.

bool g_Quit; int main(void) { t_ElapsedTime Waiting2End; SocketsCon_InitSocketConSystem(); WS_Init(); if(!WS_Start(3000)) { printf("Failed to start web server\n"); return 1; } printf("Waiting for connections on port 3000\n"); g_Quit=false; while(!g_Quit) { WS_Tick(); usleep(1000); } printf("Quiting...\n"); /* Run the web server for a while so we can send any "finished" page */ Waiting2End=ReadElapsedClock(); while(ReadElapsedClock()-Waiting2End>3) WS_Tick(); WS_Shutdown(); SocketsCon_ShutdownSocketConSystem(); return 0; }

We have a global variable g_Quit that is set to true to exit the program.

We start by calling the SocketsCon_InitSocketConSystem() function. This will initialize the sockets. In the case of basic Berkeley sockets this would do nothing. If you are using SSL it would init the SSL library, and on an embedded system this could init the whole TCP/IP stack.

The next thing to do is init and setup the web server. We call WS_Init() and then WS_Start() with the port to listen on. This will init the main socket and start it listening for incoming connections.

The printf() has be added to tell the user what port the web server is listening on.

We then enter the main loop. In it we call the function WS_Tick(). WS_Tick() runs the web server, this must be called regularly.

Next we do the usleep() so we don't pin the processor. This maybe replace with a select() call to wait for traffic on the sockets, or removed completely.

After the main loop we have a second loop. This is used to keep the web server running to finish sending any CSS, graphics, or other things if a web page quits. This is needed because if we just quit we will close the socket and hang up on the browser before it can get any more needed data.

We just wait for 2-3 seconds running the tick function.

Finally we call SocketsCon_ShutdownSocketConSystem() to free anything that was allocated in SocketsCon_InitSocketConSystem().

Last we have a utility function:

uint32_t ReadElapsedClock(void) { time_t Current; // The current time Current=time(NULL); return (uint32_t)Current; }

This is simple function for getting the number of seconds that have passed. This does not have to be real time as it is only used for delta timing. This example just gets the current real time clock value.

Options.h

This file has defines in it that are used to customize the web server.

#define DOCVER "1.0.0.0" #define WS_OPT_MAX_CONNECTIONS 16 // The max number of connections we can handle at the same time (this will include buffers needed for each connection) #define WS_OPT_ARG_MEMORY_SIZE 100 // The memory block to use to store the cookies, get args, and post args #define WS_SECONDS_UNTIL_CONNECTION_RELEASE 10 // How many seconds to wait after a connection stops sending to us before we hang up #define WS_LINE_BUFFER_SIZE 256 // The max number of bytes we can handle a single header line can be (including the GET line). This is normally in the order of 16K - 128K (we default to a lot less)

There is a DOCVER define which is used by the web server for when to let the browser know to use it's cached version or not. This is the ETAG, and if the version number in this define is the same then the browser will use the cached version, and if not it will get a new copy.

The current string is a version number, however this could be any string that you change with every release. Something like _DATE_ _TIME_ would work as long as you rebuild the WebServer.c file.

The next set of defines are used to customize the web server. Most of these are needed because in embedded code the amount of RAM is very limited and these help you control how much memory the web server will use.

WS_OPT_MAX_CONNECTIONSThe max number of connections we can handle at the same time (this will include buffers needed for each connection)
WS_OPT_ARG_MEMORY_SIZEThe memory block to use to store the cookies, get args, and post args
WS_SECONDS_UNTIL_CONNECTION_RELEASEHow many seconds to wait after a connection stops sending to us before we hang up
WS_LINE_BUFFER_SIZEThe max number of bytes we can handle a single header line can be (including the GET line). This is normally in the order of 16K - 128K (we default to a lot less)

FileServer.c

This file has the pages we support in it. It also include the functions that the web server will call to get info or the contents of the requested web page.

At the top of the file we have the includes.

#include "../../WebServer.h" #include "FileServer.h" #include <stdbool.h> #include <string.h> #include <stdio.h>

Next we have a new structure that we use to describe a file.

struct FileInfo { const char *Filename; // With Path bool Dynamic; const char **Cookies; const char **Gets; const char **Posts; void (*WriteFile)(struct WebServer *Web); };

There is nothing saying you have to use this structure as parts of it are copied into a struct WSPageProp structure. This just makes it convenient.

The first member of this structure is Filename. This is the file name of the page. This need not be a real file on the disk. This is just strcmp()ed with the URL path. For example "http://example.com/path/file.html" the filename would be "/path/file.html".

Dynamic is if this file is dynamic or static. For a static file the ETAG will be set to DOCVER so it will not be sent over and over. For dynamic the page will not be cached by the browser and it will request a new copy every time the file is loaded (the file will not be cached).

You should set the dynamic to false for any page that never changes. Things like graphics, css files should be marked as static. Pages where the content changes depending on what is sent in or where external objects effect the page content (things like time) should be marked as dynamic.

'Cookies' is a list of the cookies you want to be able to read on the page. You must list any cookies (or GETs, or POSTs vars) you want before the page is requested so the system knowns what cookies to store. This is so we don't need to store the names of cookies in RAM. These are stored in const space. The system will not load any cookies that are not listed in this.

The cookies are a list of strings followed by a NULL entry. So for example:

const char *MyCookies[]= { "CookieA", "CookieB", NULL }

Gets is the list of GET vars you want to be able to read on the page. These work the same as Cookies.

Posts is the list of POST vars you want to be able to read on the page. These work the same as Cookies.

WriteFile is a pointer to a function that will be used to output the contents of the page. This will be called from FS_SendFile().

Next we have the prototype and the list of "files" we support.

void File_Root(struct WebServer *Web); struct FileInfo m_Files[]= { /* Filename, Dynamic, Cookies, Gets, Posts, Callback */ {"/",false,NULL,NULL,NULL,File_Root}, };

The File_Root() is a normal prototype. The m_Files array is a list of all the different "files" we support and what properties apply to it.

In this case we only have 1 "file" named "/" (what the web browser will request if you do not provide a path or filename in the URL).

bool FS_GetFileProperties(const char *Filename,struct WSPageProp *PageProp) { int r; for(r=0;r<sizeof(m_Files)/sizeof(struct FileInfo);r++) { if(strcmp(Filename,m_Files[r].Filename)==0) { PageProp->FileID=(uintptr_t)&m_Files[r]; PageProp->DynamicFile=m_Files[r].Dynamic; PageProp->Cookies=m_Files[r].Cookies; PageProp->Gets=m_Files[r].Gets; PageProp->Posts=m_Files[r].Posts; return true; } } return false; }

This function is called from the web server. It collects info about a file that the web browser is requesting. It loops though all the available "files" and if there is a match (using the strcmp()) it will fill in the 'PageProp' structure with info from the array and return true (meaning we know this page).

If this function returns false then the system will generate a 404 error status and sent that to the browser.

If you want to overrule the default 404 error page (which is VERY basic) then you always return true from this function but point the FileID to an 404 error page you provide.

The next function in the file is the FS_SendFile() function:

void FS_SendFile(struct WebServer *Web,uintptr_t FileID) { struct FileInfo *File=(struct FileInfo *)FileID; /* Not needed but I always check... */ if(File==NULL) return; File->WriteFile(Web); }

This function is called from the web server after all the headers have been processed and it is time to send the content for a page. It includes a handle to the web server context and the FileID that was filled in the page prop FileID. It is big enough to store a pointer, but is an int.

The example is using FileID as a pointer into the m_Files array, but it could have been written to have the index into m_Files.

It calls the function to send the page content from m_Files.

The last part of the file is the "root" page:

const char HelloWorldHTML[]= "<html>" "<body>" "Hello World" "</body>" "</html>"; void File_Root(struct WebServer *Web) { WS_WriteWhole(Web,HelloWorldHTML,sizeof(HelloWorldHTML)-1); }

This has the HTML in a global const string, and there is the function that will be called for this page. The function just sends the whole string using the WS_WriteWhole() function (send static data).