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.
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.
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.
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.
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. |
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.
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:
pathname:match"(.-)[^/]+$"
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>") ?>
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().
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.
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.
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 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:
The authenticator uses digest or basic authentication if the client is directly specifying the authentication type by setting the "Authorization" header. Command based and HTTP client libraries integrated with applications typically set the "Authorization" header before sending the request. The authenticator also checks if the client sends a Barracuda specific "PrefAuth"http header which should be set to basic or digest.
The authenticator defaults to digest authentication if the type is not specified and the request is for a non text based URL or if the request originates from a JQuery AJAX call.
The authenticator defaults to form based authentication if the authentication is not specified and if the request is for an .html, .lsp, or .csp file.
The "auth" authenticator requires a custom response message handler, which is typically more complex to design than custom response message handlers for the other authenticator types.
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.
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.
Download Examples:
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.
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: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>
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.
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.
A library that provides a number of BAS utility and I/O functions.
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"))
len: The key length must be 16 (AES 128 bit) or 32 (AES 256 bit). The default length is set to 16.
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
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().
A table of functions for creating Barracuda objects:
-- isadmin is set in the login code local az=ba.create.authorizer(function(u,m,r,s) return s.isadmin end)
{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.
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.
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
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
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.
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.
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()
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:
-- 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", }
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:
The callback functions:
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().
See also
Binary JSON
A library that supports JSON encoding and decoding.
Takes a JSON encoded string and decodes it into a Lua table. Arguments:
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")
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.
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:
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
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:
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:
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 }
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: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
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).
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.
for name,io in pairs(ba.io()) do print(name, io:realpath"", io:resourcetype()) assert(io == ba.openio(name)) end
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")
-- 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
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()
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:
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".
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()
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.
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 | <------------------- | | <--------------- | | | | | | | |
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.
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 Type | Description |
---|---|
aescompromised | AES protected file compromised |
aesnosupport | Unknown AES encryption |
aeswrongauth | Wrong password or file compromised |
alreadyinserted | Already inserted |
auth | Authentication required or wrong credentials |
bind | Bind failed |
buftoosmall | Buf too small |
cannotconnect | Cannot connect |
cannotresolve | Cannot resolve |
enoent | Directory not found |
exist | Resource exists |
gethostbyname | Gethostbyname failed |
includeopnotvalid | Include op not valid |
incorrectuse | Incorrect use of API |
invalidname | Name not accepted by file system |
invalidparam | Invalid param |
invalidresponse | Invalid response |
invalidsocketcon | Invalid socket con |
invalidurl | Invalid URL |
ioerror | Some kind of I/O error |
iscommitted | Is committed |
locked | I/O locked (probably by WebDAV) |
malloc | Malloc failed |
mem | No memory for operation |
mixingwritesend | Mixing write send |
noaccess | Resource locked by file system |
noaeslib | AES not enabled |
noimplementation | Not implemented |
nopassword | Resource is encrypted, but no password provided |
nospace | No space left on device |
notcompressed | ZIP not compressed |
notempty | Directory not empty |
notfound | Not found |
noziplib | ZLIB not installed |
pagenotfound | Page 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 | |
proxyauth | Authentication required or wrong credentials |
prxaddress | Address type not supported |
prxcommand | Command not supported |
prxgeneral | general SOCKS server failure |
prxhost | Host unreachable |
prxnetwork | Network unreachable |
prxnotallowed | connection not allowed by ruleset |
prxnotcompat | Not a supported SOCKS version |
prxready | Not an error: socket in state proxy ready |
prxrefused | Connection refused |
prxttl | TTL expired |
prxunknown | Unkown socks err |
socketclosed | Socket closed |
socketreadfailed | Socket read failed |
socketwritefailed | Socket write failed |
sslalertrecv | TLS error |
sslclosenotify | TLS error |
sslcryptoerr | TLS error |
sslhandshake | TLS error |
sslnotenabled | TLS error |
sslnottrusted | TLS error |
sysshutdown | cosocket error: process is about to exit |
timeout | Timeout period expired before receiving data or event |
toomanyforwards | Too many forwards |
toomanyincludes | Too many includes |
toomuchdata | Too much data |
wrongpassword | Wrong password provided |
zipbuf | Memory error |
zipcompression | ZIP error |
ziperror | Unknown ZIP file format |
zipincompatible | ZIP error |
zipreading | ZIP error |
zipspanned | ZIP error |
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))
-- Prints: Thu, 01 Jan 1970 00:00:00 GMT print(ba.encdate(0)) -- Prints 1000 print(ba.parsedate(ba.encdate(1000)))
-- 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"))
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 ?>
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)
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:
local s = request:session(true) local id = s:id() -- Returns a number suitable for local server use assert(id == ba.session(id):id())
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
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.
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.
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.
ba.timer(function() trace(ba.datetime"NOW") end):set(5000,true)
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)
reset
call failed.-- 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)
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.
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)
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. |
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
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
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.
<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>
-- 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 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().
This function retrieves the peer's certificate chain in a secure connection, provided the peer has transmitted a certificate. The function returns two values:
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:
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
-- 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"
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.
-- 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) endThe values printed are the once used by the tutorial engine
Additional examples:
-- display all posted values for name,value in request:datapairs() do print(name,'=',value) end
<?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)
<form method="post" enctype="multipart/form-data"> <input type="text" name="Text input" /> <textarea name="Text area" cols="40" rows="3"></textarea> </form>
<form method="post" enctype="multipart/form-data"> <input type='file' size='40' name='File 1'/> <input type='file' size='40' name='File 2'/> </form>
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. |
data - | the received chunk. |
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.
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.
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
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().
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);
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 RFC1950setresponse() returns an object with two methods:
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.
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.
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) ?>
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:
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())
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:
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
-- 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
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
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
local s=request:session(true) function s.onterminate() ba.thread.run(function() trace"Do the work here!" end) end
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()
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.
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
{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
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.
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.
local function myservice(_ENV,relpath) -- Automatically returns if client needs to be redirected response:redirect2tls() -- We get here if secure end dir:setfunc(myservice)
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.
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.
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".
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: