Embedded Web Server C & C++ Introduction

The Barracuda Embedded Web Server's C and C++ documentation covers the following:

Example

As we explained in the Getting Starting Guide, a very simple Embedded Web Server can be written in C++ code as:

int main(int argc, char* argv[])
{
   ThreadMutex m; //Create the mutex protecting the dispatcher and server.
   SoDisp dispatcher(&m); //Create the socket dispatcher object.
   //Create the Embedded Web Server object and bind it to the dispatcher
   HttpServer server(&dispatcher);
   HttpServCon serverCon(&server,     // Create a listen object that
                         &dispatcher, // listen on port port 80
                         80);
   dispatcher.run(); //Never returns.
}

Please note that the main function above is for the purpose of the demo only. You should create a dedicated thread for the Web Server in an embedded system.

The above C++ code can be written in C code as:

int main(int argc, char* argv[])
{
   ThreadMutex m;
   SoDisp dispatcher;
   HttpServer server;
   HttpServCon serverCon;
   ThreadMutex_constructor(&m);
   SoDisp_constructor(&dispatcher,&m);
   HttpServer_constructor(&server, &dispatcher, 0);
   HttpServCon_constructor(
      &serverCon,
      &server,
      &dispatcher,
      80,    /* port */
      FALSE, /* set protocol to IPV6 */
      NULL,     /* Use any interface */
      NULL);     /* no user defined accept */
   SoDisp_run(&dispatcher, -1); /* never returns */
}

The above simple Web Server returns "404 page not found", since the Embedded Web Server above does not know about any web pages.

Pages are part of the Virtual File System. The Embedded Web Server traverses the file system and looks for the requested page. The Virtual File System consists of two objects, HttpDir and HttpPage, which can be extended by user code to do various tasks. For example an HttpDir object can implement a filter, re-rout requests and so on. One class that comes with Barracuda HttpResRdr extends HttpDir and makes it possible for the Embedded Web Server to search for pages on a standard file system.

The Virtual File System lets you:

We can add a Virtual File System to the C++ example above:

int main(int argc, char* argv[])
{
   ThreadMutex m; //Create the mutex protecting the dispatcher and server.
   SoDisp dispatcher(&m); //Create the socket dispatcher object.
   //Create the Embedded Web Server object and bind it to the dispatcher
   HttpServer server(&dispatcher);
   HttpServCon serverCon(&server,     // Create a listen object that
                         &dispatcher, // listen on port port 80
                         80);

   DiskIo diskIo;   
    // Name not needed for VFS root directory.
   HttpResRdr diskRootDir(&diskIo, NULL);
   server.insertRootDir(&diskRootDir);

   FileZipReader fileZipReader("MyZipFile.zip");
   ZipIo zipIo(&fileZipReader);
   //Name not needed for VFS root directory.
   HttpResRdr zipRootDir(&zipIo, NULL);
   server.insertRootDir(&zipRootDir);


   dispatcher.run(); //Never returns.
}

We have added two HTTP Resource Readers (class HttpResRdr) to the Virtual File System above. The first resource reader is interfaced to the standard file system. The other resource reader is interfaced to a ZIP file. The above construction allows clients to fetch HTML pages, gif images, etc. from the file system or from the ZIP file. The standard file system and the ZIP file will be seen as one coherent file system by the client. The file system and the ZIP file may have the same layout i.e. the same internal directory structure. If a file cannot be found in the file system, the Virtual File System searches the ZIP file and returns the file to the client if found.

Barracuda C Library

Barracuda is a C library written in ANSI C compatible code:
The library is written in an object-oriented way compatible with C++ and the header files contain special wrappers for C++ code. It is, therefore, imperative that you read the section Object Oriented Programming and C, both as a C and C++ programmer.

Reference Manual in C++

The reference manual is auto generated by the Doxygen source code documentation tool. This tool prefers C++, and since the Embedded Web Server supports both C and C++, C++ was the preferred layout for the reference documentation. C users should read the next paragraph (ANSI C and Object Oriented programming) which explains how to use the C interface.

ANSI C and Object Oriented Programming

The Embedded Web Server uses no global data. All data is presented as objects to the user. The object type is a C struct. Each object type has a number of member functions or methods associated with the object. Accessing the data in the objects is done via these member functions. You should never directly access the data in the C struct. The member functions start with the same name as the type. For example, class HttpRequest has a member function getHeaderValue. The fully qualified C function name will be HttpRequest_getHeaderValue, and the C++ name will be HttpRequest::getHeaderValue.

All C member functions take a pointer to an instance of the type as the first argument. For example, the HttpRequest_getHeaderValue takes the HttpRequest* type as the first argument. You use the "this" pointer in C++, thus you do not explicitly pass in the pointer to the class itself.

C code:
const char* HttpRequest_getHeaderValue(HttpRequest* o, const char* name);
C++ code:
const char* HttpRequest::getHeaderValue(const char* name);

We use the notation "o" as the "object pointer" in the C code which is equivalent to the automatic "this" pointer in C++.

Some of the classes in Barracuda are abstract classes which can be seen as a protocol between Embedded Web Server and the application code. You must inherit and implement the functionality of the abstract class before registering an instance of the class with the Embedded Web Server. Such a class is HttpPage.

typedef struct
{
   HttpPage super; /* As if inherited */
   int myOwnVar;
} MyPage;

The HttpPage object is actually an aggregate of the MyPage type. It is important that HttpPage is the first aggregate in the struct. For example, the following code will not work:

typedef struct
{
   int myOwnVar;
   HttpPage super; /* Ooops */
} MyNonWorkingPage;

A C++ compilers will be memory compatible with the C code if you write:

C code:
typedef struct
{
   HttpPage super; /* As if inherited */
   int myOwnVar;
} MyPage;

C++ code is memory compatible with above code:
class MyPage : public HttpPage
{
   int myOwnVar;
}; 

In C++, a constructor is used to initialize an instance of a class. We use the notation "struct name" followed by the name "_constructor"; for example, the HttpPage C initialization function name is HttpPage_constructor and the C++ version is HttpPage::HttpPage. You must explicitly call the constructor as a function when writing C code. The following code example shows how to write a constructor for the MyPage class:

MyPage_constructor(MyPage * o)
{
   /* It is safe to typecast MyPage to HttpPage since HttpPage is the first
    * aggregate in MyPage. We have to explisitly downcast MyPage to HttpPage
    * in C code. C++ would implicitly downcast the type.
    */
   HttpPage_constructor((HttpPage*)o, MyPage_service, "MyPage");
}

The second argument, MyPage_service, is the page service function. In a pure C++ environment, one would typically use virtual functions for overloading the behavior in the super class, but virtual functions cannot be used by C code, thus C function pointers must be used in both C and C++ code.

Other classes such as the HttpDir class can be used "as is" or can be extended; that is, the functionality of the HttpDir class can be extended or customized. Using function pointers does this, and one can replace the default service function in the HttpDir with a customized service function. The following is an example of such a class. Here we overload the HttpDir class and implement a filter that only allows Mozilla/Gecko compatible browsers to access the directory.

We recommend that you compare the above C and C++ example line by line.

An instance of the HttpDir class is a resource collection object and these objects are a fundamental part of the Barracuda Virtual File System. Many classes in Barracuda extend this class in order to provide various services. Understanding the Virtual File System and how HttpDir can be extended is important in order to assemble the best Virtual File System for your device.

Barracuda is not using exception handling, but at the same time a constructor cannot return a value. The Barracuda classes, therefore, contain a isValid() member function for constructors that can fail. You should always check if the object is valid before using the object. The reason for failure is printed to the HttpTrace library.

Please see the HttpResMgr example for more information on OO and C code.

Using IPv6

IPv6 uses some new functions, and therefore, you must have an Embedded Web Server library that is compiled with support for these new functions if you plan on using IPv6.

The IP version is specified with argument #3 when you create an instance of HttpServCon. You can create two instances of HttpServCon using the same port number if one instance is using IPV4 and the other is using IPv6.

Using the SharkSSL SSL/TLS Stack

The SharkSSL stack is a compact SSL/TLS stack designed specifically for the Barracuda Embedded Web Server.

Using the SharkSSL stack with the Barracuda Server is easy since SharkSSL comes ready to use without the need to read the SharkSSL API documentation. Enabling secure sockets in the Barracuda Server using SharkSSL involves creating an instance of the HttpSharkSslServCon class.

   // Create a secure listen object that listens on port 443
   HttpSharkSslServCon sslServCon(&server, &dispatcher, cert);

The third argument to the HttpSharkSslServCon constructor is a pointer to a SharkSSL encoded X.509 certificate.

SharkSSL includes a command line tool that converts a regular X.509 certificate to a SharkSSL encoded X.509 certificate. The SharkSSL encoded X.509 certificate is a binary representation of a standard ASCII encoded X.509 certificate.

Usage: SharkParseCert <cert file> <privkey file> [-be | -le <binary file>]

Separating the X.509 certificate parser from the SharkSSL implementation reduces the code size. The X.509 parser in the SharkParseCert is delivered as source code. You can integrate this code with your program if you are not concerned about the extra code size needed. As an example, the FuguHub Server can load regular X.509 certificates since the server includes the X.509 parser.

The SharkParseCert tool can either produce the SharkSSL certificate as C code or as a binary file. The default is to produce C code which can be included or compiled and linked with your firmware. The example code uses this approach. A binary file is produced by using the -be or -le flag. You must select the format for your target platform which is either big endian (-be) or little endian(-le). A binary file can either be stored on a file system or directly in flash memory.

The SharkParseCert tool requires that the private key file and the certificate request file are separated. We do not provide tools for handling certificate creation. We suggest using the free command-line tool openssl. For example, a private key and certificate request can be generated by running the following command using openssl:

req -new -newkey rsa:1024 -days 365 -nodes -keyout myPrivateKey.pem -out myCertRequest.pem

A certificate request, as we created above, can be sent to a commercial certificate authority. This will result in a certificate that has been digitally signed by a trusted third party so that clients using this signed certificate can be certain they are communicating with the legitimate entity. However, in a trusted environment it is possible to use an unsigned certificate or to create an in-house CA to self-sign the certificate. The example certificate that comes with Barracuda is a self-signed certificate. We created a root certificate and used the root certificate to sign our certificate request, thus producing a public key that can be processed by our parseCert tool.

How to Develop and Test CSP

Do not develop CSP in your embedded device. HTML, server-side scripting using CSP, and JavaScript development are high level languages and should be developed and tested on a host computer.

Barracuda C/C++ Server Pages (CSP) are specifically designed for rapid development on any Unix or Windows machine. You develop, run, and test the code on a host machine before you deploy the code into an embedded device. We suggest that you design a clean interface between the Embedded Web Server code and the firmware. The firmware interface should be simulated such that you can do a complete test of the Embedded Web Server on a host machine.

CSP makes it easier to separate content and logic. A web developer can do a complete GUI design, development, and unit testing offsite.

Duplicate Directories

The Virtual File System makes it possible to have duplicate directories, thus simplifying development since you can keep static HTML and JavaScript files in a directory on the regular file system and CSP files in a Virtual File System with the same name. The Virtual File System makes the two separate file systems look like one when the client (the web browser), accesses files from the Embedded Web Server. This is how we can have a 'csp' and a 'html' directory in the examples appear as one directory structure during runtime.

Static Content

The Embedded Web Server does not require a file system for static content. Static content includes standard HTML files, images, JavaScript files ,etc. Static content can be included in a ZIP file and stored directly in flash-memory or be embedded in the firmware. The ZipIo class makes it possible for the server to mount ZIP files.

Having HTML files spread out in the embedded file system turns revision control and unit testing into a nightmare.

Barracuda normally uses a ZIP file in an embedded device and the standard file system when developing and testing on a host computer such as a PC. The ZIP file and/or file system are inserted as objects into the Virtual File System. You can have multiple instances of "file systems" and/or ZIP files installed in the virtual directory structure.

The DiskIo class makes it possible for the Embedded Web Server to use the standard file system on your host machine or embedded device. A DiskIo instance is combined with a HttpResRdr instance and inserted into the Virtual File System. The HttpResRdr class can be installed anywhere in the Virtual File System. For example, the class can be installed as the root directory, thus effectively turning Barracuda into a standard file oriented web server. A HttpResRdr instance is normally not used as the root directory.

Dynamic Content

Dynamic content includes data such as HTML that is partly or entirely dynamically created in the server. Dynamic content is typically created using CSP. The CSP compiler compiles a CSP page and creates a HttpPage.

HttpPages can only reside in the Virtual File System. When developing code, avoid mixing CSP and JavaScript code in the same directory structure since this means that you would have to recompile and restart the Web Server if you want to change a JavaScript file. Writing JavaScript files normally requires extensive loading and testing of new versions. The Web Server can combine duplicate directories into one at run time. Thus, you can have a separate directory for you CSP where you compile and link the code with your server, and an instance of the HttpResRdr where you keep your static HTML and JavaScript code. If you change a JavaScript file that is in the HTML directory, simply press the refresh button in the browser and the new version will be loaded into the browser.

Debug Versus Release Build

A DiskIo instance is connected to a HttpResRdr when the examples are compiled in "debug" mode. A ZipIo instance is connected to a HttpResRdr when the examples are compiled in "release" mode.

The root of the DiskIo is set to the html directory when compiling examples in debug mode. The html directory is zipped into a ZIP file when compiling in release mode. The ZIP file can either be referenced from the file system, stored directly in flash memory, or be inserted into the executable.

A more detailed explanation of the debug/release build can be found in Introduction to the Barracuda examples.

Trace Library

It is recommended to use the HttpTrace object when developing the user interface. Most Barracuda methods also print to the trace if the operation fails. You must explicitly enable the trace functionality by providing a callback function.

Asynchronous Communication

The Barracuda server uses a combination of blocking and non-blocking sockets when sending and receiving data.

Non-blocking sockets, aka asynchronous socket communication, is typically used when sending static content to a client or when a client uploads data, for example, by using the WebDAV plugin.

Blocking sockets are used when sending data that is dynamically generated from resources such as CSP or LSP. The server, by default, uses one thread when serving a resource. The thread executing is the thread running the socket dispatcher (SoDisp).

A server with many simultaneous users can choose to activate the server thread pool. The HttpCmdThreadPool is a plugin to the Barracuda Embedded Web Server, and provides the Web Server with a pool of threads that enable the server to execute multiple concurrent LSP and CSP pages.

Using Multiple Threads

The Barracuda Server must be protected with a mutex when using multiple threads. The C and C++ startup code above shows how a mutex is used when creating an instance of the SoDisp class -- i.e. the socket dispatcher.

A callback function (event function) such as the service function in a HttpPage or a HttpDir is always protected by the SoDisp mutex. A non-callback function must set the mutex before calling any of the methods in the Barracuda API.

You can call all the API functions in Barracuda from other threads, but you must protect the call with a lock. The lock functions are part of the SoDisp object.

C lock example code:
SoDisp_mutexSet(myDispatcherObj);
//Call Web Server function
SoDisp_mutexRelease(myDispatcherObj);

There is an easier way to protect the Embedded Web Server calls when using C++. You must first get the internal ThreadMutex used by the dispatcher.

ThreadMutex& dispMutex = myDispatcherObj->getLock();

You can now protect a region of code as follows:

{ ThreadLock protectMyRegion(dispMutex);
  //Protected Web Server calls here
}

See the Dispatcher Mutex for more information.

Advanced Use

The Barracuda Server includes a number of components for advanced data transfer. The components used by plugins such as WebDAV for uploading and downloading in asynchronous mode are available and documented. The server also supports a number of methods for moving the active socket connection out of the server to a separate thread. These components are documented in the C/C++ reference manual. An introduction to these components can be found in the introduction to the internals.