This Getting Started guide provides Lua and C/C++ programmers an introduction to the Barracuda Embedded Web Server library.
Lua and C programmers will be introduced to the Barracuda Virtual File System which is what makes Barracuda truly unique and powerful. It is crucial to have a good understanding of the Virtual File System because it is the foundation of your web application(s).
The Barracuda Embedded Web Server provides a C/C++ API and a Lua API for application developers. With the exception of the startup code which must be in C code, many of the Barracuda Web Server API's can be accessed from the Lua scripting language.
Creating a Barracuda Web Server involves creating a number of objects. These objects are assembled into a web server specifically designed for your application.
The Barracuda Embedded Web Server Library is designed in ANSI C. This C library is designed in an object-oriented way compatible with C++. The header files contain special wrappers for C++ code. The following example uses C++. See the introduction to the C API for how to assemble a server using C code.
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 Web-Server object and bind it to the dispatcher
HttpServer server(&dispatcher);
// Create a listen object that listen on port 80
HttpServCon servCon(&server, &dispatcher);
// Create a secure listen object that listen on port 443
HttpSharkSslServCon sslServCon(&server, &dispatcher, cert);
dispatcher.run(); //Never returns.
}
The objects created above are explained in the C/C++ reference manual. The above code creates a platform independent mutex which protects the server in a multi thread enabled Barracuda configuration. The SoDisp is a platform independent socket dispatcher. This object dispatches socket events to the registered objects. For example, the servCon object and the sslServCon object are triggered when a client connects to port 80 and port 443. The HttpServCon object is the standard server listen object. This object typically listens for new client connections on port 80. The sslServCon object listens for connections on port 443 -- that is, it listens for secure HTTP connections. Argument cert is a pointer to a SSL certificate generated by using the command line tool SharkParseCert, which is explained in the C/C++ section.
We are using default arguments for the constructors above. You have many optional arguments that can be passed to the constructors above. For example, you can configure the HttpServCon object to listen to a non standard HTTP port, or you can configure the server to create N threads -- i.e., you can thread enable the server.
In the above example, two listen objects are created. The server permits you to create any number of listen objects. You can, for example, create another HttpServCon object listening for connections on port 80 using IPv6.
The above code shows how to assemble a standalone web server. An application embedding Barracuda should replace the main function with a thread dedicated to the server.
The above assembled web server will return the message "404 page not found" since no resources are so far installed in the Virtual File System. The Virtual File System must at a minimum contain one resource before a client can access the server.
Resources can be installed into the Virtual File System as Lua objects and/or as C/C++ objects. Objects created by C code can be manipulated by Lua code.
The following C code fragment is the minimum of what it takes to enable Lua for a Barracuda server. It assumes that we have already created the HttpServer and IoIntfs (IO interface).
lua_State* L; /* pointer to a Lua virtual machine */
struct BaLua_param blp = {0}; /* configuration parameters */
blp.vmio = pvmio; /* The required VM io. pointer to a IoIntf */
blp.server = pserver; /* pointer to a HttpServer */
L = balua_create(&blp); /* create the Lua state */
The above configuration parameters are the only parameters required to enable Lua. There are additional parameters that you can optionally configure. For example, a timer object is installed as follows:
BaTimer timer(&m,8000); blp.timer = &timer;
The timer is very useful as it enables the server to trigger timer events. Many web application frameworks such as PHP must rely on external cron scripts to run maintenance services. Barracuda includes a timer object that permits Lua code to run at regular intervals.
For detailed explanation refer to the Barracuda Lua C interface.
Lua Server Pages, or LSP for short, and C/C++ Server Pages , or CSP for short, are technologies that enable you to make dynamic and interactive web pages.
What are LSP and CSP?
A LSP file normally contains HTML tags, just like a HTML file. However, a LSP file can also contain Lua scripts, normally surrounded by the delimiters <?lsp and ?>. Lua server scripts are executed on the server, and can contain any valid Lua expressions, statements, functions, etc.
A CSP file is similar to a LSP page; however, a CSP file contains C or C++ code normally surrounded by the delimiters <% and %>, which are also known as CSP tags. The CSP compiler supports a number of tags specifically designed for C and C++ code.
LSP pages and CSP pages are provided with some predefined objects. The request object is used to get information from the client, and the response object is used to send output to the client from the server.
The following two examples show a LSP and a CSP web page hit counter. The examples execute code in the server and dynamically produce a HTML page:
| LSP example | CSP example |
|---|---|
<?lsp page.count = (page.count or 0) + 1 ?>
<html>
<body>
<p>
You are visitor <?lsp=page.count?>.
</p>
</body>
</html>
|
<%! int count; %>
<%!! count = 0; %>
<html>
<body>
<p>
You are visitor <%="%d" count++%>.
</p>
</body>
</html>
|
The above examples use expression tags, <?lsp= ?> for LSP and <%="format flag" %> for CSP. The result of an expression is emitted as HTML/XML.
Documentation:
Host operating system:
A CSP file is usually compiled and integrated into the server. However, a Barracuda server, running on an operating system that supports loadable modules, can dynamically load CSP modules at run time.
Firmware:
An operating system that does not support loadable modules, which includes many embedded operating systems, cannot dynamically load CSP modules. However, any operating system can dynamically load LSP applications. An LSP application can be upgraded during runtime, and a runtime upgrade does not affect the operation of the server.
The web server delegates the request to the Virtual File System after it has parsed and analyzed a client HTTP request command. The job of the Virtual File System is to locate the resource requested by the client and to delegate the HTTP request command to the requested resource. If no resource is found or if the Virtual File System is empty, a "404 not found" error message is sent to the client.
The following Lua example shows how to assemble a basic web server that can serve web pages to a client.
-- Create a "resource reader" (resrdr) using the Lua VM's I/O interface.
myapp = ba.create.resrdr(ba.openio("vm"))
myapp:insert() -- Insert as root application
The above example initializes a resource reader and adds the resource to the Virtual File System. A resource reader makes it, for example, possible for a browser to fetch HTML pages from your file system. We will explain the resource reader in more detail later.
The Barracuda Embedded Web Server is designed to run in embedded devices with non standard file systems. The server encapsulates file system operations into an I/O interface which shields the server from the underlying architecture. An I/O interface implementation can be a file system, a ZIP file, or even a database. The construction ba.openio("vm") above initializes the resource reader with the Lua VM's I/O interface which was configured by the C/C++ startup code.
In many web servers, a resource is synonymous with a file in the file system. The web-server simply locates the file in the file system and sends the file to the client. In Barracuda, a resource can basically be anything and, most importantly, a resource does not have to be a physical file.
An example of a resource in the Virtual File System is a LSP resource. A LSP resource contains Lua code that is executed when the client requests the resource. Another example of a resource is CSP. A CSP resource contains either C or C++ code that is executed when the client requests the resource.
Resources such as LSP and CSP are contained in a resource collection, aka a directory. The Barracuda Web Server includes many directory types such as the Resource Reader, the Resource Manager, WebDAV, and EventHandler. You can also create your own specialized directory type.
The following figure shows the C++ inheritance diagram for directories. The C++ names are not identical to the Lua names.
The directory function (object) is the Virtual File System's "base type", also known as HttpDir when writing C code. The directory function is not doing much unless you extend its functionality. The directory type's default implementation is to chain or link in other directories. We will focus more on linking directories when we explain how to construct a virtual file system tree.
Extending the directory function is best illustrated with an example:
local function myservice(_ENV,path)
-- _ENV is the command environment where the
-- globals print, request, and response are found.
print("<html><body><p>The relative path is: ",
path,
"<br> The absolute path is: ",
request:uri(), -- Print the URI component of the URL
"</p></body></html>")
end
dir = ba.create.dir("mydir") -- Create directory "mydir"
dir:setfunc(myservice) -- Enable the directory service function
dir:insert() -- Insert "mydir" as a root directory.
In the above code, we create the Lua function "mydir" and pass the Lua function as an argument to the directory constructor: ba.create.dir. The directory created is then inserted as a root directory in the server. We name the directory created "mydir", thus making the directory available at http://localhost/mydir/. The name "localhost" is the address to the server.
Function "mydir" is the directory service function, which is executed when a client accesses the directory. Just like an LSP page, a directory service function has a number of predefined objects in the command environment such as the request object, the response object, and function print. Function print sends data back to the browser.
The third argument to function mydir is the relative path to the virtual resource requested. For example, entering http://localhost/mydir/a/b/c in a browser displays the following:
The relative path is: a/b/c The absolute path is: /mydir/a/b/c
A shopping cart with clean URL's
You have probably noticed that many online shopping carts use dirty URLs such as:
http://example.com/cgi-bin/gen.pl?category=flowers&color=blue
The last part of the above URL contains URL encoded data: ?category=flowers&color=blue. In Barracuda, URL encoded data can be accessed as follows:
local data = request:data() -- Return URL encoded data as a Lua table print(data.category) -- prints flowers print(data.color) -- prints blue
Dirty URLs do not promote usability and are difficult to remember. Also, browsers can have a hard time caching pages containing URL encoded data.
Using clean URLs with Barracuda is easy. The above URL converted to a clean URL would look like:
http://example.com/cart/flowers/blue
Our cart implementation:
local function cart(_ENV,path)
category,color=string.match(path, "(%w+)/(%w+)")
response:forward(".cart.lsp")
end
dir = ba.create.dir("cart", cart)
dir:insert()
In the above example, we create a cart directory and make the directory available at URI /cart/. The cart directory service function extracts the category and the color from the path and then transfers the request to the LSP page ".cart.lsp". We have, for the sake of simplicity, omitted error checking.
The URL http://example.com/cart/flowers/blue uses a combination of physical and logical elements. The base path http://example.com/cart/ is a physical URL to the directory instance installed in the Virtual File System. The URL http://example.com/cart/flowers/blue is a logical URL; in other words, the path flowers/blue does not relate to a physical page.
The two variables, category and color which are extracted from the path, are declared in the temporary command environment. The temporary "command environment" is valid for the length of the request. This means that the Lua Server Page ".cart.lsp" can access these two variables.
Designing applications in Barracuda promotes the mediator-view architecture, where one page is doing preliminary processing and another page renders the response.
A default resource reader enables the server to return static content to a client. Static content includes HTML files, images, JavaScript Files, etc. A resource reader can also be upgraded to run LSP pages, thus delivering dynamic content.
-- Create a new ZIP I/O interface by loading "myzip.zip" from the VM's
-- I/O root directory.
local myzipio=ba.mkio(ba.openio("vm"), "myzip.zip")
zipapp = ba.create.resrdr("myzip",myzipio)
zipapp:lspfilter() -- Upgrade resource reader to execute LSP resources.
zipapp:insert() -- Install as a root directory.
A resource reader instance must be associated with a Barracuda file system, a.k.a. I/O interface. In the above example, we create a new I/O interface by attaching the I/O to a standard ZIP file located in the VM's root directory. The created I/O is then associated with a resource reader instance. Finally, the resource reader is inserted into the Virtual File System such that it is available at base URI /myzip/.
Let's assume the ZIP file contains two files:
index.lsp
directory/index.html
The first file can be accessed from a browser as: http://localhost/myzip/index.lsp
The second file can be accessed as: http://localhost/myzip/directory/index.html
When a browser accesses index.lsp, the LSP page is uncompressed, compiled, and executed by the LSP engine. The LSP page then sends uncompressed dynamically created data to the browser.
When a browser accesses directory/index.html, the resource reader checks if the browser accepts GZIP encoding. If it does, the internal index.html file in the ZIP file is converted, without uncompressing the data to a GZIP file and finally sent to the client. The resource reader uncompresses the file before sending it if the browser does not support GZIP encoding.
The Web File Server enables one to easily upload and download files using a Web Browser. Since the Web File Server is included in the BarracudaDrive consumer product, we suggest that you refer to the BarracudaDrive Web File Server product page for an introduction to the Web File Server.
Example:
local iovm = ba.openio"vm"
wfs=ba.create.wfm("fs", iovm)
wfs:insert()
In the above example, a Web File Server directory is created and made available at base URI /fs/ -- i.e. URL: http://address/fs/, where "address" is the domain or IP address of the server.
WebDAV is a set of extensions to the HTTP protocol that enables a web server to appear as a standard network drive. If you are new to WebDAV, take a look at the BarracudaDrive's WebDAV plugin product sheet for a brief overview of WebDAV.
WebDAV is particularly useful when developing LSP pages and when the server is not running on "localhost". By mapping a drive in Windows to the remote server or mounting the remote server on Linux, a standard text editor can be used to directly edit the LSP pages on the remote computer. Refreshing the LSP page in a web browser automatically recompiles the file after it has been changed.
local iovm = ba.openio"vm"
davd=ba.create.dav("mydav",ba.openio"vm",".LOCK")
davd:insert()
In the above example, a WebDAV directory is created and made available at base URI /mydav/ -- i.e.
URL: http://address/mydav/, where "address" is the domain or IP address to the server.
On Windows, the above can be mapped as a drive from a DOS command window as follows:
net use * http://address/mydav/
Windows should subsequently show information similar to the following:
Drive Y: is now connected to http://address/mydav/. The command completed successfully.
On Linux, davfs2 is the recommended file system driver.
On Mac, go to the Finder and press "Apple-K". Enter the WebDAV URL and click the connect button.
Tips:
The EventHandler, or EH for short, may take some time to become familiar with if you are a WEB 2.0 and AJAX developer. However, an embedded software engineer will more than likely find the philosophy of the EH easier to understand due to its foundation being similar to the interprocess communications (IPC) in real time operating systems. The EH is based on the asynchronous IPC mechanism typically found in industrial telecom software.
Unlike a normal HTTP request which terminates as soon as the server returns the response, the EH maintains a persistent, full duplex, bi-directional communication between the server and the connected client.
Each client connection is assigned a unique identification number which we call the connection id, or cid for short. When a client sends a message to the server, the server side EH implementation receives the cid. The server side implementation can store the cid, for example, in a Lua table, and at any time send asynchronous messages to the client. Sending a message to one client using a cid is referred to as a unicast message. The server can also send multicast messages to all connected clients.
Example:
In the following example, a simple chat server is created. The chat server works as follows:
When a new client connects, a multicast message is sent to all connected clients informing them that a new client signed on. A unicast welcome message is thereafter sent to the connecting client.
When a client sends a message to the server, the server relays the message to all connected clients; i.e., the server sends a multicast message.
When a client disconnects, a signing off message is sent to all remaining clients.
To keep it simple, all messages are sent as a string using the same message type. Message types are constructed as: interface:method. One EH can have multiple interfaces installed, and one interface can have multiple methods. The server side interface(s) and method(s) are used by clients when clients send asynchronous messages to the server. The client side interface(s) and method(s) are used by the server when the server sends messages to one or several clients. To keep it simple, we are using the same method name for both directions, X:message(...), where X is "ChatClient" for the client and "ChatServer" for the server.
00: local unis,multis --EH Unicast and multicast send interface
01: local users={} -- User database. user[cid] = username
02: local function myConnect(cid, session)
03: local uname = session and session:user() or nil
04: uname = uname and uname or "Unknown"
05: users[cid] = uname
06: multis:message(string.format("%s signing on.",uname))
07: unis:message(cid, string.format("Welcome %s.",uname))
08: end
09:
10: local function myDisconnect(cid)
11: multis:message(string.format("%s signing off.",users[cid]))
12: end
13:
14: local function myChatMsg(cid, msg)
15: multis:message(string.format("%s: %s",users[cid], msg))
16: end
17:
18: local connectionListener = {
19: connect = myConnect,
20: disconnect = myDisconnect
21: }
22:
23: -- Make EH directory path = /ChatServer.eh/
24: chat=ba.create.eh("ChatServer.eh", connectionListener)
25: chat:insert()
26:
27: local receive = {message = myChatMsg}
28: unis, multis = chat:setintf("ChatServer", "ChatClient", receive)
An EH directory is created on line 24 and inserted into the Virtual File System at line 25. The second required parameter to the EH constructor is a Lua table with two functions. These two functions are called by the EH when clients connect and disconnect.
Our EH connect callback function is on line 2. This function gets two parameters from the EH: the new client connection ID and the session object if the session object exists for this client. Session objects are automatically created if the user is authenticated. We have so far not explained how users are authenticated, but for now assume that the user has logged in. We store the user's name in a Lua table on line 5. The other functions can easily translate the cid to the username by using the cid as a key in the "users" table.
A server interface object is constructed on line 27 and this interface object is registered with our chat EH object on line 28. The two first parameters to the setintf method are the server interface name and the client interface name. The EH method setintf returns a unicast and multicast interface object. These two objects are used when sending messages to the client.
We use the multicast interface object to send all connected clients a message on line 6. A unicast message is sent to the new client connecting on line 7. Notice that a unicast message takes one more parameter, the cid.
Calling a method on the unicast or multicast object automatically translates to calling a method on the client side. The message is sent to the client as interfacename:methodname. For the above example, calling multis:message translates to sending a message to "ChatClient:message".
The above example shows a potential chat server. A chat client implementation is also required. EH clients can be designed in JavaScript and Java. A chat client can be designed using JavaScript and run as a JavaScript application in a browser or be designed as a Java application.
Design considerations:
The server side EventHandler was initially designed for being used by C or C++ code. Since C and C++ are strictly typed languages and JavaScript is a weakly typed language, an EventHandler Interface compiler was designed to produce stub code for JavaScript and C/C++ code. The stub compiler can also generate Java client stubs.
The following is the interface accepted by the EventHandler compiler for the above example:
client ChatClient
{
message {
string msg;
}
}
server ChatServer
{
message {
string msg;
}
}
The stub compiler makes it easier to use the EventHandler, especially for C and C++ code. Lua is much easier to use than C or C++; thus, stub code is not needed when using Lua on the server side. The Lua code in the above example is directly accessing the EventHandler and not via stub code, which is how C and C++ code uses the EventHandler. See the C/C++, Java, and JavaScript EventHandler documentation for more information on the stub compiler.
A Barracuda Embedded Web Server with an empty Virtual File System (VFS) cannot do anything. Attempting to access the server using a browser will return a "404 resource not found" error message.
The Virtual File System must contain at least one resource. A basic server can be assembled as follows:
local vmio=ba.openio("vm")
myapp = ba.create.resrdr(vmio)
myapp:insert() -- No argument = insert as a root directory.
In the above example, a resource reader is created and inserted as a root directory. The resource reader is initialized with the Virtual Machine's I/O interface.
A browser entering http://localhost/index.html loads index.html from the base of the VM's I/O interface.
I/O interfaces abstracts the underlying file-system architecture for the server. The Barracuda Embedded Web Server supports a number of I/O interface types such as DiskIo and ZipIo. A DiskIo maps to a location on a standard file system and a ZipIo maps to the internal structure of a ZIP file. The VM's I/O is initialized by the C/C++ startup code.
Directory properties:
The Lua .config script
The Lua .config script which must be located in the root of the VM's I/O interface is automatically loaded and executed when the C/C++ code creates the Lua VM.
The .config script is typically where you assemble your default Virtual File System. One thing to keep in mind is that Lua garbage collects all objects not being referenced. You must make sure that the directories you create in the .config script are referenced (global or owned by a global variable). An unreferenced directory will be removed from the VFS by the Lua garbage collector.
Example:
The following example shows a practical use of the directory priority mechanism in the VFS. Two resource readers are created: diskapp and zipapp. The diskapp is given priority 1 and the zipapp is given priority 0. The idea behind this construction is that you have designed an application and put the application into a ZIP file. The users can overload any of the files you have in the zip file by putting the files into c:/www. When the browser accesses a resource, the VFS first searches the user's c:/www directory. If the file is not found in the www directory, the ZIP file is searched. Assume that you have the style sheet style.css. The users can copy your style sheet from the ZIP file, change it, and put the file into c:\www. The browser loads the user's changed style sheet since it has a higher priority than the style sheet in the ZIP file.
We also create a Directory function and give it a very low priority. If the VFS cannot find the resource in diskapp or zipapp, the notFoundApp's directory callback function is activated. This function returns a 404 message to the user. The notFoundApp shows how to create customized "global" error handlers in the VFS.
local io=ba.mkio(ba.openio("root"), "c:/www")
diskapp = ba.create.resrdr(1,io) -- Priority 1
diskapp:insert()
io=ba.mkio(ba.openio("vm"),"myzip.zip")
zipapp = ba.create.resrdr(0,io) -- Priority 0
zipapp:insert()
local function manage404(_ENV,path)
print("<html><body><p>",
path,
" not found",
"</p></body></html>")
end
notFoundApp = ba.create.dir(-126) -- Priority -126
notFoundApp:setfunc(manage404) -- Enable 404 handler
notFoundApp:insert()
The Two I/O interfaces "vm" and "root" are initialized by the C/C++ code. The above example assumes that the "root" I/O is initialized to the root in a Windows computer. The base of an I/O interface can, for example, be set to a subdirectory by the C/C++ code.