The Virtual File System

This guide introduces the concept of the Virtual File System (VFS) in the Barracuda App/Web Server (BAS/BWS). It explains that the server requires the user to build a VFS to serve HTTP resources. Any attempt to access resources without a properly configured VFS will result in a 404 error. The VFS allows for the organization and management of various resources such as static files, Lua Server Pages (LSP), and directories, providing a flexible and powerful structure for serving content.

BAS and BWS differ from standard web servers because URLs are not automatically mapped to a file system. Instead, the URL path component is initially processed by the VFS, which decodes it and attempts to find a matching node. If a node is found, the matched path portion is stripped, and the node processes the remaining path component. This allows for a dynamic and programmable VFS structure, constructed at runtime, as illustrated in the following diagram.

Virtual File System

Figure 1: A virtual file system with three nodes

After parsing client HTTP requests, the web server delegates the request to the VFS. The VFS then locates and processes the requested resource. If it cannot find a matching node, it returns a "404 not found" error to the HTTP client.

The VFS resources extend beyond managing physical files to include items like Lua Server Pages (LSP), which contain Lua code executed upon request. The VFS supports various directory types, including Resource Reader (depicted as Dir 2 in Figure 1), WebDAV, and others, enhancing its flexibility.

Here's a Lua example to demonstrate setting up a basic web server:

-- 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

This code initializes a resource reader and integrates it into the VFS, enabling browsers to fetch HTML pages from your file system. Detailed explanations of the resource reader will follow later in the guide.

VFS Features:

One of the VFS's unique features is that nodes can be inserted and removed at runtime. Both the Mako Server and Xedge include easy-to-use APIs for dynamically starting and stopping web applications, which basically means inserting and removing VFS directory nodes at runtime. The Xedge introductory video shows how this works.

Directory

The directory 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"
-- Enable the directory service function
dir:setfunc(myservice)
dir:insert() -- Insert "mydir" as a root directory.

In the above code, we create the Lua function "myservice". We register this function as a directory callback function just after creating a directory object by calling ba.create.dir. The directory object 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/, where "localhost" is the address to the server.

The directory service callback function "myservice", installed by calling dir:setfunc, 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 "myservice" 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
local cartdir = ba.create.dir("cart")
cartdir:setfunc(cart)
dir:insert(cartdir, true)

Download the shopping cart examle from GitHub

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.

Resource Reader

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.

Web File Server

The Web File Server enables one to easily upload and download files using a Web Browser.

Example:

local iovm = ba.openio"vm"
wfs=ba.create.wfs("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.

Note: When you click the above run button, you will get an accessed denied message due to the cross-origin safety setting. You resolve this by clicking in the browser’s address bar and pressing the enter key.

WebDAV

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 FuguHub'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:

Constructing a Virtual File System Tree

A Barracuda App/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 App/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 davm={PROPFIND=true,OPTIONS=true}
local function manage404(_ENV,path)
   if davm[request:method()] then return false end
   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.

WebDAV and 404 Page:
Line 7 and 9 are required when a WebDAV instance is installed in the virtual file system and when using Microsoft's WebDAV client.

Hidden Files

Hidden files are files that start with a dot; for example, .page.lsp. Hidden files can only be accessed by server code. A direct request from a browser yields "404 not found" in the response. Hidden files can be accessed indirectly via method response:forward and response:include, but not by method response:redirect. A directory that starts with a dot hides the directory's content from direct client access. HTML files that end with .shtml are also hidden. The extension .shtml in Barracuda means a server side include file. Files that end with .shtml are typically used when creating hidden C Server Pages (CSP).