Lua/LSP API

Overview

Lua is a powerful, versatile, and extensible language, embedded in the Barracuda App Server (BAS) for writing high-level applications beyond just web-based ones. With our secure socket library, Lua can be used to design advanced client/server applications.

The Barracuda server and its plugins are designed by Real Time Logic, except for the Lua scripting language, which uses the MIT license and is suitable for commercial products.

In BAS, Lua code is typically triggered by events generated by C code. The following diagram illustrates this event sequence:

In the above diagram, an event generated by C code triggers the Lua environment to start execution. This Lua code can then call back to C code through Lua bindings, facilitating interaction between Lua and C functions.

The BAS library offers numerous functions for Lua scripts. You can add your own device management functions to extend BAS and Lua. Creating Lua bindings is detailed in the Lua book, and tools can automate this by parsing C/C++ headers to generate bindings. For more information, see our online tutorial Lua to C Code.

See the section Thread Mapping and Coroutines for a more detailed explanation of how C and Lua interact.

LSP

Lua Server Pages (LSP) is a versatile tool initially developed for server-side web development. Over time, its capabilities have expanded, making it ideal for embedded systems and network programming. LSP now serves as a robust foundation for building embedded applications, designing IoT solutions, and, of course, creating web applications. If you're familiar with Node.js, you may want to explore the comparison between Node.js and Barracuda App Server to understand its unique strength.


LSP for Web:

When designing web applications, LSP provides a simple and fast way of creating dynamically created web pages. LSP is similar to CSP, except that LSP does not need to be pre-compiled.

LSP is activated by creating a Resource Reader (resrdr), which is then installed in the Virtual File System. Both the Mako Server and Xedge offer straightforward configuration options for setting up a Resource Reader-powered LSP application. However, using a Resource Reader to develop a web application is just one approach. The server also supports more advanced options, such as building custom MVC engines, like the one showcased in the Light-Dashboard Example.

With LSP, Lua script can be freely intermixed with HTML or XML. LSP tags are XML compliant <?lsp    ?>. Code between these tags is executed as Lua code. Expressions are provided by the <?lsp=    ?> tag. The result of an expression is emitted as HTML/XML.

The standard LSP tags are recognized by most HTML editing tools and hence these tools can be used for creating dynamic LSP pages.

LSP can be used as an adjunct to CSP. CSP pages can include LSP pages and vice versa.

HTTP Directories

Objects implementing directory functionality:
ba.create.dir
ba.create.resrdr
ba.create.dav
ba.create.wfs

As detailed in the Virtual File System Documentation, Lua offers tools to build directory tree objects. Using the ba.create family of functions, you can create dir-derived objects, such as WebDAV, ResRdr, and others, either in preload scripts or directly within LSP pages. The LSP directory functions are designed to closely resemble the Barracuda C/C++ APIs, ensuring consistency across platforms. All directory types share a set of common methods.

The Command (Request/Response) Environment

Lua uses the concept of environments to define variable scope. In the standalone Lua interpreter, this is represented by a global environment known as _G. LSP offers the same global environment but adds an ephemeral environment called the "request/response environment" or CMDE (command environment). Variables created in LSP pages and directory functions are stored in this temporary environment and are shared across all included or forwarded pages during the request's lifetime.

The diagram (shown to the right) illustrates the relationships between the command environment, LSP pages, directory functions, and their associated tables. The command environment is created when the first LSP page or directory function executes and persists until the request ends, making global variables accessible across all included or forwarded pages and directory functions.

Note: The diagram depicts a hypothetical request delegation, meant to illustrate object relationships. In practice, request delegation is typically not as deep as shown.

Request Response Environment

Make sure to Download the Request/Response Example from gitHub.

Directory Function
A directory function is a Lua function installed in one of dir, resrdr, or dav. A directory function implements directory functionality for a dir, but extends and/or changes the behavior of a resrdr, dav, or eh. Directory functions are typically used in advanced web applications.

See also
rsrdr:lspfilter()
ba.parselsp

Lua Server Pages
Lua Server Pages(LSP) is a Lua script page with the extension .lsp. The page is executed by a Resource Reader if the Resource Reader is LSP enabled -- e.g. if function rsrdr:lspfilter() has been called.

An LSP page has the following variables configured by the LSP engine:

Page Table
Each LSP page has its own private "page" table and variables created in the page table are maintained for the execution life of the Resource Reader. Resource Readers can be created dynamically and added to the virtual file system. Resource Readers can also be removed at any time from the virtual file system. Resource Readers that are not referenced are automatically terminated by the Lua garbage collector.

<?lsp
  -- Using page variables
  print("<pre>");
  page.count = (page.count or 0) + 1
  print("Access count = ", page.count)
  print("</pre>")
?>
This code updates the variable count each time the page is displayed. When there multiple users of the page, the number may increment by more than one each time you refresh the page. Even more interesting is if we change the code to the following:

<?lsp
  -- Using page variables
  print("<pre>");
  page.count = (page.count or 0) + 1
  print("Access count = ", page.count)
  ba.sleep(10);
  print("Access count = ", page.count)
  print("</pre>")
?>
then the two values of the access count may be different when we have multiple users.

Application Table
A Resource Reader makes it possible to optionally create an application table. The application table is provided when calling rsrdr:lspfilter(apptab). The application table is available to all LSP pages in a Resource Reader, but is kept private to other Resource Readers. The application table can store code and data common to all LSP pages in the application or store and execute code that is not part of the standard web application, such as timer driven Lua code, HTTP clients, Lua socket code, etc. As an example, the xedge and wfs example code implements an "application concept" by allowing auxiliary loaded applications to load and run common code in a ".preload" script. The .preload's environment is then made available to all LSP applications as the "app" table.

Session Object/Table
The LSP plugin also supports a session object in addition to the tables described above. The session object maintains variables that are available to the the current session. Session variables are destroyed when the session terminates.

Lua globals
Lua globals (_G) are common to all pages. Variables set in one page/session may be retrieved in another page/session.

Global variables may be referenced without the _G prefix, however, to create a Global variable the _G prefix must be applied.

<?lsp
  -- Using Lua Globals
  print("<pre>");
  _G.count = (_G.count or 0) + 1
  print("Global LSP Access count = ", _G.count)

  print(_G.stdpageheader)
  print(_G.copyright)

  -- global functions
  _G.myfunction()

  print("</pre>")
?>

Thread Mapping and Coroutines

The Lua scripting language includes coroutines, which allow for lightweight threading without preemptive context switching. Coroutines let Lua manage multiple tasks within a single native thread by pausing and resuming function execution. Since coroutine switching only happens when explicitly yielding, there’s no need for thread synchronization tools like mutexes, simplifying its use.

For more information on Lua's coroutines, see the Lua coroutine documentation and online Lua tutorials: coroutines.

The Barracuda App Server (BAS) extends the usage of coroutines in several ways, including supporting cosockets, which are non-blocking asynchronous socket operations using coroutines. In BAS, native threads are used for various purposes and are mapped onto coroutines, making each event generated by C code run in its own unique Lua stack. This setup allows multiple Lua events triggered by lower-level C code to be active simultaneously, even though Lua itself doesn't support preemptive multitasking.

This means that only one native thread will actively run when executing Lua code. However, as soon as the Lua thread yields, which often happens automatically when Lua calls out to C code via a Lua binding, the coroutine yields, but the native C thread can continue to perform C code execution while another native thread executes a pending Lua coroutine task. For example, the SQLite bindings are designed to yield Lua execution while performing SQL operations, enabling another native thread to execute another Lua coroutine concurrently. See the Lua-SQLite and LSP Considerations tutorial for details.

The following LSP page demonstrates the use of several functions that may take time on the C side and that may yield the execution:

<?lsp
response:setcontenttype("text/plain")

-- Trick to make the browser flush its internal cache and start displaying data as it trickles in
for i = 1, 15 do
   print("--------------------------------------------------------------------")
end

-- Function to print and flush the server's send buffer
local function pf(...) 
    print(...) 
    response:flush() 
end

pf("Fetching 50 quotes")
local http = require("httpm").create()
local t, err = http:json("https://zenquotes.io/api/quotes")
if t then -- If we received a Lua table (decoded JSON string)
   for key, val in ipairs(t) do
      print(val.q) -- Print quote
      pf(val.a, "\n") -- Print author and flush
      ba.sleep(200) -- Sleep for 200 milliseconds
   end
end
?>

In the above example, the following functions may yield:

When you click the above run button, you should see the data gradually appearing in the browser window instead of all at once. This is due to the yielding behavior of the functions used, which allows the output to be sent in chunks and displayed progressively.

The following diagram provides a high-level overview of the event and threading mechanism in BAS:

The above diagram approximates a typical assembled BAS server such as the Mako Server and Xedge standalone; however, in Xedge standalone, the Thread Pool and Thread Library are combined. If you assemble your own server using C code, you decide on the components to include. The only required component is the Socket Event Dispatcher. While the server can run without additional threads, using the optional Thread Pool and Thread Library enhances performance for Lua code calling time-consuming C functions, like SQLite operations, and enables blocking code, such as the HTTP client library, to run without interfering with other services.

Note: when you call blocking code such as ba.sleep(), you suspend the native thread. You can see from the above diagram that this is not a good idea when you run in the context of the Socket Event Dispatcher and the Timer thread, as this will suspend these threads from performing their operations. You can initiate lengthy operations within the context of these two threads by calling ba.thread.run().

BAS Key Components:

  1. Socket Event Dispatcher
    • Handles all network data, including non-blocking socket operations.
    • Typically runs on the main thread.
    • Powers the advanced cosockets API.
  2. Thread Pool
    • Handles HTTP requests by activating threads to manage the request/response of LSP related code.
    • Enhances performance for LSP code (request/response calls) calling time-consuming or blocking C functions, like SQLite operations.
    • High-end servers like Mako include the Thread Pool and Thread Library, while RTOS examples like Xedge standalone rely on the Thread Library.
  3. Thread Library

Introduction to Lua Authentication and Authorization

See also:
General introduction to authentication
introduction to authentication in C code

Note: See the example download section for authentication examples you may download and run on your own computer. The authentication logic can also be used with external identity providers such as OpenID Connect. See method request:login() for details.

Authentication in the Barracuda server is an optional component that can be installed in any of the directory types. You can have multiple authenticators or you can use one authenticator, as shown in the following example, that makes all pages in the server protected.

local dir=ba.create.dir()
dir:insert()
-- Enable authentication and
-- authorization for the directory.
dir:setauth(authenticator,authorizer)
-- Note: you must also reference (anchor)
-- the dir to prevent it from garbage collecting.
Objects implementing
directory functionality:
ba.create.dir
ba.create.resrdr
ba.create.dav
ba.create.wfs

In the above example, an unnamed directory is created and installed as a root directory. We install the directory in the root, thus covering all pages in the server. The last line inserts an authenticator and authorizer in the server. We will soon look into how one can create authenticator and authorizer objects. Authenticators can be installed, replaced, or removed in a running system. For example, the authenticator is removed by calling dir:setauth().

The Lua authentication and authorization logic are wrappers for the authentication and authorization classes implemented in C code. The C classes are based on an object oriented design and many of the classes are designed to be extended by the application designer. The Lua wrappers use the same concept and one can create multiple authenticator and authorizer types. The authenticator and authorizer can be implemented in Lua, but one can also use drop in authenticator and authorizer solutions implemented in C code. We are currently providing one authenticator and authorizer drop in solution that stores user database information as JSON. See ba.create.jsonuser() for more information.

Note: If you don't need to interface with an existing user database and plan to store a user database in local persistent storage, we recommend using the JSON-encoded authenticator database created with ba.create.jsonuser(). For Xedge and the Mako Server, we strongly recommend using the TPM-protected version, which securely encrypts the user database. Refer to the TPM API and ba.tpm.jsonuser() for more details.

The authenticator object is responsible for the authentication handshaking between the client and the server. The authorizer is an optional component that authorizes the action performed by authenticated users. The following example expands on the previous example and implements an authenticator, but we are not using an authorizer.

local dir=ba.create.dir()
dir:insert()
-- The username/password callback function.
local function getpassword(username)
   if username == "admin" then return "admin" end
end
-- Create the username database from our getpassword func.
local authuser=ba.create.authuser(getpassword)
-- Create authenticator by using the username database.
local authenticator=ba.create.authenticator(authuser)
-- Enable authentication for the directory.
dir:setauth(authenticator)

An authenticator is created by calling ba.create.authenticator. The authenticator requires a user database and we create a basic user database by calling ba.create.authuser. The user database object in turn requires a password lookup function. The job of the password lookup function is to look up the user and return the password if the user is found. The authenticator matches the password with the password provided by the user and accepts the user if the password matches. The above password lookup function returns the password "admin" if the username is "admin" -- i.e. the user is accepted if the username and password is "admin". The above getpassword callback function returns the password in plaintext. You can also return a hash value or use external authentication.

The above is all that is required in order to implement authentication for a directory. The authentication defaults to digest authentication since we have not configured the authenticator. The authenticator is also using an integrated HTTP response message which is displayed if the user clicks the cancel button in the browser's digest pop up window.

Authorizing Users

An authorizer object is created as follows:

local function authorizer(username, method, path)
     -- return true or false
 end
local  authorizer = ba.create.authorizer(authorizer)
dir:setauth(authenticator,authorizer)

The authorizer callback function determines user access based on the username, HTTP method, and requested relative path. It returns true if the user has access, and false or nothing if the user does not.

You have flexibility in how you authorize users. While authorizers typically use the username, method, and path, you can implement additional criteria as needed. Each LSP page can make its own access decisions, or you can override the directory service function to create a generic authorizer tailored to your application.

The Authenticator Types

The authenticator example above is using HTTP digest authentication, which is the default if the type is not specified. The authenticator type can be specified by setting field "type" in the optional "option table".

local authenticator=ba.create.authenticator(authuser,{ type="basic" })

The following authenticator types can be specified:

Encrypted Passwords

All authenticator types are designed to work with user databases where the passwords are not stored in clear text, but as a hash value. However, the hash encoded passwords must be stored using an encoding called HA1. See storing passwords as a hash value for details.

Creating a Custom Response Message Handler

A login response message handler is responsible for sending login information and login failed information to a client. The function is not called unless the client requires login information, or if the authenticator failed to authenticate the client, or if the client is denied by the authenticator, or an installed login tracker denied the request.

From the above login sequence, the response handler is called when the user requests a protected resource (1). The response handler requests the user credentials (2) by sending login information to the client. Note: The response handler should send response data but must not set the HTTP authorization header since this header is managed by the the authenticator. The response handler is also called if the username and/or password does not match (3).

The following example shows how to create a login response message handler for form based authentication:

-- Custom Response Message Handler
local function loginresponse(_ENV, authinfo)
   if authinfo.username then response:forward".loginfailed.lsp" end
   response:forward".loginform.lsp"
end
local authenticator=ba.create.authenticator(
   authuser,{type="form", response=loginresponse})

The _ENV variable is the command environment where globals such as the request and response objects are found.

The authinfo is a table with information about the login status. The username is set if the user failed to login. The username is not set when the client requests the login page. Notice that we are not using an "else" statement when forwarding the request to the login form. The "else" statement is not needed since response:forward by default does not return to sender, thus the code below ".loginfailed.lsp" will not execute unless username is not set. The pages ".loginform.lsp" and ".loginfailed.lsp" are regular LSP pages that send HTML login information and error information to the client.

Authentication Examples

Download Examples:

Creating a Form Response Message Handler

Form based authentication requires a custom response message handler. The response handler can emit the complete response page, but it is easier to create a response handler that forwards the request to a dedicated LSP page. The following example shows how to create the ".loginform.lsp" page for the form response message handler we showed previously.

<html>
  <body>
    <form method="post">
      Username: <input type="text" name="ba_username"><br>
      Password: <input type="password" name="ba_password"><br>
      <input type="submit" value="Login">
    </form>
  </body>
</html>

The above HTML form includes the two required form fields ba_username and ba_password. Pressing the submit button sends the login information in plaintext to the server. It is for this reason that one should use a secure (SSL) connection when using form based authentication. See the " force secure connection" example for more information.

Using form based authentication safely without using SSL (sform)

The form authenticator supports cryptographic hashing and implements an authentication scheme similar to digest authentication, which allows user identity to be established securely without having to send a password in plaintext over the network. The form authenticator adds two additional fields to the authinfo table that can be used by the response handler when creating the login form.

The Dynamic Navigation Menu Example implements authentication using sform.

The following examples show how to create a form response message handler and an LSP login form that supports cryptographic hashing.

Form response message handler:

We must make a modification to the the form response message handler we showed previously such that the response handler supports our new form login page.

local function loginresponse(_ENV, au)
   authinfo=au -- Set authinfo in the global command environment
   if authinfo.username then response:forward".loginfailed.lsp" end
   response:forward".loginform.lsp"
end

The code above is similar to our previous example, except for that we make the variable authinfo available in the command environment. We can use authinfo in the the new ".loginform.lsp" page by making authinfo global in the command environment.

Form login page:

The new ".loginform.lsp" page includes two JavaScript files and two new form fields. We have also changed the "submit" button to a standard html button. Changing the submit button to a standard button makes it impossible to submit the form if JavaScript is disabled, thus preventing the password from being sent in plaintext.

<html>
  <head>
    <script src="/rtl/jquery.js"></script>
    <script src="/rtl/sha1.js"></script>
  </head>
  <body>
    <form method="post">
      Username: <input type="text" name="ba_username"><br>
      Password: <input type="password" name="ba_password" autocomplete="off"><br>
      <input type="hidden" name="ba_seed" value="<?lsp= authinfo.seed ?>">
      <input type="hidden" name="ba_seedkey" value="<?lsp= authinfo.seedkey ?>">
      <input type="button" id="ba_loginbut" value="Login">
    </form>
  </body>
</html> 

Notice how we dynamically create the two form fields ba_seed and ba_seedkey by using LSP and the values in the authinfo Lua table. The global authinfo table was made available to the LSP page by the response handler above. The form does not include any JavaScript code. The JavaScript code in "sha1.js" is intelligent and auto discovers the HTML form with the fields ba_password, ba_seed, and the button with id="ba_loginbut". These fields are required by the JavaScript code, which automatically calculates a SHA-1 hash from the password and the seed value. The seedkey is required by the authenticator in the server when the form is submitted. The user will not be able to login if any of these fields are missing.

The two JavaScript files are found in the Barracuda SDK /WebResources/ directory. The two pre-compiled servers that come with the SDK includes these two files in the integrated ZIP file and makes them available in the /rtl/ directory. The JavaScript file sha1.js requires jquery.js

Many browsers give the user the option of saving the username and password in the browser. The JavaScript code run when pressing the login button replaces the password entered by the user by the SHA1 hash before submitting the page, thus making it impossible for the user to save the password. This feature increases security but degrades the user experience. If you want the user to be able to save the password, make the following changes to the HTML form:

    <form method="post">
      Username: <input type="text" name="ba_username"><br>
      Password: <input type="password" id="ba_password2"><br>
      <input type="hidden" name="ba_password">
      <input type="hidden" name="ba_seed" value="<?lsp=authinfo.seed?>">
      <input type="hidden" name="ba_seedkey" value="<?lsp=authinfo.seedkey?>">
      <input type="button" id="ba_loginbut" value="Login">
    </form>

The ba_password is now changed to a hidden variable and the password field is using the id="ba_password2". Notice that we are using an id and not a name attribute for ba_bassword2. Form fields without a name are not submitting to the server, which is what we want -- i.e. we want to prevent the password from being sent in plaintext. The JavaScript code in sha1.js is designed to look for this combination. The hash is created from ba_password2 + seed and inserted into ba_password. The above construction allows the user to save the password in some browsers.

Sform Authenticator Limitations:
  1. The authentication will not work if the user has JavaScript disabled. The file sha1.js includes a JavaScript implementation of SHA1 and code to extract and manipulate the HTML form fields. The clear text password is replaced by the hash of the password + seed.
  2. The authentication will not work if using an external authenticator, where the password is kept as a secret by the authenticator.
  3. The following field must be added to the form when using a user database with encrypted passwords.

    <input type="hidden" name="ba_realm" value="Barracuda Server">
    

    Realm is the authenticator's realm value. The default realm value is Barracuda Server.

    You must also include spark-md5.min.js, which is used for calculating HA1.

    The following shows the complete example:

    <script src="/rtl/sha1.js"></script>
    <script src="/rtl/spark-md5.min.js"></script>
    <form method="post">
      Username: <input type="text" name="ba_username"><br>
      Password: <input type="password" name="ba_password" autocomplete="off"><br>
      <input type="hidden" name="ba_realm" value="Barracuda Server">
      <input type="hidden" name="ba_seed" value="<?lsp= authinfo.seed ?>">
      <input type="hidden" name="ba_seedkey" value="<?lsp= authinfo.seedkey ?>">
      <input type="button" id="ba_loginbut" value="Login">
    </form>
    

Force Secure Connection

Basic and form based authentication is unsafe unless the login information is sent over a secure connection. The following example makes sure the client is using a secure connection:

-- Create a secure URL from  URI (path)
-- cmd is the request and response object.
-- The username/password callback function.
-- Notice how we use the optional _ENV command environment.
local function getpassword(username, upasswd, _ENV)
   if not request:issecure() then
      -- Deny login: send redirect request
      response:redirect2tls()
      -- redirect2tls does not return to caller.
   end
   if username == "admin" then return "admin" end
end
-- Create the username database from our getpassword func.
local authuser=ba.create.authuser(getpassword)

-- The login response message handler
local function loginresponse(_ENV, authinfo)
   if not request:issecure() then
      -- Remove Authorization header set by basic or digest authenticator
      response:setheader("Authorization",nil)
      -- redirect2tls does not return
      response:redirect2tls()
   end
   -- The connection is secure if we get this far.
   if authinfo.username then response:forward".loginfailed.lsp" end
   response:forward".loginform.lsp"
end

local authenticator=ba.create.authenticator(authuser,{response=loginresponse})
dir=ba.create.dir()
dir:insert()
dir:setauth(authenticator)

The login response message handler checks if the URL is secure, and if it is not, sends a redirect request to the client. Removing the "Authorization" header set by the authenticator is necessary if using Digest or Basic authorization since we do not want the client to send the credentials before using a secure connection.

A client normally starts by requesting a secure resource without providing the user's credentials (1), but a non browser client such as a HTTP client library may directly send the credentials (3). The password is sent in plaintext and an eavesdropper could intercept the password. We cannot prevent this on the server, but we can change the getpassword() function to ignore the login request and force the connection to a secure connection. The response is not normally committed in a getpassword() function, but the authenticators are designed such that they assume the getpassword() function sent a "denied" request if the response is committed. Method response:sendredirect commits the response.

Note: The modified getpassword() function with the secure redirect is not needed if using the shtml authenticator since the secure form authenticator denies all non secure requests and delegates the request directly to the response message handler.

External Lua Links

Barracuda Lua APIs

The BAS APIs, accessible globally via "ba," are divided into two sections: the Standard API and the Auxiliary API. While most BAS builds include the Auxiliary API, the BAS source code library can be compiled without it. The Auxiliary API encompass all client-side APIs, such as HTTP, along with crypto and socket APIs.

Additionally, each LSP page is equipped with predefined variables and objects, including: request, response, cookie, session and page.

ba

A library that provides a number of BAS utility and I/O functions.


ba.aesdecode(key, string)
Decodes and decrypts a string encrypted and encoded with function ba.aesencode. The key must be the same as the key you used when encoding the string.
ba.aesencode(key,string)
Encrypts a string using AES encryption and returns the encrypted string as a B64 encoded string.

key: The key can be any string, but is preferably created with function ba.aeskey()
string: The string to be encrypted and converted to B64 encoding.

This function is typically used as a substitute for creating a session object. The LSP page can instead store the session state as a hidden variable in the dynamically created page returned to the browser. The following example illustrates how to encode and decode state information in a hidden variable.

Encoding:
-- Data to be encoded and stored in hidden variable
local stateInfo={x=10,y="Top Secret"}
local data=ba.aesdecode(app.key, ba.json.encode(stateInfo))
Decoding:
-- Data received via hidden variable from client
local stateInfo = ba.json.decode(
   ba.aesdecode(app.key, request:data"MyHiddenVariable"))
ba.aeskey([len]])
Create a 16 or 32 byte long key that can be used by ba.aesencode() and ba.aesdecode(). The key is typically created at startup in the .preload script and stored in the app table.

len: The key length must be 16 (AES 128 bit) or 32 (AES 256 bit). The default length is set to 16.

ba.b64decode(string)
decodes a Base64 string, or a Base64url string.
ba.b64encode(string)
encodes a string using Base64 encoding.
ba.b64urlencode(string)
encodes a string using Base64url encoding.
ba.urldecode(string)
Decodes a URL encoded string.
ba.urlencode(string)
URL encodes a string.
ba.clock()
returns the number of milliseconds since system start.
ba.cmpaddr(addr1, addr2)
Compares two IP addresses. Returns true if the two IP addresses are equal.
print(ba.cmpaddr("127.0.0.1", "127.0.0.1")) -- Prints true
print(ba.cmpaddr("::1", "0:0:0:0:0:0:0:1"))  -- Prints true
print(ba.cmpaddr("68.4.198.129","::ffff:68.4.198.129"))  -- Prints true
print(ba.cmpaddr("68.4.198.129","::ffff:68.4.198.128"))  -- Prints false
ba.deflate(data [,rfc1950])
Deflates (compresses) a string or a table of strings and returns the compressed data.

data: The data to be compressed, which must be a string or a table of strings. The function works similarly to the Lua function table.concat when this argument is a table. The table is concatenated and compressed.

rfc1950: Optional Boolean argument, which defaults to false. By default, data is compressed using RFC1951, which means compression is applied without adding the ZLIB deflate header. While the proper method according to specification is RFC1950 (which includes the ZLIB header), modern browsers accept both RFC1950 and RFC1951 without issues. Previously, Internet Explorer had difficulty handling the RFC1950 specification, but with IE now obsolete, this concern is no longer relevant.

See also: response:setresponse().

ba.create

A table of functions for creating Barracuda objects:

ba.create.authenticator(authuser[,options])
Creates an authenticator object for a directory. The returned object can be used as the target for dir:setauth(). See the introduction to authentication for more information on how to use this object.
authuser=object -- An object created with ba.create.authuser or ba.create.jsonuser.
options=table -- A table with the following optional fields:
type=string -- "auth" | "digest" | "basic" | "form" | "sform" | "dav". Default is "digest". See the authenticator example for details.
realm=string -- The name of the authentication realm (used by basic and digest authenticators). The realm's default name is "Barracuda Server".
tracker=boolean -- If set to true, the client login tracker will be used if it is available. Defaults to false.
response = function(_ENV, authinfo) -- The optional Response Message Handler.
_ENV
The command environment.
authinfo=table
A table with the following fields:
string type
type="form"|"basic"|"digest"
string username
Name of the user attempting to authenticate.
string password
The password entered by the user.
number maxusers
The maxusers variable returned by the authenticate callback function. This variable is negated when the maximum number of logins is reached.
boolean recycle
true|false.
number inactive
The maximum session inactivity time in seconds.
number loginattempts
The number of login attempts.
boolean denied
denied=true|false. Set if the user is denied access by the login tracker -- i.e. if the user is banned.
string seed
Set if using the form based authenticator. The seed can be used for cryptographic hashing. See Using form based authentication safely without using SSL for more information.
string seedkey
Set if using the form based authenticator. Field seedkey is an encrypted version of the seed.
Fields username,password,maxusers,recycle,and inactive are only set if the user failed to login or is denied access by the tracker. Fields loginattempts and denied are only set if a user tracker is active.
ba.create.authorizer(function)
This function creates an authorizer object for a directory, which can be used as the target for dir:setauth(). The authorizer function is the sole parameter, and the callback function determines whether a user is authorized to access a resource. The function returns true if the user is authorized, and false or nothing if they are not.
function(username, method, relpath, session)
user
The login name of the user attempting to access the resource.
method
The HTTP method being used, such as "GET" or "POST". These are the same names returned by request:method()
relpath
The path name relative to the owning directory object, indicating the resource the user is trying to access.
session
The session object associated with the user. You can use session attributes in conjunction with the other parameters to implement fine-grained authorization.
returns: true|false
-- isadmin is set in the login code
local az=ba.create.authorizer(function(u,m,r,s) return s.isadmin end)
ba.create.authuser(function)
Creates an authenticator user database object. The returned object can be used as the target for ba.create.authenticator() See the Introduction to Lua Authentication for example code.
function(username [, upasswd [, _ENV ]])
username -- The username sent from the client.
upasswd -- The password sent from the client is provided when using Basic and Form Authentication. This value is nil when a Digest Authenticator calls the callback function.
_ENV -- The command environment is normally not needed, but can be used in special cases.
The function returns the following parameters in order: password [, maxusers [, recycle, inactive]].
password=string|table|boolean
The default is to return a string (the plaintext password), or nil if user is not found.
  • String: return the password if the user is found in the database [and if the user is accepted], otherwise return nil or nothing.
  • Table: return an HA1 hash.
  • Boolean: return true (authenticated) or false (not authenticated). Boolean return value is compatible with the Basic and Form Authenticator. See using an external authenticator for more information.
maxusers=number (default: 3)
The maximum number of concurrent login sessions the user is allowed to create. Setting maxusers to 0 will prevent the user from logging in. Note, the Response Message Handler must be designed to respond with a special message if maxusers is zero.
recycle=boolean (default: false)
Enable recycling of older sessions if the maxusers ceiling is hit. Note, the Response Message Handler must be designed to respond with a special message if this variable (is not returned or is false) and maxusers is hit.
inactive=number (default: session time)
The maximum inactive time interval in seconds. If this time elapses without any user activity, the user will be logged out. The default session time is used if no value is returned.
ba.create.jsonuser()
Creates an authenticator user database object that is using JSON as the user database format. The returned object can be used as the target for ba.create.authenticator(). The tutorial How to Create a WebDAV Server explains how to use "jsonuser" in detail. For Xedge or the Mako Server, we strongly recommend using the TPM-protected version, which securely encrypts the user database. Refer to the TPM API and ba.tpm.jsonuser() for more details.
The created object has the following additional authenticator methods:
juser:set(userdb)
userdb = table or string -- A Lua table or a JSON encoded string. The Lua table must have the following fields:
 {username1=user1, username2=user2} -- etc. 
Where user1 and user2 are tables with the following fields:
{
 pwd='password',
 roles={'role1','rol2','etc'},
 maxUsers=number,
 recycle=boolean,
 inactive=number
}

The pwd field is either a string storing a password in plaintext or a Lua table encapsulating an HA1 hash.

The fields maxUsers, recycle, and inactive are optional. The roles table is required, but you can set it to an empty table if you do not plan on using authorization. The following basic example sets up a database with two users and no authorizer.

local ju=ba.create.jsonuser()
ju:set{username1={pwd='pwd1',roles={}},username2={pwd='pwd2',roles={}}}
dir:setauth(ba.create.authenticator(ju))
Returns:

The function returns 'true' on success and 'nil,error' if the userdb parameter is incorrect.

juser:authorizer()
Creates and returns a jauthorizer (JSON authorizer) object. Each JSON user database can be associated with an unlimited number of JSON authorizers. You can, for example, use the same authenticator on multiple directories, but create a unique JSON authorizers for each directory.

The combined JSON authenticator user database and the JSON authorizer provides a ready to use user database and constraint management using JSON as the database format. The JSON user database and the JSON constraints can be saved to a file system.

Examples:
  • The following example sets up an authenticator and authorizer that simulates the same authentication and authorization logic as in the security C code example. The tutorial How to Create a WebDAV Server explains in detail how this example works.

    -- Setup the users, Defaults: maxusers=3,recycle=false,inactive=false
    local userGuest={pwd='guest',roles={'guest'},maxusers=100}
    local userKids={pwd='kids',roles={'guest','family'}}
    local userDad={pwd='dad',roles={'guest','family','dad'},recycle=true,inactive=60*60}
    local userMom={pwd='mom',roles={'guest','family','mom'},recycle=true,inactive=60*60}
    
    -- Create a JSON user database object and install the user database
    local authuser=ba.create.jsonuser()
    authuser:set{guest=userGuest,kids=userKids,dad=userDad,mom=userMom}
    
    -- Create a digest authenticator (using default values).
    local authenticator=ba.create.authenticator(authuser)
    
    -- Setup the constraints
    local constr1={urls={'/*'},methods={'GET'},roles={'guest'}}
    local constr2={urls={'/*','/family/*'},methods={'POST'},roles={'mom','dad'}}
    local constr3={urls={'/family/*'},methods={'GET'},roles={'family'}}
    local constr4={urls={'/family/mom/*','/family/dad/*'},methods={'GET'},roles={'mom','dad'}}
    local constr5={urls={'/family/dad/*'},methods={'POST'},roles={'dad'}}
    local constr6={urls={'/family/mom/*'},methods={'POST'},roles={'mom'}}
    local constr7={urls={'/family/kids/*'},methods={'GET','POST'},roles={'family'}}
    -- Note, the constraint names are not used by the authorizer.
    local constraints={Guest=constr1,FamilyPost=constr2,FamilyGet=constr3,
       Parents=constr4,Dad=constr5,Mom=constr6,Kids=constr7}
    
    -- Create the authorizer and install the constraints.
    local authorizer=authuser:authorizer()
    authorizer:set(constraints)
    
    -- Create a directory and set the authenticator and authorizer
    local dir=ba.create.dir()
    dir:setauth(authenticator,authorizer)
    -- Note: you must also reference (anchor) the dir so it is not garbage collected.
    
  • The Dashboard App Tutorial shows how to use JSON authenticator and authorizer. See end of the file source/.lua/cms.lua for implementation details.
ba.create.dav([,name] [,priority], io [,lockdir] [,maxuploads, maxlocks])
Creates a WebDAV directory. The first two parameters are the same as ba.create.dir(). Note: A WebDAV instance requires a specialized 404 handler.
io - an I/O interface created by ba.mkio() or ba.openio()
lockDir - is the name of the lock directory. The lock directory, which should use a ".name" on Unix, is where the WebDAV server keeps information about client locks. a WebDAV client can use the HTTP method LOCK to lock a resource.
Some tools such as Microsoft Word use locking to prevent a second user from editing the same document. If the lockdir parameter is nil or if the directory does not exist and the WebDAV server is unable to create this directory, the WebDAV server will operate in read only mode. It is recommended to create this directory before creating a WebDAV object.
maxuploads - is the limit value for concurrent uploads. The WebDAV server uploads data to the server using asynchronous sockets and does not need a dedicated thread for the upload. The WebDAV server needs to dynamically allocate resources when starting a new upload. You can set a maximum number of concurrent uploads to limit memory use. Maxuploads is more useful for an embedded device with limited memory. Downloads are also using asynchronous sockets, but it is not possible to set a limit on the number of downloads.
maxlocks - is the maximum number of WebDAV locks. Note that not all tools use locking, and not all WebDAV clients support locking.
The created object has the following additional directory method:
dav:io()
returns the objects I/O interface.
ba.create.dir([name] [,priority])
Creates a virtual directory node.
Parameters:
  • name - the name of the directory; nil or nothing indicates an unnamed directory. An unnamed directory can be installed as a root directory or as a chained child directory. A root directory and chained child directory act as if they are part of the parent -- i.e. a root directory is part of the top server node and a chained child behaves as an extension to the parent. Named directories are only activated if the name matches the "top" name in the path.
  • priority - optional priority of the directory. Default is zero. Negative gives lower priority and positive gives higher priority.

The HTTP directory object returned by this function supports a variable set of functions.

local function testfunc(_ENV,path)
  response:setdefaultheaders()
  response:write[[
    <!-- example usage -->
    <html>
    <head>
    <title>test</title>
    <meta http-equiv="Content-Type"
       content="text/html; charset=utf-8">
    </head>
    <body>
    <pre>HELLO WORLD!
    ]]
  print("relative path=",path)
  print("absolute path=",request:uri())
  response:write[[
    </body>
    </html>]]
  return true -- say we are done
end
testdir = ba.create.dir("test")
testdir:setfunc(testfunc)
testdir:insert() 
Run the example one time and navigate to tutorial.realtimelogic.com/test/xyz
  Output:

HELLO WORLD!
relative path= xyz
absolute path= /test/xyz

ba.create.domainresrdr(domainname [,priority] ,io [,404-page])
Creates a Barracuda Domain Resource Reader. A domainresrdr is similar to a standard Resource Reader, except for that the service function only searches for the resource if the domain name matches the client's host header. A domainresrdr acts as a domain filter for a resrdr. The additional directory functions available to a Resource Reader are also available to a domainresrdr.
domainname - represents a domain name.
priority - optional priority of the directory.
io - an I/O interface created by ba.mkio() or ba.openio() a reference to the I/O interface exists for as long as the created object exists.
404page - is the name of CSP, LSP or HTML page to be displayed when the URL is not found. The domainresrdr requests the virtual file system to continue searching if the requested page is not found and a 404 page is not provided.
local domains={
   "my-domain-a.com",
   "my-domain-b.com",
   "my-domain-c.com"
}
local hio=ba.openio"home" -- Mako's home directory
dirs={} -- Reference all domain directory objects: prevent GC of directories.
for _,domain in pairs(domains) do
   -- Create a sub-dir for each domain
   assert(hio:stat(domain) or hio:mkdir(domain))
   local dir=ba.create.domainresrdr(domain, ba.mkio(hio, domain))
   dir:insert() -- as root dir
   dirs[domain] = dir -- Reference
end
ba.create.wfs([,name] [,priority], io [,lockdir] [,maxuploads, maxlocks])

Creates a Web File Server (WFS) directory. A Web File Server is a Lua implemented directory function that combines a WebDAV object and a Web File Manager. The Web File Server makes it possible for the same directory to serve requests from browser clients and WebDAV clients. The Web File Server directory function makes the assumption that browser clients send a "User-Agent" string containing the word "Mozilla" and WebDAV clients do not. Browser requests are delegated to the Web File Manager directory function, and WebDAV requests are delegated to the WebDAV directory function.

Note: A WebDAV instance requires a specialized 404 handler.

The WFS can be used as is or be customized for various purposes. See the Web File Manager Readme File for an introduction to the Web File Server's capabilities.

See also
ba.create.dav

The BAROOT/bin directory includes a pre-compiled version of the wfs example server, which incorporates a WFS instance.

Note: NetIo instances are designed to interoperate with a Web File Server and will not function unless the server has a WFS instance installed in the virtual file system. See the NetIo Tutorial for details on using the client and server to set up a network file system.

The Web File Server's integrated Web File Manager is a Lua implemented version of the Web File Manager in directory BAROOT/xrc/misc/HttpResMgr.c

The Lua implemented Web File Manager requires a number of resource files. The resource files required are the same files required by the HttpResMgr. See the HttpResMgr example for more information.

The created object has the following additional directory method:
wfs:configure(options)
options=table -- A table with the following optional fields:
tmo=number
The Web file Manager adds an additional session URL button if the 'tmo' field is set to a non zero value and an authenticator is set on the Web File Server. The 'tmo' field specifies the maximum idle time in seconds before the session URL expires.
helpuri=string
A help button is added to the Web File Manager if the helpuri is provided. The uri must point to your customized Web File Manager Readme File.
logouturi=string
A logout button is added to the Web File Manager if the logouturi is provided.
Example:
require"wfs" -- Install function ba.create.wfs
fs=ba.create.wfs("fs", ba.openio("root"), ".LOCK")
fs:configure{tmo=2*60*60,helpuri="/my/help/",logouturi="/logout/"}
fs:insert()
ba.create.resrdr([name] [,priority], io)

Creates a Barracuda Resource Reader. The first two parameters are the same as ba.create.dir().

The name represents a directory name. io - an I/O interface created by ba.mkio() or ba.openio() a reference to the I/O interface exists for as long as the created object exists.

When using the Mako Server or the Xedge, the two servers automatically create a resource reader for each application loaded. The resource reader directory object is automatically included in the application's environment and can be accessed in the .preload script as 'dir'. You can remove the resource reader from the server's virtual file system as follows if you create an application that does not provide a Web/REST/AJAX API: dir:unlink().

The created object has the following additional directory methods:

rsrdr:getapp()
returns the application table if installed, otherwise nil is returned.
rsrdr:io()
returns the objects I/O interface.
rsrdr:lspfilter([apptab])
enables Lua Server Pages (LSP) for the directory. The optional apptab is an optional table that can be made available to all LSP pages in the resrdr. See the command environment: application table for more information.
rsrdr:header(table-with-key-value-pairs)
Include additional/custom HTTP headers as part of every response for all resources delivered from the Resource Reader, including LSP pages. The method is typically called in a .preload script (as dir:header{...}) when configuring CORS settings that should be the same for all LSP pages included in the application. Individual LSP pages can set new CORS header values and/or overwrite the pre-set values.
-- Example: harden security policy
rsrdr:header{
   ["content-security-policy"]=
      "default-src 'self'; connect-src https://mydomain.com",
   ["x-xss-protection"]="1; mode=block",
   ["x-frame-options"]="SAMEORIGIN",
   ["x-content-type"]="nosniff",
   ["Strict-Transport-Security"]="max-age=31536000; includeSubDomains",
}
rsrdr:maxage(seconds)
Sets header "Cache-Control: max-age=seconds" on all resources delivered by the Resource Reader, except for LSP pages.
rsrdr:insertprolog(dir [,reference])
Inserts a directory as a child in the Resource Reader. The method is similar to dir:insert, except for that the directory is inserted in the Resource Reader's prologue directory list. The prologue directory list is search prior to searching for resources in the Resource Reader. Directories inserted with dir:insert() are only searched if the resource was not found in the Resource Reader. Method rsrdr:insertprolog() is typically used when authentication is only needed on sub-directory in the Resource Reader. A prologue directory with an authenticator and directory name matching the directory in the Resource Reader that must be protected can be inserted as a prologue directory. The prologue directory will be activated if the relative URL matches the prologue directory, the authenticator will kick in, and make sure the user is authenticated.
ba.create.upload(io, maxuploads)
Creates an upload object that can upload files using either HTTP PUT or HTTP POST multipart/form-data. Argument io, the I/O-interface is where the upload is saved. Argument maxuploads is the maximum allowed number of concurrent uploads.

The upload functionality has been specifically designed for uploading (large) files asynchronously to persistent storage, such as a hard drive, on the server. The upload functionality can be activated from an LSP page or from a directory function. The benefit of using a directory function is that one can use the relative path received in the directory callback function as the path to where to store the uploaded file. In other words, one can use the upload functionality as a base for creating a Web File Manager. For example, the WFS is internally using an upload object.

Unlike request:multipart() and request:rawrdr(), which requires a dedicated thread while the upload is in progress, ba.create.upload() uses asynchronous sockets for the upload. The active socket is moved out of the web-server and into an object handled by the upload logic. The upload logic is particularly efficient at handling many concurrent uploads. There are no limitations to the file upload size, except for hard drive limitations. The upload logic maintains HTTP 1.1 persistent connections. A persistent connection is automatically moved back into the web-server at the end of the upload and after a response is sent to the client.

Function ba.create.upload() is returning an object that is internally using the C/C++ HttpUpload class. The HttpUpload class is also used by the WebDAV server when uploading files.

Calling the object returned by ba.create.upload() as a function activates the upload. The function does not return control to the caller: upload(request, path, startfunc, completefunc, errorfunc [,environment])

  • Argument "request" is the LSP or "directory function's" request object.
  • Argument "path" must be a (directory+)filename if the upload is HTTP PUT.
  • Argument "path" must be a directory name if the upload is HTTP POST multipart/form-data. The uploaded file is saved as path+name, where name is the file name encoded in the multipart/form-data stream. The name originates from the HTML form input type name attribute: <input type='file' name='file'>
  • The arguments startfunc, completefunc, and errorfunc are Lua callback functions that are activated on start of the upload, when the upload completes, or on upload error. The 3 functions must be declared as follows:
    function uploadcallback(_ENV, upload)

    _ENV - is the environment table passed into the upload object. The upload object creates a new environment table if no table was passed into the upload object.

    upload - provides information about the upload by the following methods:

    • upload:name()
      Returns the path name relative to the I/O Interface used in ba.upload().
    • upload:url()
      Returns the request URL including the relative path..
    • upload:multipart()
      Returns true if the request is a HTTP POST multipart/form-data stream. Returns false if the request is HTTP PUT.
    • upload:session()
      Returns the session object, if any.
    • upload:response()
      Returns the deferred response object.

The callback functions:

  • Function start(_ENV, upload):
    The optional "start" function is called if the uploaded data is encoded as a HTTP POST multipart/form-data stream. The callback is called as soon as the "name" element is extracted from the multipart/form-data encoded stream. The Lua callback can, for example, be used as a method for authorizing the upload -- e.g. checks if the uploaded file name is correct. Calling upload:response() in the "start" callback function terminates the upload.
  • Function complete(_ENV, upload):
    The "complete" function is called when the upload completed successfully. This function typically fetches the deferred response object by calling upload:response() and sends a response message to the client.
  • Function error(_ENV, upload, error, extra):
    The "error" function is called if the upload fails. The upload may fail if the socket connection breaks or if the hard drive is full. Argument "error" is the error code, and argument "extra" is any additional error information upload may provide. You cannot send a response to the client if the socket connection is broken.
ba.exec(prog)

prog - string containing the executable and optional arguments.

Starts program prog in a separated process. The function waits for the process to terminate and returns the result printed to standard out by the child process. This function is similar in functionality to io.popen, but does not provide a bidirectional pipe.

This function returns the result produced by the child process on success, otherwise nil followed by an error code is returned.

This function is available on systems that support the process model such as Linux, QNX, and Windows.

See also ba.forkpty().

JSON

See also
Binary JSON

A library that supports JSON encoding and decoding.

ba.json.encode(table[,table][,size])
The function encodes all provided tables to one JSON encoded string. The function is typically used for converting one Lua table. The function can optionally convert multiple tables in a format that is compatible with the stream based JSON parser. Converting large tables in memory constrained devices may fail. The function returns "nil,"mem" if sufficient memory cannot be allocated. The internal buffer grows as needed, when encoding the JSON data. The initial start size is 512 bytes. You can optionally set the start size to a larger value, which is suggested if you plan on converting large Lua tables. Userdata, functions, and threads are set to ba.json.null.
ba.json.decode(json [, jnull] [, stacklen, namelen])

Takes a JSON encoded string and decodes it into a Lua table. Arguments:

  • string json: - The json data to convert.
  • boolean jnull: - Default true; JSON null is set to the ba.json.null value if jnull is true or not set, and to Lua nil if jnull is false.
  • integer stacklen, integer namelen: - Default 16,255; see the JSON C API for details. You typically do not need to change the default values.

On error, nil and an error message is returned. The error message is one of 'parse', 'data', 'interface', or 'memory'.

The input string is assumed to be UTF-8 encoded.

The string parameter can contain multiple JSON tables. In this case, multiple Lua tables (or [nil,message] pairs) are returned.

        print(ba.json.decode('{"myvar":null}').myvar == ba.json.null) -- prints true
        print(ba.json.decode('{"myvar":null}',false).myvar == nil)  -- prints true
        local table1, table2 = ba.json.decode('{}{}')
        assert(type(table1) == "table" and type(table2) == "table")
        
ba.json.encodestr(string)
Encodes a string as JSON string.
ba.json.null
A userdata that represents the JSON null value.
ba.json.parser([jnull] [, stacklen, namelen])
Module JSONS simplifies the use of the stream based JSON parser when used with a TCP/IP or WebSocket connection.

Create a stream based JSON parser object. The JSON parser object functions similar to ba.json.encode, but unlike function ba.json.encode which is limited to parsing complete JSON messages, the stream based parser parses data from a stream as the data trickles in. See ba.json.decode for the optional arguments and their default vaules.

The JSON parser is ideal for building advanced, asynchronous, message passing protocols on any type of communication channel. The communication can, for example, be between a standalone web server and other programs, or between embedded systems that are interconnected. The JSON parser can easily be used together with the socket library, but you can also use the JSON parser in combination with your own specialized communication channels such as message queues, USB connections, serial connections, etc.

parser:parse(data[,true])

Parse a chunk of data using the JSON parser object. The second argument, if true, instructs the parser to return all assembled JSON objects as one array of objects.

Return values:

  • true: The parser successfully parsed the data chunk, but the data chunk did not contain a complete JSON object.
  • true, object1, object2... : The parser successfully parsed the data chunk and assembled at least one object. The JSON parser can return multiple objects if the data chunk contained more than one JSON object. The parser returns multiple values if the second argument to parser:parse() is false or not present.
  • true, array : The parser successfully parsed the data chunk and assembled at least one object. All assembled objects are stored in the array table. The parser returns one array with the assembled objects if the second argument to parser:parse() is true.
  • nil, errorcode: The parser failed parsing the data chunk. You cannot reuse the JSON parser if the parser aborts the stream parsing. You must start over by creating a new JSON parser object.

Example 1, JSON Server:

The following example shows a potential use case for the JSON parser. A specialized LSP page is designed to extract the active socket connection from the current request and morph the HTTP request into an asynchronous receive channel for JSON data. See the socket API for more information on how to use sockets. Note: see also the JSONS module.

<?lsp

-- Activated when s:event(asyncReceiveCoroutine) is called below
local function asyncReceiveCoroutine(s)
   local parser=ba.json.parser()
   -- variable x: multiple types, or nil on error
   local x,err=true,nil
   while x do -- While no error
      -- Block and wait for data
      x,err=s:read()
      if x then -- x is JSON socket data
         local array 
         x, array = parser:parse(x,true)
         if x then -- If ok
            if array then -- If at least one object
               for _,v in ipairs(array) do
                  -- Dispatch the parsed JSON object 'v'
               end
            end
         else
            -- Parse error
            err=array -- array is now error code
         end
      end
   end
   if err == "closed" then
      print"Socket closed"
   else
      print("Socket or JSON parser error:", err)
   end
   send=nil
   -- Return: exit and close connection
end


response:flush() -- Send HTTP response headers
-- Morph HTTP request into a socket connection
local s = ba.socket.req2sock(request)
-- Enable asynchronous socket receive
s:event(asyncReceiveCoroutine)

?>

The above code is designed to receive data. You can easily extend the code to also send asynchronous JSON data to the client side.

Example 2, JSON Client:

The following example shows a basic JSON HTTP client that can be used with the above JSON service.

local c=http.create.basic()
c:request{method="GET",url="URL-TO-SERVER_RESOURCE"}
-- Morph HTTP request into a socket connection
local s=ba.socket.http2sock(c)
local data={txt="hello"} -- The data to send
s:write(ba.json.encode(data)) -- Encode and send

Module JSONS

JSONS (JSON stream parser) is a small Lua library that simplifies the use of the JSON parser object when using the JSON parser for parsing data that trickles in on a TCP/IP connection or on a WebSocket connection.

Load the library as follows.

      local js = require("JSONS")
    
JSONS functions:
js.create([table,] socket [, cfg])

Returns a JSON Stream parser object. The following example shows how to create a JSON stream parser object:

        local js = require("JSONS").create(socket)
      
Parameters:
  • table - An optional Lua table which is converted into a JSONS object. This is also the return value from the "create" function. You may store any value in this table. A table will be created if you do not provide this argument.
  • socket - A valid socket object such as a standard TCP/IP client/server socket or a WebSocket.
  • Table cfg - optional configuration options:
    • Number maxsize - Set a security limit on the maximum number of bytes a JSON stream can contain before a complete JSON object is parsed. Setting a limit prevents a rough sender from sending a huge JSON object that may exhaust the memory in the server.
    • Function bincb(data, bytesRead, frameLen, js) - The JSON parser is designed to process data in the JSON format, specifically encoded in WebSocket UTF-8 text frames. If the parser encounters a binary WebSocket frame instead of a UTF-8 text frame, the function js:read() will return an error with the code nil, "binary". To handle and process binary frames efficiently, installing a callback is recommended. When installed, the callback is triggered when a binary frame is received. The parameters data, bytesRead, and frameLen provided to the callback are consistent with the arguments that the socket:read() function can return. The js argument represents the JSONS instance.
JSON stream parser object methods:
js:get([timeout])
Reads from the socket and returns one JSON object as a Lua table when fully assembled. The optional timeout is passed to the socket:read method. Returns nil,error on error, where error is an error code from socket:read, parser:parse, or "maxsize". The error code "maxsize" is returned when the optional security parameter "maxsize" triggers.

The method blocks on the socket:read method until "timeout" or until a complete JSON object is parsed and assembled. Should a chunk of data read from the socket include multiple objects, the first object will be returned and the other objects will be saved internally. Subsequent calls return the stored JSON objects, one at a time, until the internal list is empty. Method socket:read will then be called when the internal list is empty. The method can be used with standard blocking sockets and asynchronous sockets, including cosockets when used by a socket coroutine.
js:put(table)
Encodes Lua table to JSON and calls socket:write. Returns the return value(s) from socket:write. The method can be used with standard blocking sockets and cosockets when used by a socket coroutine.
js:close()
Closes the socket by calling socket:close. Returns the return value(s) from socket:close.
ba.loadfile(filename[,io][,_ENV])
This function is similar to the native Lua loadfile(), but differs in that it uses a Barracuda I/O object to read the file that is loaded. The I/O, if not provided, defaults to the VM I/O. The file is loaded using the VM's default I/O if io is not provided. If env is provided, sets env as the value of the first upvalue of the created function. The first upvalue will be the _ENV variable. Example: io:dofile("myscript.lua",setmetatable({},{__index=_G})).
ba.mime(extension)
Returns the mime type for the file extension. The second argument is a boolean which indicates if the extension is known. The server returns "application/octet-stream", false, if the extension is unknown.

DateTime, TimeTable, and TimeSpan

The DateTime type represents dates and times with values ranging from 00:00:00 (midnight), January 1, 0001 through 11:59:59 P.M., December 31, 9999 in the Gregorian calendar. Time values are measured in nanosecond units called ticks. The ticks are internally stored as a 3-tuple {seconds, nanoseconds, offset}. The offset represents a time zone in minutes controlled by you. The 3-tuple {0, 0, 0} represents the time "1970-01-01T00:00:00Z". The time is internally always assumed to be UTC. The offset is used during encoding to ISO 8601 and optionally when encoding to TimeTable.

The DateTime object works together with the following supporting Lua tables:

TimeTable : {
   year = number,   -- 1 to 9999
   month = number,  -- 1 to 12
   day = number,    -- 1 to [max value depends on month]
   hour = number,   -- 0 to 23
   min = number,    -- 0 to 59 
   sec = number,    -- 0 to 59 
   nsec = number,   -- 0 to 999,999,999
   offset = number, -- -1,439 to +1,439 [ i.e. time zone +- 60*24-1 ]
}

TimeSpan : {
   days = number,    -- Minimum int value to maximum int value
   hours = number,   -- Minimum int value to maximum int value
   mins = number,    -- Minimum int value to maximum int value
   secs = number,    -- Minimum int value to maximum int value
   nsecs = number,   -- Minimum int value to maximum int value
}
Create a DateTime object
ba.datetime("MIN")
ba.datetime("MAX")
ba.datetime("NOW" [,local] [,TimeSpan])
ba.datetime(ISO8601 [,TimeSpan])
ba.datetime(timetable [,local] [,TimeSpan])
ba.datetime(secs, [nanosecs [,offset]])
ba.datetime(secs, nanosecs ,offset, TimeSpan)
Arguments:
  • The first argument is one of the following:
    • "NOW" - Use the current UTC time. You may set your own time zone by providing the offset in minutes (number) as the second argument. Note that not all platforms support nanoseconds.
    • ISO 8601 encoded string. The string is parsed and stored internally as the 3-tuple. An ISO 8601 encoded string includes time zone information, and the offset tuple is set according to the information found in the parsed string.
    • TimeTable - A table with key/value pairs as explained above. You may set the second argument to true if the timetable is encoded using local time zone and if the table includes 'offset'. The time will then convert and store as UTC internally.
    • A 3-tuple tick value. Only the seconds tuple is required; however, all 3-tuple values must be provided if also providing a TimeSpan.
    • "MIN" - the minimum value (seconds = -62135596800).
    • "MAX" - the maximum value (seconds = 253402300799 and nano-seconds = 999999999).
  • Argument TimeSpan is optional and enables adjusting the time according to the values in the TimeSpan table.
Function ba.datetime returns a DateTime object with the following methods:
tostring([offset])
Return a ISO8601 encoded string. The optional 'offset' argument enables using a different time zone than the one stored internally.
offset([offset])
Set and/or get the time zone. The old value is returned if setting the time zone.
ticks()
Returns seconds, nanoseconds, and offset
date([local])
Returns a TimeTable. The optional local argument is a boolean that can be set if the TimeTable should be encoded using the local time zone.
Metamethods:

In addition, the DateTime object supports the following meta methods: __add, __sub, __eq, __lt, __le, and __tostring. The metamethods enable arithmetic operations and automatic conversion to ISO8601 string.

Valid operations:
  • DateTime1 - DateTime2 : returns nano-seconds, throws exception if DateTime1 < DateTime2
  • DateTime +/- TimeSpan : returns DateTime, throws exception if out of range
  • DateTime +/- nano-seconds : returns DateTime, throws exception if out of range
  • DateTime1 OP DateTime2, where OP is <, ==, ~=, or > : Returns bolean value
Examples
print(ba.datetime"MIN")     -- Prints: 0001-01-01T00:00:00Z
print(ba.datetime"MAX")     -- Prints: 9999-12-31T23:59:59.999999999Z
print(ba.datetime"NOW")     -- Prints the current UTC time
print(ba.datetime(0))       -- Prints: 1970-01-01T00:00:00Z
print(ba.datetime(0,0,60))  -- Prints: 1970-01-01T01:00:00+01:00
print(ba.datetime(0,0,-60)) -- Prints: 1969-12-31T23:00:00-01:00

print(ba.datetime"MIN" + {secs=62135596800}) -- Prints: 1970-01-01T00:00:00Z
-- MIN + (59 seconds and 123456789 nano seconds)
print(ba.datetime"MIN" + 59123456789) -- Prints 0001-01-01T00:00:59.123456789Z 
print(ba.datetime"MIN" - 1) -- Invalid date range

print(ba.datetime{year=1000}) -- Prints: 1000-01-01T00:00:00Z

print(ba.datetime({year=2000},          true)) -- Prints: 2000-01-01T00:00:00Z
print(ba.datetime({year=2000,offset=60},true)) -- Prints: 2000-01-01T00:00:00+01:00

print(ba.datetime({year=2000}, {days=-2000})) -- Prints: 1994-07-11T00:00:00Z

print(ba.datetime(-62135596800)) -- Prints: 0001-01-01T00:00:00Z
print(ba.datetime(-62135596800,0,0,
    {hours = 62135596800/(60*60)})) -- Prints: 1970-01-01T00:00:00Z

I/O interface

The I/O interface provides common functions for working with files stored on different media types, including standard file systems, ZIP files, and network files. The Lua I/O interface connects to the C-side implementation of IoIntf. C startup code initializes these interfaces and makes them accessible to Lua via ba.openio(ioname). New I/O interfaces can be created using ba.mkio(baseio, path).

  • DiskIo: Provides an interface to traditional file systems. It requires a porting layer for various embedded file systems, C-side initialization, and installation in the C startup code to enable standard file operations in Lua. Several porting layers are included.
  • ZipIo: Allows access to files within ZIP archives, ideal for embedded systems without a file system. It is often used in production environments to package applications and requires a C-side setup like DiskIo if embedded in the firmware. ZIP files can be encrypted and signed.
  • NetIo: Enables file access over a network, useful during development or in production systems without local storage. It allows embedded systems to simulate or interact with a file system over the network, supporting both client and server setups.

The I/O interfaces are supported on all environments, from High Level Operating Systems (HLOS) to deep embedded RTOS environments. The standard Lua I/O is also supported on all HLOS, but not RTOS environments. The standard Lua I/O can be accessed by prefixing 'io' with _G. Example: local io = _G.io.

The I/O interfaces are delivered as C code and require compilation, linking, and installation by the C startup code. The Mako Server and Xedge provide examples for setting up these interfaces. ZipIo and NetIo are suitable for embedded systems without a file system, with NetIo for development and ZipIo for deployed applications. See the NetIo Tutorial for how to use the NetIo in an embedded device without a file system during development.

See also the Lua Io.

ba.io()
Returns a table with all I/O resources registered by the C/C++ startup code. The C/C++ startup code must call the function balua_iointf() for each I/O that should be available to the Lua code. The key is the I/O name, and the value is the I/O resource. See the I/O Methods for information on the methods supported by the returned objects.
for name,io in pairs(ba.io()) do
   print(name, io:realpath"", io:resourcetype())
   assert(io == ba.openio(name))
end


See the Mako Server's I/O Interfaces for an example of how this function can be used in an assembled server.
ba.openio(ioname)
Returns a Barracuda I/O object. The I/O object must have been installed by the C startup code by calling function balua_iointf().

Depending on the operating system and I/O type, some I/O objects cannot be used directly with the root folder. The I/O may require a base path. For example, on Windows, the disk drives are virtualized and require a base path such as "/c/". You may consider creating a new I/O by using ba.mkio and provide the required base path instead of having to use a base path for each file you open. As an example, the two following constructions produce the same result on Windows: fp = ba.openio"disk":open"/c/temp/myfile.txt" and fp = ba.mkio(ba.openio"disk", "/c/"):open"/temp/myfile.txt"

C/C++ startup code example:

/* Register the DiskIo as "disk" */
balua_iointf(L, "disk",  (IoIntf*)&diskIo);

Lua code example:

--Open the "disk" I/O interface created by the C startup code:
local diskio = ba.openio("disk")

--Open the "net" I/O interface created by the C startup code:
local netio = ba.openio("net")

ba.mkio() - Create a new I/O:
  • ba.mkio(baseio, path) - Creates a new I/O interface from a DiskIo or URL, allowing file access on a standard file system.
  • ba.mkio(filehandle) - Creates a new I/O interface using an existing Lua file handle for file operations.
  • ba.mkio(name) - Creates a new I/O interface from an embedded ZIP file, previously installed with the C function balua_installZIO.
ba.mkio(baseio, path)
Dynamically create an I/O interface object by opening a directory, a URL, or a ZIP file. The path must be relative to the base I/O. The I/O object returned from this function have the same methods as ba.openio().
-- Create a new DiskIo instance by using the sub directory "tmp" as
-- the base directory for the new I/O:
local appsio = ba.mkio(ba.openio("disk"), "tmp/")
print("appsio:")
for name in appsio:files"/" do print(name) end

-- Create a ZipIo by opening a ZIP file in the /home/mako/tutorials/
-- directory:
local zio = ba.mkio(ba.openio("disk"), "/home/mako/tutorials/intro.zip")
print("\nzio:")
for name in zio:files"/" do print(name) end

-- Required by the two examples below using https://
ba.openio("net"):netconf{shark=ba.sharkclient()}

-- Create a new NetIo by using the URL
-- https://tutorial.realtimelogic.com/fs/home/mako/tutorials/
-- as the base path. Note: Requires that a wfs instance is running on
-- tutorial.realtimelogic.com/fs/.
local netio = ba.mkio(ba.openio("net"),
                      "https://tutorial.realtimelogic.com/fs/home/mako/tutorials/")
print("\nnetio:")
for name in netio:files"/" do print(name) end

-- Mount the ZIP file
-- https://tutorial.realtimelogic.com/fs/home/mako/tutorials/opc.zip
-- by chaining the NetIo and ZipIo
local netzipio = ba.mkio(
   ba.openio("net"),
   "https://tutorial.realtimelogic.com/fs/home/mako/tutorials/opc.zip")
print("\nnetzipio:")
for name in netzipio:files"/" do print(name) end
ba.mkio(filehandle)
Dynamically create a ZIP I/O by using a Lua file handle that refers to a valid zip file. You may use file handles opened with io.open() and io.tmpfile(). Zip files opened with io.open() must use the "rb" (read binary) mode. Note: this feature requires a server compiled with integrated Lua I/O support.

Example 1:

-- Open using std Lua io
local fp = _G.io.open("my-zip-file.zip","rb")
-- Example: extract from zip file and print file content
local zio = ba.mkio(fp)
local fp = zio:open"my-file-inside-zip-file.txt"
print(fp:read"a")
fp:close()
zio:close()

Example 2:

-- Using online tutorial server, open the intro.zip application
local fp = _G.io.open("/home/mako/tutorials/intro.zip","rb")
local zio = ba.mkio(fp)
-- Open Amazon root cert embedded in ZIP file
local fp = zio:open"AmazonRootCA1.pem"
print(fp:read"a")
fp:close()
zio:close()
I/O object Methods:
Barracuda I/O objects have the following methods associated with them:
io:open(path[, mode)]

This function opens a file, in the mode specified in the string mode. In case of success, it returns a new file handle.

The optional mode string can be any of the following "POSIX file" open modes:

  • "r": read mode (the default);
  • "w": write mode;
  • "a": append mode;
  • "w+": update mode, all previous data is erased;
  • "a+": append update mode, previous data is preserved, writing is only allowed at the end of file.

Note: the DiskIo for most embedded RTOS ports support mode "r" and "w" only. The ZipIo supports mode "r" and the NetIo supports mode "r" and "w".

The objects returned by open() have the following methods:

fh:close(object)
Same as standard Lua.
fh:flush()
Same as standard Lua.
fh:read([option])
Same as standard Lua, except that the only options supported are a number of bytes or the "a" (all of the file). The default option reads LUAL_BUFFERSIZE bytes.
fh:seek(position)
Set file pointer to absolute position.
fh:write(string, ...)
Same as standard Lua.
local dio = ba.openio"disk"
local fp = dio:open("/tmp/hello.txt", "w")
assert(fp,"Should work on online demo server")
fp:write"Hello\n"
fp:close()
local fp = dio:open("/tmp/hello.txt", "a+")
fp:write"World\n"
fp:close()
local fp = dio:open("/tmp/hello.txt") -- Defaults to "r" - read mode
print(fp:read"a") -- Read entire file
fp:close()
io:resourcetype()
Returns two strings indicating the type of resource where files exist (e.g. disk) and the type of operating system.
io:type(object)
Returns true if the object is a Barracuda file handle.
io:files(dirname [,true])
Returns an iterator that will iterate over all the files in the named directory. The iterator returns each file name in the directory. If the optional second argument is set to true, the iterator returns: name, isdir, mtime, size.
io:stat(path)
Returns a table of attributes for the named path (file or directory). If the path does not exist then 'nil' is returned. The fields of the table are named as follows:
  • name - the path name
  • mtime - last modified time
  • size - the size of the file (in bytes)
  • isdir - true if the path is a directory
  • type - either "directory" or "regular"
io:realpath(path)
Returns the real path (absolute) path name of the provided relative path for 'disk' type I/Os and connected 'net' type I/Os. The function returns nil for 'zip' type I/Os and unconnected 'net' type I/Os.
io:mkdir(path)
Creates a new directory.
io:rmdir(path)
Removes a directory.
io:remove(path)
Removes (deletes) a file.
io:rename(oldpath, newpath)
Renames a file or directory.
io:loadfile(path [,_ENV])
Loads a Lua source file. Refer to the Lua base library loadfile() function. If env is provided, sets env as the value of the first upvalue of the created function. The first upvalue will be the _ENV variable. Example: io:dofile("myscript.lua",setmetatable({},{__index=_G})).
io:dofile(path [,env])
Loads and executes a Lua source file. Refer to the Lua base library dofile() function. If env is provided, sets env as the value of the first upvalue of the created function.
Extended methods:
The extended methods provide additional features for the I/O interfaces. Additional features are provided by the I/O interfaces by calling the C/C++ side I/O interface property function. The following Lua bindings are wrappers for calling the underlying C side property function. Calling one of the extended methods on an I/O interface of incorrect type has no effect.

DiskIo:
io:hide(true|false)
Some files systems such as DOS based file systems support attributes for hiding files. The hide method sets the file attribute to hidden or clears the attribute. The hide method has no effect on file systems that do not support the "hidden" attribute. UNIX type file systems typically have hidden files starting with a dot. Examples: .config, .myfile.lsp.
ZipIo:
io:setpasswd(password [,binpwd [, pwdrequired]])

Sets the password for accessing a ZIP file that was opened using ba.openio or created with ba.mkio, provided the ZIP file is password-protected. The Barracuda platform only supports ZIP files encrypted with AES.

In addition to setting a password for individual ZIP files, you can apply a password to all loaded ZIP files globally when compiling the C code. This method allows embedding an obfuscated password that automatically applies to all ZIP files. See Signed and Encrypted ZIP files for more details. You can still set a password for specific files, which will override the globally applied password.

Parameters:

  • password (string):
  • The password to be set for accessing the ZIP file.
  • binpwd (optional, boolean):
  • Set it to true if the password is in the BAS binary ZIP file format. When enabled, the binary password will be translated to an ASCII string when used. The default is false. Use the tool binpwd2str to create the passwords.
  • pwdrequired (optional, boolean):
  • Set to true to enforce password protection on all files within the ZIP. This ensures that no file within the ZIP can be replaced by a non-password-protected file, enhancing security against tampering. The default is false.
io:encrypted(filename)
Returns true if 'filename' is AES encrypted; otherwise, false is returned. Returns nil if 'filename' is not found.
io:close()
Close a dynamically created ZipIo, i.e. a ZipIo created with function ba.mkio().The ZipIo can no longer be used after you call io:close().

A dynamically created ZipIo keeps the ZIP file open until the garbage collector collects the I/O or until you explicitly call method io:close(). This function is designed for operating systems such as Windows and CE that locks open files. The file cannot be deleted from the file system before the file is closed. Explicitly calling method io:close() makes it possible to delete the ZIP file from the file system without having to wait for the garbage collector to collect the dynamically created ZipIo.
NetIo:

The NetIo is similar to a network file system and makes it possible for the server to access resources on another Barracuda server. The NetIo is typically used during development and is a requirement when developing Lua code on an embedded device with no native file system support. See the NetIo Tutorial for details on using the NetIo client and server to set up a network file system.

Note that you may get severe performance degradation when using the NetIo since the NetIo generates multiple requests for each resource fetched. The NetIo speed is directly proportional to the speed of the TCP/IP stack being used in the embedded system. The faster the TCP/IP stack, the faster the NetIo will respond. The following diagram illustrates a page request using the NetIo:

Browser        Embedded System         Web File Server
  |                   |                       |
  |    get page       |                       |
  | --------------->  |                       |
  |                   |  stat (file exists?)  |
  |                   | --------------------> |
  |                   |         yes           |
  |                   | <-------------------  |
  |                   |                       |
  |                   |   get fragment 1      |
  |                   | --------------------> |
  |                   |    fragment 1         |
  |     response      | <-------------------  |
  | <---------------  |                       |
  |                   |                       |
  |                   |   get fragment 2      |
  |                   | --------------------> |
  |                   |    fragment 2         |
  |     response      | <-------------------  |
  | <---------------  |                       |
  |                   |                       |
  |                   |                       |

Figure 6: Browser to Device to WFS Sequence Diagram

io:netconf({options})
Configure a NetIo.

A NetIo is typically created and installed uninitialized by the C/C++ startup code. Method io:netconf() makes it possible to either configure the NetIo installed by the C/C++ startup code or to configure a NetIo clone created with with ba.mkio(). See the NetIo C documentation for more information.

{options}
user=string
Username sent to destination server.
pass=string
Password sent to destination server.
proxy=string
Use a HTTPS or SOCKS5 proxy.
proxyport=number
Proxy port number defaults to 8080 for HTTPS proxy and 1080 for SOCKS5 proxy.
socks=bool
Set to true if you are using a SOCKS5 proxy. The default is to use HTTPS proxy by using HTTP CONNECT.
proxyuser=string
Proxy username
proxypass=string
Proxy password
intf=string
Bind to interface-name/IP-address. The default is to bind to any interface.
ipv6=bool
Force use of IPv6 address translation. Note, the server must have been compiled with IPv6 support.
shark = SharkSSL object
The SharkSSL object is required when using secure connections -- i.e. URLs starting with https://. This attribute is ignored if the AUX library is not enabled.

All attributes are optional and you can call the method multiple times to configure the NetIo. For example, setting user and password multiple times does not affect any of the other attributes set previously.

Note: "User, pass" and "proxyuser, proxypass" should be in pairs. The password is set to the empty string "" if missing.

The NetIo must be configured before calling ba.mkio(ba.openio("net"), URL) if a proxy and/or password are required, since ba.mkio verifies the URL before creating a new NetIo. You can either configure the NetIo installed by the C/C++ code or you can make a copy by calling ba.mkio(ba.openio("net")) -- i.e. without providing a URL. Calling ba.mkio() without providing a URL creates a copy of the original I/O.

Error codes:
The methods in the I/O object returns nil and three error codes if calling the method(s) fail.

Error codes returned:
  1. Error Type (short string).
  2. Descriptive error message.
  3. Optional error message that may be returned by the native file system.

The error type can be any of the following:
Error TypeDescription
aescompromisedAES protected file compromised
aesnosupportUnknown AES encryption
aeswrongauthWrong password or file compromised
alreadyinsertedAlready inserted
authAuthentication required or wrong credentials
bindBind failed
buftoosmallBuf too small
cannotconnectCannot connect
cannotresolveCannot resolve
enoentDirectory not found
existResource exists
gethostbynameGethostbyname failed
includeopnotvalidInclude op not valid
incorrectuseIncorrect use of API
invalidnameName not accepted by file system
invalidparamInvalid param
invalidresponseInvalid response
invalidsocketconInvalid socket con
invalidurlInvalid URL
ioerrorSome kind of I/O error
iscommittedIs committed
lockedI/O locked (probably by WebDAV)
mallocMalloc failed
memNo memory for operation
mixingwritesendMixing write send
noaccessResource locked by file system
noaeslibAES not enabled
noimplementationNot implemented
nopasswordResource is encrypted, but no password provided
nospaceNo space left on device
notcompressedZIP not compressed
notemptyDirectory not empty
notfoundNot found
noziplibZLIB not installed
pagenotfoundPage not found
pem_cert_unrecognized_format
pem_cert_unsupported_type
pem_key_cert_mismatch
pem_key_parse_error
pem_key_passphrase_required
pem_key_unrecognized_format
pem_key_unsupported_encryption_type
pem_key_unsupported_format
pem_key_unsupported_modulus_length
pem_key_wrong_iv
pem_key_wrong_length
proxyauthAuthentication required or wrong credentials
prxaddressAddress type not supported
prxcommandCommand not supported
prxgeneralgeneral SOCKS server failure
prxhostHost unreachable
prxnetworkNetwork unreachable
prxnotallowedconnection not allowed by ruleset
prxnotcompatNot a supported SOCKS version
prxreadyNot an error: socket in state proxy ready
prxrefusedConnection refused
prxttlTTL expired
prxunknownUnkown socks err
socketclosedSocket closed
socketreadfailedSocket read failed
socketwritefailedSocket write failed
sslalertrecvTLS error
sslclosenotifyTLS error
sslcryptoerrTLS error
sslhandshakeTLS error
sslnotenabledTLS error
sslnottrustedTLS error
sysshutdowncosocket error: process is about to exit
timeoutTimeout period expired before receiving data or event
toomanyforwardsToo many forwards
toomanyincludesToo many includes
toomuchdataToo much data
wrongpasswordWrong password provided
zipbufMemory error
zipcompressionZIP error
ziperrorUnknown ZIP file format
zipincompatibleZIP error
zipreadingZIP error
zipspannedZIP error
ba.mallinfo()
This function is available if the BAS library is configured to use dlmalloc, which is the default for most real-time operating system (RTOS) builds. The function returns a table with:
  • size - The size of the memory region provided at system startup.
  • ordblks - The number of ordinary (i.e., non-fastbin) free blocks.
  • usmblks - The "highwater mark" for allocated space-that is, the maximum amount of space that was ever allocated.
  • uordblks - The total number of bytes used by in-use allocations.
  • fordblks - The total number of bytes in free blocks.
  • keepcost - The total amount of releasable free space at the top of the heap. This is the maximum number of bytes that could ideally be released, but there is no API for this.
local fmt=string.format
local m=ba.mallinfo()
print(fmt("\nTotal memory: %d bytes",m.size))
print(fmt("High water:	   %d bytes",m.usmblks))
print(fmt("Used:	   %d%% (%d bytes)", m.uordblks*100//m.size, m.uordblks))
print(fmt("Free:	   %d%% (%d bytes)",(m.size-m.uordblks)*100//m.size, m.size-m.uordblks))
ba.encdate(time)
Encodes time in a format suitable for an HTTP response.
-- Prints: Thu, 01 Jan 1970 00:00:00 GMT
print(ba.encdate(0))
-- Prints 1000
print(ba.parsedate(ba.encdate(1000)))
ba.parsedate(string)
Parses the date string and returns the number of seconds since 1970-01-01 00:00:00 GMT. This function is designed for parsing the HTTP date header and is used by several library functions such as http:stat().
-- Prints the number 0
print(ba.parsedate"Thu, 1 Jan 1970 00:00:00 GMT")
-- Prints: 2001-09-11T18:00:00Z
print(ba.datetime(ba.parsedate"Tue, 11 Sep 2001 18:00:00 GMT"))

ba.parselsp(string[,embed])

Parses and returns the Lua Server Page as a Lua chunk. The returned string can for example be loaded by function load().

The default is to parse the LSP page as a standalone page, unless embed is set to true. The LSP page expects the following arguments when compiled as a standalone page: page(_ENV,pathname,io,page,app), where _ENV is the command environment, pathname is typically derived from a directory functions relative path. The arguments io,page, and app are the LSP page's I/O object, page table, and application table.

See the tutorial How to Build an Interactive Dashboard App for a practical example of how to use function ba.parselsp()

Function parselsp can for example be used as an alternative to response:include(). The following example shows how to use parselsp in one LSP page to include another LSP page.

<?lsp
local fp,err=io:open"mypage.lsp"
if fp then
   local data
   data,err = fp:read"a"
   fp:close()
   if data then
      -- Convert LSP to Lua
      data,err = ba.parselsp(data)
      if data then
         local func
         -- Compile Lua code
         func,err = load(data,"mypage.lsp","t",_ENV)
         if func then
            local ok
            -- Let child use our: _ENV,io,page,app
            ok,err = pcall(func,_ENV,"mypage.lsp",io,page,app)
         end
      end
   end
end
?>
ba.rndseed(seed)
Seeds SharkSSL, ba.aeskey(), and ba.rnd(). Argument seed is a number between 0 and 0xFFFFFFFF or a string. SharkSSL uses a circular 255 word size buffer for seed values and you improve the randomness if you call this function several times. Strings are converted to four byte word values and shark is seeded for each word value.

The following example connects to the Randomness Beacons project at NIST, downloads the data, and compacts the downloaded data using HMAC SHA512. The binary SHA512 string (64 bytes long) is then used as seed value.

ba.thread.run(function() -- Run the blocking HTTP request in a separate thread
    -- Create an HTTP object
   local http=require"httpc".create()
   local ok,err=http:request{
      trusted=true,
      url="https://beacon.nist.gov/beacon/2.0/pulse/last",
   }
   if ok and http:status() == 200 then
      local rnddata=http:read"*a" -- Read all random data
      rnddata=ba.crypto.hash("hmac","sha512",ba.clock())(rnddata)(true,"binary")
      ba.rndseed(rnddata) -- 512 bit compacted value (16 32bit words)
   end
   -- Seed using millisec clock.
   -- The HTTP response time creates a random delay.
   ba.rndseed(ba.clock()) -- Number
end)
ba.rnd([ [low ,] high])
Generates a random number. If no parameters are given a positive integer between 0 and 0x7fffffff is returned. If one parameter is provided then it represents the upper value of a random integer between 0 and the upper value and if two parameters are provided it represents the lower and upper limits of a random integer.
ba.rnds(size)
Generates and returns a random number of byte size 'size'.
size - in bytes. Valid range is 1 to 8.
ba.rndbs(size)
Generates and returns a byte string of length 'size' with random numbers.
size - number of bytes to return. Valid range is 1 to 0xFFFF.
ba.session(args)

Function ba.session() is an alternative to method request:session(). Method request:session() can only be used within the command environment. Function ba.session() does not have this limitation and can therefore be used from any environment.

Function ba.session() can be used in three modes:

ba.session(session-number)
session-number - number created with method session:id() or from the ID's returned by ba.sessions().
Returns the session object if found.
local s = request:session(true)
local id = s:id() -- Returns a number suitable for local server use
assert(id == ba.session(id):id())
ba.session(session-ref, request [,create])
session-ref - a string created with method session:id(true). Notice the difference between calling session:id() and session:id(true), the former returns a number suitable for local server use and the latter returns a string suitable for public use -- i.e. for use by a HTTP client such as a browser.
request - the request object.
create - a boolean value that defaults to false. Setting this variable to true also stores the session permanently in the server for the current requests thus making method request:session() return the same object, if called.
Returns the session object if found.
ba.session(request, relpath [,create])
Search for the session object by using a session URL. A session URL is a URL that includes the session-reference embedded as the first component in the relpath (relative path). As an example, the Web File Server is managing session URL's by using this function. Barracuda server sessions are normally automatically managed by using session cookies. Using a session URL is a convenient way to manage sessions if the client does not support cookies or does not support any standard authentication mechanism.
request - the request object.
relpath - the relative path is typically provided by a directory function.
create - a boolean value that defaults to false. Setting this variable to true also stores the session permanently in the server for the current requests thus making method request:session() return the same object, if called.
Returns: session,relpath where session is the session object and relpath is the original relpath minus the session-reference that was embedded in the original relpath.

The following code fragment is from the Web File Server implementation. The Web File Server code can be found in the Barracuda SDK /WebResources/rtl/.lua/ directory. The code fragment shows the authService directory service function, which is installed if an authenticator is set on the Web File Server and if the session URL is enabled.

local function authService(_ENV,relpath)
   local s
   if not request:user() then
       -- User is not authenticated or no session cookie.
      local p
      -- Does the relative path include a session reference?
      s,p=ba.session(request,relpath,true)
      if s then
         -- Yes, relpath included the session
         s:maxinactiveinterval(sesTmo)
         -- Set the new relative path
         relpath=p
      end
   end
   -- Delegate the request to the standard directory service function
   service(_ENV,relpath,s)
end
ba.sessions()
Returns an array with the session id for all sessions active in the server.
ba.sessions(username)
Returns an array with the session id for all active sessions used by the authenticated user 'username'.
ba.sleep(milliseconds)

Pauses the execution of the current request or thread for the specified duration in milliseconds. The pause may allow other Barracuda threads to run, potentially resulting in a longer pause than specified. Calling ba.sleep(0) allows other pending Barracuda threads to execute, effectively acting as a yield operation.

Note: Given that BAS operates largely on an event-driven model use this function sparingly, ideally within the context of an LSP page or when running in ba.thread.run(). Refer to the threading documentation for more information on how this function interacts with Barracuda threads.

ba.seterrh(function)

Install a Lua error handler. The error handler is called if a Lua script causes an exception. The callback function's first argument is the error message. The callback function receives the ephemeral _ENV as a second argument if an LSP page produced the error. Function ba.seterrh() returns the previosly installed error handler, if any.

An error handler could for example be installed in a deployed system and send the error message by using the SMTP library. Another possibility is to upload the error message to an online database by using the HTTPS client library. Note: All errors are sent to the Barracuda HttpTrace library, thus a Lua error handler is typically not needed during debugging.

See also Mako Server's logerr option and Xedge's SMTP settings.

ba.timer(function)

Function ba.timer schedules tasks to run after a specified time. It can either call a function once (one-shot timer) or repeatedly at set intervals (interval timer). When creating a timer, the timer callback determines when to stop. If the callback returns true, the timer repeats. If it returns false or nothing, the timer stops and cancels itself automatically. You can easily create specialized timers that run for a while and then stop by returning false. In addition, the timer function can operate as a Lua coroutine, stay in a forever loop, and use coroutine.yield() to pause and wait for the next timer tick.

The ba.timer function returns an object with three methods: set, reset, and cancel. These methods control the timer's state, which can be active or inactive. Initially, the timer is inactive and needs to be activated with set. The reset and cancel methods work on an active timer, while set is used to activate or reactivate an inactive timer.

Keep in mind that the timer object, like all Lua objects, can be collected by the Lua garbage collector if no references are held. You might be tempted to immediately call the set method on the returned object like this:

ba.timer(function() trace(ba.datetime"NOW") end):set(5000)

However, this might get garbage collected before the timer runs. To prevent this, keep a reference to the timer object:

timer = ba.timer(function() trace(ba.datetime"NOW") end)
timer:set(5000)

Alternatively, the set method has a parameter that enables automatic referencing, allowing the first example to work if you include the reference option.

The ba.timer function allows for creating advanced timer logic. However, if you're looking for a simpler approach similar to the JavaScript timer API, check out our JavaScript-like timer wrapper API. This wrapper offers a familiar interface, closely mirroring JavaScript's timer functionalities, making it easier for users accustomed to JavaScript to manage timers. For those also using the C/C++ API, it's helpful to know that the Lua timer logic internally utilizes the functionality of the C/C++ Barracuda timer class.

The timer object methods:

timer:set(millisecs [,reference [,immediately]])
Activates an inactive timer object. If the timer is active, the timer is cancelled and then activated. The timer callback function is activated in "millisecs" time unless method "reset" or "cancel" is called before the timer triggers.
  • Integer millisecs - the timer or interval time.
  • Boolean reference - the timer will be automatically referenced if this argument is set to true. The reference will be maintained until the timer is cancelled by calling function set or cancel, or when the timer function stops the timer. Without the auto reference, a global reference maintained by the Lua code creating the timer would have been required. Setting this argument to true is also convenient for creating a self referencing "one-shot timer" as shown in the following example:
    ba.timer(function() trace(ba.datetime"NOW") end):set(5000,true)
  • Boolean immediately - the timer function is run immediately if the third argument is set to true. Running the timer function immediately is sometimes useful when running the timer function in interval mode and as a coroutine. The timer function can be used in a similar manner to how one creates a thread -- i.e. the thread function is called which then enters a forever loop.
    ba.timer(function() 
      -- This code runs immediately when 'set' is called
      local starttime = ba.datetime"NOW"
      for i = 1, 10 do
         coroutine.yield(true) -- Wait for the next timer tick
         trace("This coroutine timer has run for: ",
               ba.datetime"NOW" - starttime, "picoseconds")
      end
      trace("Exiting timer coroutine")
    end):set(1000, true, true)
    
timer:reset(millisecs)
Resets the timer if the timer is active. Returns true if the timer was active and the timer was successfully reset. Returns nil if the timer is inactive and the reset call failed.
timer:cancel()
Cancels the timer if the timer is active. Returns true if the timer was active or nil if the timer was inactive.

The timer callback function:
The timer callback function is run as a "one-shot timer" if the timer callback function does not return a value or if the function returns false. The timer is automatically re-activated with the previous timeout value if the function returns true.
-- One-shot timer example:
local function timeout()
  -- do something
end
-- Create a timer. The timer object 't' is referenced in the _ENV table.
t = ba.timer(timeout)
-- Set the timeout to one second.
t:set(1000)

-- Interval timer that never stops.
local function timeout()
  -- do something
  return true
end
-- Create a self referencing interval timer
ba.timer(timeout):set(1000,true)

As an optional feature, the timer callback function can be run as a Lua coroutine. In coroutine mode, the timer callback is run as an interval timer.

The following example illustrates how the timer callback function can be run in coroutine mode. The timer saves the counter variable "i" on the stack. The variable is re-used when the timer function is reactivated. The timer is activated a total of five times before the timer exits.
local function timeout()
   for i=1,5 do
      trace("Interval", i)
      coroutine.yield(true)
   end
   trace("Stop interval")
   coroutine.yield(false)
end

-- Create a self referencing interval timer and start the coroutine immediately.
ba.timer(timeout):set(1000,true,true)

A timer callback function in coroutine mode cannot be re-used when the function calls coroutine.yield(false). However, the function can call timer:cancel(), or timer:reset() to either temporarily cancel an active timer or to change the interval time.

Timer Examples

This example demonstrates how to create a coroutine timer in an LSP page that can be started, canceled, and reset. The timer is initialized on the first LSP invocation, and subsequent LSP calls reset the timer. You can toggle the cancel/set state using the URL argument ?cancel=. Notice that the printouts follow the pattern A->B->A->B, regardless of how the timer is managed (set, canceled, or reset).

<?lsp
response:setcontenttype"text/plain"
if page.t then -- If timer saved in LSP's persistent page table.
   if request:data"cancel" then
      if page.cancel then
         trace("set: 2000")
         page.cancel=false
         page.t:set(2000)
      else
         trace("cancel")
         page.cancel=true
         page.t:cancel()
      end
   else
      trace("reset: 1000")
      page.t:reset(1000)
   end
else -- First LSP invocation
   -- Create timer
   local function run()
      trace"--- start ---"
      while true do
         trace"A"
         coroutine.yield(true) -- Wait for the next timer tick
         trace"B"
         coroutine.yield(true) -- Wait for the next timer tick
      end
   end
   page.t=ba.timer(run)
   page.t:set(500)
end
?>
See console for trace data.

The example below illustrates how a wrapper for the timer can be designed to create an API compatible with the JavaScript timer API. Notice the prefixing of _G before the three function names. This specific usage ensures that the functions are declared within the global environment, making them accessible in a manner akin to JavaScript's global scope.

-- setTimeout(callback, delay)
-- Schedules a function to be executed once after a specified delay.
-- callback: function - The function to be executed after the delay.
-- delay: number - The time in milliseconds after which the callback
--        function should be executed.
-- returns a handle - A unique identifier for the timeout.
function _G.setTimeout(callback, delay)
   local t = ba.timer(function() callback() return false end)
   t:set(delay,true)
   return t
end

-- setInterval(callback, interval)
-- Schedules a function to be executed repeatedly at specified intervals.
-- callback: function - The function to be executed at each interval.
-- interval: number - The time in milliseconds between each execution
--           of the callback.
-- returns a handle - A unique identifier for the timeout.
function _G.setInterval(callback, interval)
   local t = ba.timer(function() callback() return true end)
   t:set(interval,true)
   return t
end

-- clearTimer(timer)
-- Cancels a timeout or interval set by setTimeout or setInterval.
-- timer: handle - The identifier of the timeout or interval to clear.
function _G.clearTimer(timer)
   timer:cancel()
end

-- Example code

setTimeout(function() trace"One shot timer" end, 100)

local timerHandle = setInterval(function() trace"interval" end, 300)
setTimeout(function()
              trace"Canceling interval"
              clearTimer(timerHandle)
           end,
           1000)

setTimeout(function()
              for ix=1,5 do
                 trace("Coroutine timer", ix)
                 coroutine.yield(true)
              end
              trace"Done"
           end, 1500)
ba.tracker
A table of functions for accessing the data provided by the default client login tracker. A login tracker can be associated with an authenticator as an optional security enhancement. See introduction to authentication for more information. The table is not installed unless the C startup code has activated the default tracker by calling function balua_usertracker_create().
ba.tracker.successful()
Returns an array of tables containing:
name
The user's login name.
time
The time the user last logged in.
addr
The user's IP address
ba.tracker.attempted()
Returns an array of tables containing:
name
The attempted login name.
time
The attempted login time.
addr
The user's IP address.
counter
The total number of login attempts.
aux
Auxiliary counter. Number of last login attempts is counter - aux.
ba.tracker.clearcache()
Removes all failed login attempts from the login tracker cache.
ba.tracker.setlogh(function)
Set a Lua login handler callback. The function will be called when a user logins or fails to login.
Callback function(successful, name, addr [, _ENV]) arguments:
successful - Boolean set to true if successful login or false on failed login.
name - The (attempted) login name.
addr - The user's IP address.
_ENV - The command environment is normally not needed, but can be useful in special cases.
ba.users()
Returns an array with the name(s) of the current active user(s).

The following example terminates all sessions:

-- Iterate all authenticated users
for _,name in ipairs(ba.users()) do
   -- Iterate all sessions for the user
   for _,sesid in ipairs(ba.sessions(name)) do
      local s = ba.session(sesid) -- Fetch session using session ID.
      print(string.format("Logging out %s:%X IP address: %s",name,sesid,s:peername()))
      s:terminate()
   end
end
ba.version()
Returns 3 values: the BAS library version, the LUA version, and the time and date the library was compiled.
       local bv,lv,date=ba.version()
       print(bv,lv,date)
       -- ba.parsedate requires 'day', but it's not used
       date="Mon, "..date:gsub("^(%w+)%s*(%w+)","%2 %1")
       print(ba.parsedate(date), ba.datetime(ba.parsedate(date)))
Prints:
5354	504	Nov 14 2022 06:34:18
1668407658	2022-11-14T06:34:18Z

request object

The request object is a global variable in the command environment and is available to directory functions and LSP pages. The request object provides information sent by the client to the server.

request example

<html>
<!-- request example usage -->
<?lsp
  -- has the page been modified in the last 2 minutes
  print("<h2>request methods</h2>");
  print("<pre>");
  request:checktime(response, os.time() - 120)
  print("request:checktime(response,os.time() - 120)");
  print("request:user() ", request:user())
  print("request:cookie'z9ZAqJtI'", request:cookie"z9ZAqJtI")
  print("request:header'User-Agent'", request:header"User-Agent")
  print("\nAll request headers")
  for k,v in next, request:header() do print(k,'=',v) end
  print("\nrequest:method() ", request:method())
  print("request:uri() ", request:uri())
  local t =request:data()
  print("request:data() ", request:data())
  for k,v in next, t do print("\t",k,'=',v) end

  print("\nrequest:session() ", request:session(true))
  if request:session() then
    print("session id", request:session():id(),"\n")
  end
  print("request:version() ", request:version())
  print("</pre>")
?>
</html>


The request object has the following methods:
request:abort()
This method, which aborts the execution, is the same as response:abort().
request:allow({methods, ...,} [, usedefaults])
The allow() method checks if the HTTP request method is one of the HTTP methods specified in the parameter list. The function will cause the script to terminate if the request method is not one the allowed methods and the status code is set to 405 (method not allowed).
If the request method is allowed then allow() returns true.
allow() also terminates the script when the request method is only 'OPTIONS'.
By default the methods OPTIONS and HEAD are added automatically to an allowed option list, setting the second parameter to false prevents these defaults from being automatically added.
This function is typically used at the start of a LSP or directory function.

Example,
-- only allow GET and POST for this page
request:allow{"GET", "POST"} -- Auto abort script if not GET or POST

-- valid request; let's continue
print"hello world"
Test by removing "GET".
request:clientcert()

Request the client to send an X.509 certificate. The call may initiate a TLS re-handshake and a browser may prompt the user to select a certificate. The function returns true if the client provided a certificate and false otherwise. The function is typically used together with request:certificate().

request:certificate()

This function retrieves the peer's certificate chain in a secure connection, provided the peer has transmitted a certificate. The function returns two values:

  1. Certificate Chain: A table representing the peer's certificate chain. This is only available if the connection is secure and the peer has sent a certificate.
  2. Validation Status: A boolean value indicating the validity of the certificate. It is true if the certificate has been successfully validated against the certificate store.

The certificate store for server connections is empty in the default configuration for all example programs, including the Mako Server. Consequently, the validation status (the second return value) will be false in these scenarios.

Important Notes:

  • Prerequisite for Retrieval: The request:certificate() function will return null unless request:clientcert() has been previously invoked.
  • Configuring Certificate Store in Mako Server: When using the Mako Server, you can create a certificate store by setting the certstore attribute. This allows the server to validate client certificates against the specified store.
Example:
if request:clientcert() then
   local certT,trusted = request:certificate()
   print("You are", trusted and "" or "not", "trusted")
   print(certT and ba.json.encode(certT) or "No client certificate")
end
request:cipher()
Returns the cipher suite and SSL protocol version being used if the connection is secure. See socket:cipher() for more information on the return values.
request:checktime(time)
Handles the "If-Modified-Since" request header.
The time parameter is effectively what you wish to be the last modified time of the LSP. If the parameter time is newer or the same as the request "If-Modified-Since" time, then the function returns true.
The function sends a "304 Not modified" response to the client and terminates the script if the parameter time is older than the request "If-Modified-Since".

Example,
   -- switch off the default cache headers
   response:setheader"Cache-control"
   response:setheader"Pragma"

  -- check the modified time (anything up to a day old)
  if not request:checktime(os.time() - (60*60*24)) then return end

  -- lets provide the updated page
  print"hello world"
request:user()
Returns the username, password (if known), and authentication type of an authenticated user for this request (if one exists for the request). If there is no authenticated user, the function returns: false,name -- where name is the application name.
Possible values of the authentication type are; 'form', 'basic' and 'digest'.
request:login([username, [maxusers, [recycle]]])
Sign a user into the user management system integrated in the server. The login mechanism is designed for server side code that is not using the standard web authentication mechanism, but that still wants to create a user session for a user. Signing a user in enables this user to access secure services on the server protected by the server's authentication logic. This method is typically used when using a third party authenticator such as an OpenID Connect provider. A user can be created unbound without a name. You may also bind the user to a name and limit the amount of sessions the user can create. The default value for maxSessions is one and applies if a username is provided. See the example Single Sign On (SSO) using Microsoft AD for a practical example of how to use method request:login()
  • maxusers=number (default: 1)
  • recycle=boolean (default: true)

See ba.create.authuser for more information on the optional arguments.

The function returns true if the user is logged in and false if the condition maxusers/recycle settings prevents login.

request:logout([all])
Logout the request user and terminates the session. The optional parameter 'all' defaults to false. Set 'all' to true if you want to terminate all of the user's active sessions. A user may be logged in using more than one client. One must typically set all=true when changing password since all clients must be terminated or the clients may get a 403 response. If there is no authenticated user, then false is returned.
request:cookie(cookie name)
Returns the named cookie object.
request:env()
returns the command environment.
request:header([header name])
Returns a string with the value of the named HTTP header. If no parameter is provided, then a table containing all request header name/value pairs is returned.
request:domain()
Returns the host header without any port number. Typically used with name-based virtual hosting. The returned value is always lowercase. Returns the client's IP address if the client did not provide the host header.
request:method()
Returns the name of the request method. e.g. GET, POST, etc.
request:data([name ...])
When name is not provided returns a table containing any www-url-encoded POST or QUERY data sent to the server. The table may be empty if there is no POST or QUERY data. When one or names are provided the values of these names are returned. Note this function will not return multiple values for the same name; If this is required use request:datapairs(). Use method request:rawrdr() for any non www-url-encoded data sent to the server.
Example:
  -- get some values
  custnbr = request:data("custnbr")        -- get the value of custnbr
  name, num = request:data("name", "num")  -- get two values

  -- Print all values.
  -- Method request:data() returns a Lua table with all name/val pairs.
  for name,value in pairs(request:data()) do
    print(name,'=',value)
  end
The values printed are the once used by the tutorial engine

Additional examples:

request:datapairs()
An iterator that will traverse of all www-url-encoded name/value pairs that have been sent to the server with POST or GET. This method should be used instead of request:data() when the request includes identical names, e.g. name=val1&name=val2. Otherwise, use the much easier to use request:data() method. Note: this method cannot be used for non www-url-encoded data.
Example,
  -- display all posted values
  for name,value in request:datapairs() do
    print(name,'=',value)
  end
request:rawrdr([blocksize])
Returns a function that can be used as an iterator for reading PUT and POST data. You cannot use this function for posted www-url-encoded or multi-part data. The function throws an exception if any of the following conditions are met:
  • Request is missing content-length and not chunk encoding.
  • Request is of type URL encoded data. URL encoded data is handled directly by the server. See request:data or request:datapairs for more information.
  • Request is of type multipart/form-data. You must use request:multipart
The returned function also takes an optional blocksize as its only parameter. The default blocksize varies depending on the platform. For example, Windows is usually 512 and UNIX is usually 1024.
The iterator returned transparently handles chunk encoded data. A client can either set a content length or send data using chunk encoding. Download request:rawrdr() example from gitHub.
The following LSP example echoes the uploaded data back to the client:
<?lsp
for data in request:rawrdr() do
   response:write(data)
end
?>
The following LSP example echoes JSON data back to the client:
<?lsp
if "application/json" == request:header"Content-Type" then
   local jparser = ba.json.parser()
   local ok,table
   for data in request:rawrdr() do
      ok,table=jparser:parse(data)
      if not ok or table then break end
   end
   if ok and table then response:json(table) end
   response:senderror(400, "Invalid JSON")
end
?>
<html>
<script>
const data={
   weekdays:
     ["Sunday", "Monday", "Tuesday", "Wednesday",
      "Thursday", "Friday", "Saturday"]
  };
fetch(location.href, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
        'Content-Type': 'application/json'
    }
 }).then(res => res.json())
   .then(res => {
         console.log(res);
         document.body.innerHTML=JSON.stringify(res);
      });
</script>
<body></body>
</html>
See the browser's console for output (inspect -> console)
request:multipart(callbacks[,bufsize][,keepAlive])
Decode multi-part encoded POST data. Calling this function starts a multi byte parser, which calls your callbacks as data is parsed from the data stream. Download request:multipart() example from gitHub.
The parser allocates "bufsize" bytes for temporary storage. The buffer must not be smaller than the content size of the largest input field element of type "text" or textarea. The default value is 8K.
You can optionally set keepAlive to false if you want the web-server to close the connection after the upload is completed and you have sent a response message to the client.
Argument callbacks is a table with callback functions:
  • formdata(name, value)
  • beginfile(name, filename, contenttype, transferencoding)
  • filedata(data)
  • endmp()
  • error(emsg)

The functions are optional. The data is simply discarded if the multi byte stream contains data not handled by a callback. The function(s) can terminate the multi byte stream parsing by returning false.
Callback function formdata is called for each input field in the form. For example, the function will be called twice if you have the following form:
<form method="post" enctype="multipart/form-data">
<input type="text" name="Text input" />
<textarea name="Text area" cols="40" rows="3"></textarea>
</form>
Callback functions beginfile and filedata are called if the form contains an input of type "file". For example, the function beginfile will be called twice if you have the following form and the filedata function is called repeatedly to give you data as it trickles in.
<form method="post" enctype="multipart/form-data">
<input type='file' size='40' name='File 1'/>
<input type='file' size='40' name='File 2'/>
</form>
Callback function beginfile(name, filename, contenttype, transferencoding) arguments:
name - the name as specified in the HTML "input" type.
fileName - the name and possible path of the file entered by the user. The path separator is platform dependent.
contentType - the content mime type such as "text/plain". This parameter is NULL if not specified in the multipart data stream.
contentTransferEncoding - the transfer encoding such as "gzip". This parameter is NULL if not specified in the multipart data stream.
Callback function filedata(data) arguments:
data -       the received chunk.
Callback function endmp() is called at the end of the multi byte stream sent from the client.
Callback function error(emsg) is called if either parsing the multi byte stream failed or the connection closed.
request:uri()
Returns the request URI. e.g. /myrequest.lsp
request:url()
Returns the request URL. e.g. http://localhost/myrequest.lsp. This value is also returned by the tostring(request) function.
request:session([create])

Returns the current session object associated with this request or, if there is no current session and create is true, returns a new session. If create is false and the request has no valid session, this method returns false. To make sure the session is properly maintained, you must call this method before the response is committed. The default value of create is `false'.

See also ba.session.

request:version()
Returns the HTTP request version as a string.
request:issecure()
Returns true if a HTTPS connection is being used.

See also response:redirect2tls()
request:peername()
Returns a string of the client name/IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.
request:sockname()
Returns a string of the server name/IP address, the connected port number, and a boolean value set to true for IPv6 and false for IPv4.
request:setnodelay(bool)
Disable or enable the Nagle algorithm.

response object

The response object is a global variable in the command environment, accessible to directory functions and LSP pages. It offers methods for sending messages to the client. Most response methods return nil, error-message if an error occurs. If you need to compress dynamically generated response data, use the response:setresponse() method to handle this.

response:abort()
Abort the execution of the LSP page or directory function. This function may also abort across page boundaries if response:forward/redirect and response:include sets the optional "return to caller" false.
response:bytecount()
The number of bytes that have been sent to the client.
response:containsheader(header name)
Searches the internal response header database for a header with the specified name. If the header does not exist, the function returns false.
response:clearkeepalive()
Causes a close of the current connection after the response is sent.
response:createcookie(cookie name)
Creates a cookie with the provided name. The cookie object is returned.
response:deferred()
Convert the standard response object to a deferred (limited) response object, close the standard response object, and return the deferred response object. The deferred response object enables an application to defer the response to a later time. A deferred response should be executed in context of a thread.
response:downgrade()
Changes the protocol to HTTP/1.0.
response:encoderedirecturl(url [, withdata [, sessionURL]])
Encodes the specified URL into an absolute URL, or if encoding is not needed, returns the URL unchanged.

If the withdata parameter is specified and is true, this method also includes all url-encoded parameters in the request line and in the body if the client sent a POST message.

If the sessionURL parameter is specified in addition to withdata and a session is defined for the client, then this method encodes as above but also includes the Barracuda session ID as &z9ZAqJtI=ID.
response:encodeurl(path)
Translates a path to characters that are safe for use in a URL. For example, the following path "a/../path/with spaces" is translated to: "path/with%20spaces". Note, URL encoded parameters, if any, are not translated.
response:flush()
Forces any content in the response write buffer to be written to the client. A call to this method automatically commits the response, meaning the status code and headers will be written.
response:forward(path [,return2caller])
response:redirect(path[,return2caller])
Forwards a request from this LSP page to another resource (directory function, LSP page, CSP page, HTML file, etc). This method allows one LSP page to do preliminary processing of a request and another resource to generate the response (Request delegation). forward() should be called before the response has been committed to the client (before response body output has been flushed). This method throws an error if the response has already been committed or if the resource is not found. Any data in the response buffer is erased when the request is forwarded.

The forwarded page gets the same the Lua environment as the originating page. See the request environment for more information.

The path is the resource where you want to delegate the request. The path is assumed to be an absolute path value on the server if the string starts with "/". The path is otherwise assumed to be relative to the directory of the current page.

The return2caller is a boolean value that defaults to false. You must set this value to true if you want to return to the calling Lua script after forward has completed.

Redirect:
Method forward is similar to method redirect, with the exception that forward bypasses any form of required authentication and/or authentication.
response:include(path[,return2caller])
Includes the content of a resource (directory function, LSP page, CSP page, HTML file, etc) in the response. In essence, this method enables programmatic server side includes. See the introduction to Request delegation for more information. The response object has its path elements and parameters remain unchanged from the caller's. The included servlet cannot change the response status code or set headers; any attempt to make a change is ignored.

The included page gets the same the Lua environment as the originating page. See the request environment for more information.

The path is the resource you want to include (embed). The path is assumed to be an absolute path value on the server if the string starts with "/". The path is otherwise assumed to be relative to the directory of the current page.

The return2caller is a boolean value that defaults to true. Setting this variable to false will normally still make the call return to caller unless the included page calls method such as response:sendredirect, or any other method that aborts the request.
response:committed()
Returns a boolean (true/false) indicating if the response has been committed. A committed response has already had its status code and headers sent to the client. You cannot set response headers or response status if the response is committed. The server maintains a response buffer. The size of the response buffer is configured by the C startup code.
response:env()
returns the command environment.
response:initial()
Returns false if this is an include or forward from another directory function or LSP file; that is, the first page called during the request processing. Note, directory functions can optionally decide if the page is a "forwarded" page or not.
response:isforward()
Returns true if this is a forward request from another another directory function or LSP file. The second return value is the count of the number of times that the request has been forwarded. Note, directory functions can optionally decide if the page is a "forwarded" page or not.
response:isinclude()
Returns true if this is an include from another LSP file. The second return value is the count of the number of times that the request has been included.
response:getdata()
Returns the response data or nil if data is committed. Typically used by advanced directory functions.
response:getstatus()
Returns the HTTP status code. Typically used by advanced directory functions.
response:json(table[,table]* [ret2caller])
Encode Lua table(s) as JSON and send the response. The function simplifies sending JSON response data to a client. The function accepts multiple tables. The following example illustrates how this function works internally when only one table is supplied:
function response:json(table,ret2caller)
   local data=ba.json.encode(table)
   response:reset()
   response:setheader("Content-Type","application/json")
   response:setheader("Cache-Control",
               "no-store, no-cache, must-revalidate, max-age=0")
   response:setcontentlength(#data) 
   response:send(data)
   if(ret2caller) then return true end
   response:abort()
end
response:redirect2tls([return2caller])

Converts the URL to HTTPS and sends a redirect (301) response to the client if this is a non secure connection. This function fails if the response is committed.

This function returns true if a 301 redirect request is sent to the client and if the optional argument 'return2caller' is set to true, otherwise the function does not return. The function returns false if the connection is already secure.

See also dir:redirect2tls().

response:reset([string])
Clears any data that exists in the buffer as well as the status code and headers. Clears the content of the underlying buffer in the response without clearing headers or status code. The optional string parameter may be "headers" or "buffer", if not supplied both headers and buffers are cleared. If the response has been committed, this method returns false.
response:send(data)
An advanced function that sends data to the client. You must make sure you set the content length before sending data to the client. This function differs from response:write in that data is not sent using chunk transfer encoding and therefore requires a content-length.
response:senderror(code [, message])
Sends an error response as a simple HTML page to the client using the specified status code. An optional message can be sent with the response code. If the response has been committed, this method returns false.
response:sendredirect(url [,302 [,return2caller]])

Sends a redirect response to the client using the specified redirect location URL. A relative URL is automatically converted to an absolute URL during this call.

This function, by default, sends a 302 (temporary redirect). Set second argument to true if you want to send a 301 permanent redirect message.

This function, by default, does not return to the caller. Set the third argument to true if you want to return control to the caller. The default is for the server to automatically roll back the call chain to prevent writing to the response data after the redirect request is sent to the client.

Example:
response:sendredirect"../start.html";
 -- Send a permanent redirect request
response:sendredirect("https://realtimelogic.com", true);
response:setbasic(realm)
response:setdigest(realm)
Set the status code and HTTP basic or digest authenticate headers. The methods are typically used in a response message handler, when using the auth type authenticator, to force a client into authenticate using HTTP authentication.
response:setcontentlength(length)
Sets the "Content-Length" header value. If the header had already been set, the new value overwrites the previous one. LSP response data is normally sent chunk encoded and the HTTP header Transfer-Encoding: chunked is automatically set by the Web Server. Calling setcontentlength changes the internal state in the web-server to raw-response-writing and removes the Transfer-Encoding header, if previously set.
response:setcontenttype(type [,replace])
Sets the "Content-Type" header. type is a type string. e.g. "text/plain"
Note that the default encoding for all LSP pages is "text/html; charset=utf-8". The optional 'replace', which defaults to boolean true, replaces the content type, if already set.
response:setdateheader(name, time)
Sets a response header with the given name and date value (time). The date is specified in terms of seconds. If the header had already been set, the new value overwrites the previous one. The response:containsheader() method can be used to test for the presence of a header before setting its value.
name is the name of the header to set (e.g. "Expires").
time the assigned time value in number of seconds elapsed since midnight (00:00:00), January 1, 1970. This is the time format returned by os.time().
response:setdefaultheaders()
Sets the most common header values in LSP files. Sets content type to "text/html; charset=utf-8" and sets the header "Cache-Control" to "No-Cache". This method is automatically inserted by the LSP page.
response:setheader(name [, value])
Sets a HTTP response header with the given name and value. If the header had already been set, the new value overwrites the previous one. The response:containsheader method can be used to test for the presence of a header before setting its value. If the value is not provided then the named header is removed from the response.
setheader() should be called before the response has been committed to the client, (before response body output has been flushed). If the response already has been committed, this method returns false.
response:setmaxage(seconds)
Sets header "Cache-Control: max-age=seconds". Can, for example, be used by LSP code to override the default headers inserted by the LSP page. See response:setdefaultheaders() for more information.
response:setresponse(function|true|false)

Sets a HTTP response filter that either filters the response data or traps/redirects the response data. The filter works with any resource that uses the standard buffered output functions such as response:write and the C/C++ versions such as HttpResponse::printf. The filter does not function if the response is delegated to a C/C++ resource using the non-buffered function HttpResponse:send. In other words, one cannot delegate the response to a WebDAV, or HttpResRdr. However, one can delegate the response to an LSP enabled HttpResRdr if the requested resource is an LSP page.

In addition to the examples below, see the Light Dashboard for a real-world example of how to use 'setresponse'. In this example, compression is enabled in function cmsfunc(), which is found in cms.lua.

response:setresponse(function) Installs a generic filter function
response:setresponse([false]) Enables deflate compression using RFC1951
response:setresponse(true) Enables deflate compression using RFC1950
setresponse() returns an object with two methods:
obj:abort() Revert to the original HTTP response buffer. Returns true on success and false if response data is committed.
obj:finalize(true|false)
  • obj:finalize(true) sends compressed data to the client if setresponse is in compress mode.
  • obj:finalize([false]) returns the compressed data if setresponse is in compress mode.
  • obj:finalize() flushes any remaining data to the callback function if setresponse is in generic filter mode.

See the examples for more information on how to use method finalize().

The following examples are intended to serve as .preload scripts that can be executed using the Mako Server or Xedge. To understand the examples provided below, it is recommended that you review the Command Environment and the functionality of the Resource Reader (resrdr).

-- The global 'dir' is the .preload's Resource Reader
local dir=dir -- convert to closure so it works in serviceFunc()
local function serviceFunc(_ENV,path)
   local ae = request:header("Accept-Encoding")
   -- If requesting an LSP file and the client accepts deflate compression
   trace(path)
   if path:find("%.lsp$") and ae and ae:find("deflate") then
      trace"Using compression"
      local xrsp = response:setresponse() -- Activate compression
       -- Run the resource manager's service function
      local found = dir:service(request,path)
      if found then
         response:setcontenttype("text/html")
         xrsp:finalize(true) -- Send compressed data to client
         -- Above sets Content-Encoding and Content-Length before sending data
         return true -- LSP page (resource) found
      end
      xrsp:finalize() -- Not found, but we must clean up
      return false -- Resource not found
   end
    -- Service request without using compression
   trace"Not using compression"
   return dir:service(request,path)
end
dir:setfunc(serviceFunc) -- Install

In the example above, a directory function is created and installed within the Resource Reader, acting as a filter. This filter analyzes incoming requests and enables compression if the client supports deflate compression. Since compression cannot be applied to non-LSP resources, the function checks whether the request is for an LSP page by verifying if the requested resource ends with .lsp.

The call to xrsp:finalize(true) after the Resource Manager's service function renders the response, compiles all data, and sends it to the client. By default, the finalize method returns the data without sending it to the client. The following example demonstrates a modified service function that fetches compressed data and sends it to the client using response:write.

local dir=dir
local function serviceFunc(_ENV,path)
   local ae = request:header("Accept-Encoding")
   -- If requesting an LSP file and the client accepts deflate compression
   if path:find("%.lsp$") and ae and ae:find("deflate") then
      local xrsp = response:setresponse() -- Activate compression
      -- Run the resource manager's service function
      local found = dir:service(request,path)
      if found then
         local data=xrsp:finalize() -- Fetch compressed data
         response:setcontenttype("text/html")
         response:setcontentlength(#data)
         response:setheader("Content-Encoding", "deflate")
         response:write(data) -- Write is now the standard socket write
         return true -- LSP page (resource) found
      end
      xrsp:finalize() -- Not found, but we must clean up
      return false -- Resource not found
   end
   -- Service request without using compression
   dir:service(request,path)
end
dir:setfunc(serviceFunc) -- Install

Fetching data by calling finalize() without sending it immediately (as opposed to using finalize(true)) is beneficial in applications that implement caching. This allows a service function to be designed in a way that it can handle the caching of dynamically generated LSP data.

As an example, a cache can be implemented by using a table.

local function serviceFunc(_ENV,path)
   if pageCacheTable[path] then
      sendCachedPage(pageCacheTable[path])
      return true -- found
   end
   -- Not in cache
   .
   .

The most common use of HTTP filters is to compress response data, and the methods described above are optimized for this functionality. However, if you require filtering for purposes other than compression, a generic filter function can be installed. The following example demonstrates how to install a filter by calling response:setresponse(myRespFilter).

local dir=dir
local function serviceFunc(_ENV,path)
   -- If requesting an LSP page
   if path:find("%.lsp$") then
       -- Create a table for storing response data and a function to collect
      local dataTable={}
      local function myRespFilter(data)
         table.insert(dataTable, data)
      end
       -- Activate response filter
      local xrsp = response:setresponse(myRespFilter)
       -- Run the resource manager's service function
      local found = dir:service(request,path)
      xrsp:finalize() -- Cleanup
      if found then
         local data
         local ae = request:header("Accept-Encoding")
         -- If client accepts deflate compression
         if ae and ae:find("deflate") then
            response:setheader("Content-Encoding", "deflate")
            data = ba.deflate(dataTable) -- Compress and assemble response data
         else
            data = table.concat(dataTable) -- Assemble response data
         end
         response:setcontenttype("text/html")
         response:setcontentlength(#data)
         response:write(data) -- Write compressed or non compressed data
         return true -- LSP page (resource) found
      end
      return false -- Resource not found
   end
   return dir:service(request,path)
end
dir:setfunc(serviceFunc) -- Install

In the above example, the myRespFilter callback function is invoked when the internal web-server buffer reaches capacity. The frequency of these calls depends on the ratio of the LSP data size to the internal web-server buffer size. At a minimum, the callback is triggered once when finalize() is called. It’s important to note that the buffer size can be configured through C/C++ code at startup.

Response data is collected into a table at line 8, which is then assembled into compressed data if the client supports compression, or left uncompressed if the client does not support it.

Other uses

Beyond the examples provided, the HTTP filter can also be applied to resources that are either included or forwarded using response:include and response:forward. The filter operates with any resource that dynamically generates response data, such as CSP and CGI resources.

response:setstatus(status)
Sets the status code for this response. This method is used to set the return status code when there is no error (for example, for the status code 304 Not Modified). If there is an error, the senderror() method should be used instead. setstatus() should be called before the response has been committed to the client (before response body output has been flushed). If the response already has been committed, this method returns a non zero value.
response:write(string, ...)

Send data to the client using chunk encoding, the default LSP encoding. Note: the data is added to the response buffer, if not full. The response is automatically committed if the response buffer is full.

example:
<?lsp
  response:write"hello world"

  -- create a convenience function for writing data
  local fmt = string.format
  local function prt(...) response:write(...) end
  local function prtf(...) prt(fmt(...)) end

  prt("<br>as ","many ", "strings as you like<br>")
  prtf("You can format numbers %d", 1234)

?>
response:writesize()
Returns the number of bytes that currently exist in the response write buffer. response:flush() should reset this number to zero.

Deferred Response Object

The deferred response object is a special response object created by some methods -- e.g. upload:response(). The deferred response is typically used by a response that is detached from the original client request. A deferred response should be executed in context of a thread. The deferred response object is a Lua wrapper for the "C" class HttpAsynchResp. A deferred response object has limited buffer management and the methods must be called in the following sequence:

  1. defresp:setstatus()
  2. defresp:setheader () or defresp:setcontentlength()
  3. defresp:write() or defresp:send()
  4. defresp:close()
defresp:setstatus(status)
Sets the status code for this response. The status defaults to 200 OK.
defresp:setheader(name, value)
Sets a HTTP response header with the given name and value.
defresp:write(string, ...)
Send data to the client using chunk encoding.
defresp:setcontentlength(integer)
Sets the "Content-Length" header value. This method is used in combination with method defresp:send().
defresp:send(string)
Send data to the client. You must make sure you set the content length before sending data to the client. This function differs from defresp:write in that data is not sent using chunk transfer encoding and therefore requires a content-length.
defresp:close()
Flush and close the response.
defresp:valid()
Returns true if the response object is valid -- i.e. not closed.

cookie object

A cookie is used for exchanging a small amount of information between a page and a web browser.
A cookie's value can uniquely identify a client, so cookies are commonly used for session management.
A cookie has a name, a single value, and optional attributes such as a comment, path and domain qualifiers, a maximum age, and a version number.

Typical usage:

 cookie = request:cookie"myCookie"
 print("Cookie:",cookie and "yes" or "no")
 if not cookie then  -- If no cookie set for this page
   print"Creating session cookie"
   cookie = response:createcookie"myCookie"
   cookie:value"Hello World" -- Set
   -- Active cookie i.e. send cookie to client
   cookie:activate()
 end
 -- This should never fail
 assert("Hello World" == request:cookie"myCookie":value())
cookie:activate()
Activates the cookie.
The cookie will not be sent to the client unless this function is called. You cannot active a cookie if the data is committed. See response:committed() for more information.
cookie:delete()
Deletes a cookie, previously made persistent with function setMaxAge.
cookie:comment([comment])
Gets or sets a comment that describes a cookie's purpose.
cookie:domain([domain])
Gets or sets the domain name set for this cookie.
cookie:maxage([time])
Gets or sets the maximum age of the cookie, specified in seconds. By default, 0 indicates the cookie will persist until browser shutdown.
cookie:name()
Returns the name of the cookie.
cookie:path([uri])
Gets or sets the path on the server to which the browser returns this cookie
cookie:secure([boolean])
Either returns true if the browser is sending cookies only over a secure protocol, or false if the browser can send cookies using any protocol.
If the boolean flag is specified, informs the browser whether the cookie should only be sent using a secure protocol, such as HTTPS or SSL.
cookie:httponly([boolean])
Returns the HttpOnly cookie attribute.
Marks or unmarks this cookie as HttpOnly if the boolean flag is specified.
HttpOnly cookies are not supposed to be exposed to client-side scripting code, and may therefore help mitigate certain kinds of cross-site scripting (XSS) attacks.
cookie:value([value])
Gets or sets the value of the cookie

session object

HTTP is a stateless protocol, however, many web applications require persistent state information stored at the server side. The session object provides a way to identify a HTTP client across requests.

Modern web applications may use a combination of stateless requests such as HTTP/REST and persistent connections using WebSockets. The session object provides methods that enable seamless state information integration between HTTP/REST and WebSockets. The session logic is also integrated with the authentication logic and enables a modern Single Page Application to initially authenticate and create a session object using HTTP, when the application initially loads. The session/authentication information is then made available when the client initiates the WebSocket connection. The session is also available for server side SMQ code that may require identification of a user that was authenticated using HTTP.

The session management is handled by the server and is in C code available via the HttpSession class. The Lua interface enables HttpSession interaction as depicted in the following figure:

          ref1 = request:session()
   +----+
   |ref1+-----+   +---------+         +-------------+
   +----+     +---> Session |         | HttpSession |
                  |         +-------->+             |
   +----+     +---> table   |         | (C object)  |
   |ref2+-----+   +---------+         +-------------+
   +----+
          ref2 = ba.session()

The Lua session table references the HttpSession C object as shown in the above figure and provides Lua bindings for the HttpSession C methods. The session table allows you to:

The session table is accessed via session table reference object(s) (depicted as ref1 and ref2 in the above figure). All session methods, except session:lock() and session:release(), operate indirectly on the session table via a session reference object. A session reference object is for example created by calling method request:session(true).

Note:

Using session methods and reading session table attributes

  session = request:session() -- returns nil/false if there is no session
  if session then
     print("we have a session ", session)
     session:maxinactiveinterval(60);
     print("id", session:id());
     print("creationtime"        ,os.date("%c",session:creationtime()))
     print("lastaccessedtime"    ,os.date("%c",session:lastaccessedtime()))
     print("maxinactiveinterval" ,session:maxinactiveinterval())
     print("usecounter"          ,session:usecounter())
 
     session.attr1="val1"
     session.attr2="val2"

     -- print 'attr1=val1' and 'attr2=val2'
     for k,v in pairs(session:attributes()) do
        print(string.format("%s=%s",k,v))
     end
  else
     print"No session: run example below first"
  end

Shopping basket

  -- Using session variables
  local session = request:session()
  if not session then -- no session ?
    print"you must first login using a valid username and password"
    request:login"my-username" -- simulate login and create session
  else
    -- no basket? - create it
    session.basket = session.basket or {}
        -- add an item to the basket table
    session.basket[#session.basket +1] = "something new"
    print("We have ", #session.basket, " items in the shopping basket")
  end
Attributes

You may set your own attributes on the session and persistently store the attributes for the lifetime of the session.

        local session=request:session(true)
        session.myattr1=val1
        session["myattr2"]=val2
        
session:attributes()

Returns the session table. The method is typically used if server code requires iterating over the attributes set on a session table.

        local session=request:session(true)
        session.key1="val1" -- Indirect session table access
        session["key2"]="val2"
        for key,value in pairs(session:attributes()) do
           print(string.format("%s=%s",key,value))
        end
        
session:id([true])
Returns a unique number or string identifier assigned for this session. The string identifier, which is returned if second argument is 'true', may be used in construction of a session URL. See ba.session for more information.
session:creationtime()
Returns the time when this session was created, measured in seconds since midnight January 1, 1970 GMT. This time can be formated with os.date()
session:lastaccessedtime([true])
Returns the last time the client sent a request associated with this session, as the number of seconds since midnight January 1, 1970 GMT, and marked by the time the container received the request. This time can be formated with os.date(). The last access time stamp is updated if the optional boolean argument is 'true'.
session:maxinactiveinterval([interval])
Returns the maximum time interval, in seconds, that the session container will keep this session open between client accesses.
OR specifies the time, in seconds, between client requests before the session container will invalidate this session.
session:peername()
Returns a string of the client name/IP address associated with the session.
session:usecounter()
Gets the session usage counter.
session:terminate()
Unbinds any objects bound to this session object, destroys the session object, and frees the memory for this object. All session variables are destroyed.
session:user()
Returns authenticated user information.
If the session does not belong to an authenticated user, the function returns false. Otherwise, two strings are returned: the name of the user and the authentication type. The authentication type will be one of "basic", "digest", "form", or "default".
session.onterminate
The server calls onterminate when the server destroys the session object and if this attribute is set to a function.

Example:
local s=request:session(true)
function s.onterminate()
   ba.thread.run(function()
                 trace"Do the work here!"
              end)
end
session:lock()

Lock a session and prevent the session timer from terminating the session when the session:maxinactiveinterval() expires. Locking a session is useful when designing certain web applications using persistent connections such as WebSockets and SMQ. persistent WebSocket and SMQ connections do not terminate when the session expires and locking the session makes it easier for certain types of applications to maintain a consistent state.

As an alternative, you may inverse the application's logic by installing a session.onterminate callback that terminates any persistent WebSocket and/or SMQ connection(s) when the server's session object expires.

Note: a session will be terminated when session:terminate() is called, regardless of lock state.

Method request:session() returns a session reference object that lets you interact with the session, but the object itself is not the session. A new object is returned for each request:session() call, thus the following construction creates three locks:

local s1=request:session(true)
s1:lock()
local s2=request:session(t)
s2:lock()
local s3=request:session()
s3:lock()
session:release()

Release a lock created by calling session:lock(). See session:lock() for restrictions. The session lock is also released when the session reference object (the object returned by request:session()) garbage collects.

Authenticator User Database Object

The objects created by ba.create.authuser() and by ba.create.jsonuser() are typically used by the underlying C authenticator code for granting or denying access.
The authenticator user database object has the following method that can be used by Lua:
authuser:getpwd(username)
Returns nil if username is not found in the database or if the authenticator returns a boolean value.
Returns the following if username is found: password [, maxusers [, recycle, inactive]]
The returned string 'username' can be a plaintext password or an HA1 hash. See ba.create.authuser for more information on the return values.

Authenticator Object

The authenticator object created by ba.create.authenticator() is typically used by the underlying C code for a directory type when authenticating the user. The authenticator can also be used by Lua directory functions.
All authenticator object types have the following method that can be used by Lua:
authenticator:authenticate(request, path)
Authenticate the client
request=object
The request object from the command environment.
path=string
The path is typically from the directory function's relative path argument.
Example:
local function myDirectoryService(_ENV,relpath)
   if authenticator:authenticate(request, relpath) then
      response:write"You are authenticated"
   else
      -- The response was sent by the
      -- authenticator's Response Message Handler
   end
   return true -- True means resource found and response is committed
end

Authorizer Object

The authorizer object created by a number of functions such as ba.create.authorizer() is typically used by the underlying C code for a directory type when authorizing the user. The authorizer can also be used by Lua directory functions.
All authorizer object types have the following method that can be used by Lua:
authorizer:authorize(request,method,path)
request=object
The request object from the command environment.
method=string
The HTTP method type the user must have access to execute -- i.e. 'GET'.
path=string
The path is typically from the directory function's relative path argument.

JSON Authorizer (jauthorizer) Object

The JSON authorizer object, which inherits from the authorizer object, created by method jauthenticator:authorizer() is an optional authorizer that can be used as the target for ba.create.authenticator(), when used together with the jauthenticator that created the jauthorizer.
The jauthorizer object has the following additional authorizer methods:
jauthorizer:set(constraintdb)
jauthorizer:casesensitive([boolean])
The JSON Authorizer is by default case sensitive. Turn off the case sensitive authorizer for case insensitive file systems such as FAT based file systems.
constraintdb = table or string -- A Lua table or a JSON encoded string. The Lua table must have the following fields:
 {constraintname1=constraint1, constraintname2=constraint2} -- etc. 
where constraint1 and constraint2 are tables with the following fields:
 {urls={'relpath1','relpath2','etc'},methods={'GET','etc'},roles={'role1','etc'}} 
The constraint name is not used by the authorizer, but the authorizer requires a table and not an array; thus dummy names must be created for the constraints -- e.g. constraintname1

directory object

Objects implementing directory functionality:
ba.create.dir
ba.create.resrdr
ba.create.dav
ba.create.wfs

The HTTP directory object is returned by a number of functions such as the ba.create.xxx functions.

The Lua directory object is a wrapper for the HttpDir "C" class or any class that inherits from HttpDir such as HttpResRdr.

dir:baseuri()
Returns the URI to the location where the directory is installed in the virtual file system. Returns "/" if the directory is not installed in the virtual file system.
dir:insert() -- Insert as root directory
dir:insert(dir [,reference]) -- Insert a child directory
Inserts a directory into the server's virtual file system. See the Virtual File System Introduction and Constructing a Virtual File System Tree for more information. See also dir:insertprolog().
reference -- Reference the child directory in the parent directory. Referencing the child prevents the child from garbage collecting as long as the parent exists.
dir:name()
Returns the name of the directory
dir:p403(path)
Set a 403 denied request handler. The directory forwards the request to the page if an authorizer is installed and the user is denied access by the authorizer. The default for a directory is to send a basic 403 message if a 403 denied request handler is not installed. Parameter 'path' is the path to a page that can be accessed by response:forward.
dir:setauth(authenticator, [authorizer])
Enables authentication and optionally authorization for this directory. The authenticator is created by ba.create.authenticate() and the authorizer is created by ba.create.authorize() or jauthenticator:authorizer(). Returns true on success.

Note: when setting an authenticator for a Resource Reader, the authenticator logic enables bypassing of authentication for any resource stored in the "public" directory, if such a directory exists. This construction makes it possible to load resources used by a form based HTML authenticator page.

dir:redirect2tls([return2caller])
Enable automatic redirection of all non secure requests to secure (TLS) requests. The return2caller is a boolean value that defaults to false. You must set this value to true if you want to return to the calling Lua code if a redirect is required.


The function returns false if the connection is secure and no redirect is required. The function returns true if a redirect is required and argument return2caller is true.

Note that dir:redirect2tls() cannot be used in combination with dir:setfunc(). If a directory function is required, the directory function must internally manage the redirect. The following example shows how a directory function can be used to redirect all non secure requests to secure requests:
local function myservice(_ENV,relpath)
    -- Automatically returns if client needs to be redirected 
   response:redirect2tls()
   -- We get here if secure
end
dir:setfunc(myservice)
dir:service(request, relpath [,sim-forward])
Calls the service function of this directory. This function delegates the response to the active service function if you call another directory than "self" and the original service function if called from "self".
The request object is required.
The relative path (relpath) parameter is required.
Setting the optional parameter sim-forward, a boolean value, to true, makes the service function simulate a response:forward call without resetting the response buffers. Simulating a response:forward is useful if you need to bypass authentication and/or authorization in. It is also possible to access hidden files when delegating to a Resource Reader. A hidden file is a file that starts with a dot -- e.g. ".preload".
dir:setfunc(function)
Sets the directory service function.
Apply dir:setfunc() to objects created with:

A directory function is the directory's service callback function which is activated by the server's virtual file system when searching for resources. All directory types have a default service function implemented in C code. The Lua code can install a new directory function that either replaces the original service function or extends the functionality of the original service function. The original "C" service function can be called from within the Lua service function by calling dir:service().

The directory function must be declared as follows:

  local function myservice(_ENV,relpath)

The _ENV variable is the command environment. The relpath variable is constructed from the URL and is the relative URL path component for the directory branch.

The directory function must return true if the resource was found and a response was sent to the client. The function must return false if the resource was not found and no response was sent to the client. The virtual file system will continue its search for a suitable resource if the directory function returns false. A no return value is interpreted as returning true.

See ba.create.dir for example code.

Removes the directory from its parent (or the server).

Standard Lua Libraries

All of the standard Lua libraries are implemented with the two exceptions that the io library is only supported when the target platform supports ANSI I/O (MAC, Windows, POSIX) and the os library functions are implemented when it is appropriate on the target platform. os.exit() is not implemented on any Barracuda platform.
Platform independence is achieved by using the ba library.

Barracuda global functions

print(string, ...)

When calling function print() from the command environment, print() is a function that sends response data to the client. Function print() is otherwise the standard Lua print() function. To call the standard print() from a command environment, call print() using the global scope variable -- e.g. _G.print().

Function print() differs from response:write() in that print() calls the Lua tostring() function on each parameter that is passed to print(). Function print() is useful for generating diagnostics and small amounts of text on a web page, use response:write() in preference to print().

Note: the global _G.print(), that prints to the console, may not be available in embedded systems. Use trace() as a replacement.

Example:

<?lsp
  print("hello", "world")
?>
The above example would emit "hello\tworld\n".
trace(string, ...)
tracep([info,] priority, string, ...)
Writes the string to the web server trace log. Function trace() is useful for generating diagnostics and print debug information. The optional info is a boolean that defaults to true. Info makes tracep emit line/function info. This cannot be disabled for function trace.
Function trace is hard coded to use priority 0, the highest priority level. Function tracep lets you set the priority.
The trace library must be initialized at system start by the C startup code and is recommended to be used with the TraceLogger.
_emit(string)
Function _emit() is available in the command environment and is used by the LSP pages when sending parsed HTML chunks to the client. The LSP parser converts an LSP page and instruments the parsed code with _emit[[data-chunk]] around each parsed chunk.

Standalone Extended Executable Lua Interpreter: Xlua

A standalone executable Lua interpreter is delivered with the Barracuda Server's SDK. The executable, called xlua, is found in the SDK's bin directory.

The executable xlua is an extended version of the standard standalone executable Lua interpreter and includes many API's from the Barracuda server in addition to the original API's.

The standalone interpreter makes it easy to create standalone applications for sending (secure) E-mail, using the HTTP(S) client libraries, and creating (secure) client and server socket applications in Lua.

Note that the Mako Server can also execute a script and exit. See the Mako script option for details.

The extended executable includes: