This section introduces the C and C++ APIs for the Barracuda Embedded Web Server.
The Barracuda Embedded Web Server uses the Virtual File System (VFS) as its URL routing mechanism. Learning how this routing mechanism works is important because the server uses the VFS to decide which object should handle a request. If no matching resource or route is found, the request typically results in a "404 Not Found" response.
The VFS concept is explained in more detail in the Lua documentation, which provides a clear introduction to how BAS and BWS route requests and organize resources. See BAS and BWS Virtual File System.
The documentation is organized as follows:
Barracuda is a C library written in ANSI C compatible code:
The library uses an object-oriented design that is compatible with C++, and the header files include C++ wrappers for the C API. Both C and C++ programmers should read ANSI C and Object-Oriented Programming to understand how the API maps between the two languages.
The reference manual is generated by the Doxygen source code documentation tool. Since Doxygen presents this object-oriented API most clearly as C++, the generated reference uses the C++ layout. C users should read the next section, ANSI C and Object-Oriented Programming, which explains how the same API is used from C.
The Barracuda Embedded Web Server is written in ANSI C, but the public API is organized as an object-oriented system. An object is a C struct instance, and the functions that operate on the object are named as methods. The method name starts with the type name, followed by an underscore and the method name. For example, the C method HttpRequest_getHeaderValue is the C equivalent of the C++ method HttpRequest::getHeaderValue.
Most objects do not expose their fields as application-level API, even though the fields are visible in the header files. Treat the struct layout as the object's private storage unless the reference documentation explicitly says otherwise. Access state through the supplied methods and macros. This keeps C and C++ code compatible and prevents application code from depending on internal fields that may change.
Every C method takes the object pointer as its first argument. The documentation often names this argument o, meaning "object". This is the same value that C++ supplies automatically as this.
C code: const char* HttpRequest_getHeaderValue(HttpRequest* o, const char* name); C++ code: const char* HttpRequest::getHeaderValue(const char* name);
The call sites therefore differ only in how the object pointer is supplied:
C code:
const char* ua = HttpRequest_getHeaderValue(request, "User-Agent");
C++ code:
const char* ua = request->getHeaderValue("User-Agent");
C has no automatic constructors or destructors. When using the C API, you must explicitly initialize every object before using it. The constructor name is the type name followed by _constructor. If an object owns resources, call the matching _destructor before the object's storage goes out of scope or is released.
HttpDir rootDir; HttpDir_constructor(&rootDir, 0, 0); /* Use rootDir here. */ HttpDir_destructor(&rootDir);
The C++ wrappers in the same headers call these C functions from C++ constructors, destructors, and inline methods. This is why the same library can be used from both C and C++ without changing the binary object layout.
Some constructors cannot report failure directly. For these objects, check the corresponding isValid method or macro before using the object. For example, connection-oriented objects such as HttpConnection, SoDispCon, and WSS expose validity checks because their constructors or setup functions may depend on system resources.
Inheritance is implemented by embedding the base object as the first member of the derived struct. This makes a pointer to the derived object have the same address as a pointer to the base object. The application can then cast between the two types in the same way that C++ implicitly converts a derived pointer to a base pointer.
typedef struct
{
HttpPage super; /* Inherits from HttpPage. Must be first. */
int visitCount;
} MyPage;
The base object must be the first member. The following layout is invalid because (HttpPage*)o would point to visitCount, not to an HttpPage object:
typedef struct
{
int visitCount;
HttpPage super; /* Wrong: the base object is not first. */
} MyNonWorkingPage;
The valid C layout is memory compatible with the corresponding C++ inheritance layout:
C code:
typedef struct
{
HttpPage super;
int visitCount;
} MyPage;
C++ code with compatible layout:
class MyPage : public HttpPage
{
int visitCount;
};
ANSI C does not have virtual functions, so Barracuda uses function pointers for behavior that must be supplied or replaced by the application. The most common examples are service callbacks. An HttpPage stores an HttpPage_Service callback, and an HttpDir stores an HttpDir_Service callback.
static void
MyPage_service(HttpPage* page,
HttpRequest* request,
HttpResponse* response)
{
MyPage* o = (MyPage*)page;
o->visitCount++;
HttpResponse_printf(response,
"Number of visits: %d",
o->visitCount);
}
void
MyPage_constructor(MyPage* o, const char* name)
{
HttpPage_constructor(&o->super, MyPage_service, name);
o->visitCount = 0;
}
The callback receives the base object pointer. If the callback belongs to a derived object, cast the base pointer back to the derived type. This is safe only because HttpPage super is the first member of MyPage.
Register the object by passing the base pointer to the API that expects the base class:
MyPage page; MyPage_constructor(&page, "status.html"); HttpDir_insertPage(&rootDir, &page.super);
HttpDir can also be customized by replacing its service function with HttpDir_setService. This pattern is useful when creating filters, wrappers, REST endpoints, or directory implementations that need to run code before delegating to the original directory behavior.
Some Barracuda types are interfaces rather than complete objects. An interface is a struct containing function pointers that define a protocol between the web server and application code. Examples include IoIntf, DirIntf, ResIntf, AuthenticatorIntf, AuthorizerIntf, UserIntf, and LoginRespIntf.
An implementation embeds the interface as its first member and initializes the function pointers in its constructor. For example, DiskIo embeds IoIntf super and can therefore be passed to code that expects an IoIntf*. The web server calls the interface functions without knowing whether the storage is backed by a disk, ZIP file, network resource, or another application-specific implementation.
typedef struct DiskIo
{
IoIntf super; /* Inherits from IoIntf. */
/* DiskIo private data follows. */
} DiskIo;
This interface style is the C equivalent of implementing an abstract base class in C++. You provide the callback functions, initialize the interface with the proper constructor, and pass the base/interface pointer to Barracuda.
super or superClass, following the existing headers.isValid when the reference documentation says the object can fail during construction or setup.The Gecko filter examples show the same object model in practice. Compare the C and C++ examples line by line to see how explicit C constructors, casts, and callback functions map to C++ constructors, implicit base-class conversion, and method calls.
An HttpDir instance is a resource collection object and a fundamental building block in the Barracuda Virtual File System. Many Barracuda classes extend HttpDir to provide specialized services. Understanding how the Virtual File System works, and how HttpDir can be extended, is important when designing the resource layout for your device.
See the HttpResMgr example for more information about using the object-oriented C API in application code.
As explained in the Getting Started Guide, a minimal Embedded Web Server can be written in C++ as follows:
int main(int argc, char* argv[])
{
ThreadMutex m; // Protects the dispatcher and server.
SoDisp dispatcher(&m); // The socket dispatcher.
// Create the Embedded Web Server object and bind it to the dispatcher.
HttpServer server(&dispatcher);
HttpServCon serverCon(&server, // Create a listen object that
&dispatcher, // listens on port 80.
80);
dispatcher.run(); // Never returns.
}
The main function above is for demonstration purposes. In an embedded system, the web server should normally run in a dedicated thread.
The same startup sequence can be written in C by declaring the objects and explicitly calling their constructors:
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, /* Use IPv4. Set TRUE for IPv6. */
NULL, /* Use any interface. */
NULL); /* No user-defined accept callback. */
SoDisp_run(&dispatcher, -1); /* Never returns. */
}
Pages are installed in the Barracuda Virtual File System. When a request arrives, the server traverses this virtual tree and looks for a matching resource. The two core VFS objects are HttpDir, which represents a directory or directory-like service point, and HttpPage, which represents a page. User code can extend these objects. For example, an HttpDir can filter requests, reroute requests, implement REST-style services, or delegate to another directory. The built-in HttpResRdr class extends HttpDir and lets the server read resources from a standard file system or another IoIntf implementation.
The Virtual File System lets you:
HttpPage and directories with HttpDir.The following C++ example adds two resource readers to the Virtual File System:
int main(int argc, char* argv[])
{
ThreadMutex m; // Protects the dispatcher and server.
SoDisp dispatcher(&m); // The socket dispatcher.
// Create the Embedded Web Server object and bind it to the dispatcher.
HttpServer server(&dispatcher);
HttpServCon serverCon(&server, // Create a listen object that
&dispatcher, // listens on port 80.
80);
DiskIo diskIo;
// Name not needed for the VFS root directory.
HttpResRdr diskRootDir(&diskIo, NULL);
server.insertRootDir(&diskRootDir);
FileZipReader fileZipReader("MyZipFile.zip");
ZipIo zipIo(&fileZipReader);
// Name not needed for the VFS root directory.
HttpResRdr zipRootDir(&zipIo, NULL);
server.insertRootDir(&zipRootDir);
dispatcher.run(); // Never returns.
}
The example installs two HttpResRdr instances. The first reader is backed by the standard file system through DiskIo. The second reader is backed by a ZIP file through ZipIo. To the client, both sources appear as one coherent file system. The file system and ZIP file may even have the same internal directory structure. If a requested file is not found in the first reader, the Virtual File System continues searching the next reader and returns the resource if it is found there.
To use IPv6, the Embedded Web Server library must be compiled with IPv6 support enabled.
The IP version is selected when you create an instance of HttpServCon. You can create two HttpServCon instances that use the same port number if one instance listens on IPv4 and the other listens on IPv6.
SharkSSL is a compact SSL/TLS stack designed specifically for the Barracuda Embedded Web Server.
SharkSSL is integrated with Barracuda, so you normally do not need to work directly with the low-level SharkSSL API. To enable secure sockets, create an instance of HttpSharkSslServCon instead of the regular HttpServCon.
// 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 standard X.509 certificate to SharkSSL's encoded certificate format. The encoded certificate is a compact binary representation of a standard ASCII-encoded X.509 certificate.
Usage: SharkParseCert <cert file> <privkey file> [-be | -le <binary file>]
Keeping the X.509 certificate parser outside the SharkSSL runtime reduces firmware size. The X.509 parser used by SharkParseCert is delivered as source code, so you can integrate it with your program if runtime certificate parsing is more important than minimizing code size.
The SharkParseCert tool can produce the SharkSSL certificate as C code or as a binary file. By default, it produces C code that can be included in, or compiled and linked with, your firmware. The examples use this approach. To produce a binary file, use the -be or -le flag, depending on whether your target platform is big endian or little endian. The binary file can be stored on a file system or directly in flash memory.
The SharkParseCert tool expects the private key file and certificate request file to be separate files. Barracuda does not include certificate creation tools. You can use the free command-line tool openssl to generate keys and certificate requests. For example:
req -new -newkey rsa:1024 -days 365 -nodes -keyout myPrivateKey.pem -out myCertRequest.pem
The certificate request can be sent to a commercial certificate authority. The result is a certificate signed by a trusted third party, which lets clients verify that they are communicating with the expected server. In a trusted environment, you can instead use an unsigned certificate or create an in-house certificate authority and self-sign the certificate. The example certificate included with Barracuda is self-signed: a root certificate was created and used to sign the certificate request, producing a certificate that can be processed by SharkParseCert.
Do not develop CSP directly on your embedded device. HTML, CSP server-side scripting, and JavaScript are high-level development tasks and should be developed and tested on a host computer.
Barracuda C/C++ Server Pages (CSP) are designed for rapid development on Unix and Windows host machines. Develop, run, and test the web interface on the host before deploying it to an embedded device. A clean interface between the Embedded Web Server code and the device firmware makes this workflow much easier. Simulate the firmware interface on the host so the web application can be tested without the target hardware.
CSP helps separate presentation from device logic. A web developer can design, implement, and unit test the user interface on a host computer while the firmware team maintains a clear device-facing API.
The Virtual File System supports duplicate directory names. This simplifies development because static HTML and JavaScript files can live in one directory on the regular file system, while compiled CSP pages can live in another VFS branch with the same name. To the client browser, the two branches appear as one directory tree. This is how the example csp and html directories can appear as one runtime directory structure.
The Embedded Web Server does not require a native file system for static content. Static content includes HTML files, images, style sheets, JavaScript files, and similar resources. These files can be stored in a ZIP file in flash memory, embedded directly in the firmware, or served from a regular file system. The ZipIo class lets the server mount ZIP files as resource collections.
Having HTML files spread out in the embedded file system turns revision control and unit testing into a nightmare.
A common setup is to use the standard file system while developing and testing on a host computer, then use a ZIP file when deploying to the embedded device. The ZIP file, file system, or both are inserted into the Virtual File System as objects. You can install multiple file-system and ZIP-backed objects in the same virtual directory structure.
The DiskIo class lets the Embedded Web Server read from the standard file system on a host machine or embedded device. A DiskIo instance is combined with a HttpResRdr instance and inserted into the Virtual File System. HttpResRdr can be installed anywhere in the VFS. It can be installed as the root directory, effectively making Barracuda behave like a conventional file-oriented web server, but embedded applications commonly install it under a specific branch or combine it with other VFS objects.
Dynamic content is content that is partly or entirely generated by the server at runtime. Dynamic HTML is typically created with CSP. The CSP compiler compiles each CSP page into an HttpPage.
HttpPage objects reside in the Virtual File System. During development, avoid mixing CSP source and JavaScript files in a way that forces you to recompile and restart the server whenever a JavaScript file changes. JavaScript development usually requires frequent edit, reload, and test cycles. Since the Virtual File System can merge duplicate directories at runtime, you can keep compiled CSP pages in one VFS branch and keep static HTML, CSS, images, and JavaScript in an HttpResRdr branch. When a JavaScript file changes in the static HTML directory, refresh the browser and the updated file is loaded without rebuilding the server.
When the examples are compiled in "debug" mode, a DiskIo instance is connected to a HttpResRdr. When the examples are compiled in "release" mode, a ZipIo instance is connected to HttpResRdr.
In debug mode, the root of DiskIo is set to the html directory. In release mode, the html directory is packaged as a ZIP file. The ZIP file can be referenced from a file system, stored directly in flash memory, or linked into the executable.
See Introduction to the Barracuda examples for a more detailed explanation of the debug and release build layouts.
Use the HttpTrace object while developing the user interface. Many Barracuda methods write diagnostic information to the trace when an operation fails. Trace output must be explicitly enabled by providing a trace callback function.
The Barracuda server uses both blocking and non-blocking sockets when sending and receiving data.
Non-blocking sockets, also called asynchronous socket communication, are typically used when sending static content to a client or receiving uploaded data, such as uploads handled by the WebDAV plugin.
Blocking sockets are used when sending dynamically generated data from resources such as CSP or LSP pages. By default, the server uses one thread to serve a resource: the thread running the socket dispatcher, SoDisp.
A server with many simultaneous users can enable the server thread pool. The HttpCmdThreadPool plugin provides a pool of worker threads so the server can execute multiple LSP and CSP pages concurrently.
The Barracuda Server must be protected by a mutex when it is accessed from multiple threads. The C and C++ startup examples above show this mutex being passed to SoDisp, the socket dispatcher.
Callback functions, such as HttpPage and HttpDir service functions, are already protected by the SoDisp mutex. Code that runs outside a Barracuda callback must acquire the mutex before calling Barracuda API methods.
You can call Barracuda API functions from other threads, but each call must be protected by the dispatcher lock. The lock functions are part of the SoDisp object.
C lock example code:
SoDisp_mutexSet(myDispatcherObj); // Call Web Server functions here. SoDisp_mutexRelease(myDispatcherObj);
C++ code can use ThreadLock to protect a scope. First get the internal ThreadMutex used by the dispatcher:
ThreadMutex& dispMutex = myDispatcherObj->getLock();
Then protect the region that calls the Web Server API:
{ ThreadLock protectMyRegion(dispMutex);
// Protected Web Server calls here.
}
See the Dispatcher Mutex for more information.
The Barracuda Server includes components for advanced data transfer. The asynchronous upload and download components used by plugins such as WebDAV are available for application code and are documented in the C/C++ reference manual. The server also supports moving an active socket connection out of the server and into a separate thread. For an overview, see the introduction to the internals.