Lua is an embeddable, extensible, extension language and is used in the Barracuda server for writing applications at a higher level than applications designed in C/C++ code. Applications designed in Lua are not limited to web-applications, thus Lua applications can be used as a method of designing your overall (embedded) application.
The Barracuda server and all plugins are designed by Real Time Logic, with the exception of the Lua scripting language. Lua uses the MIT license model and can therefore be used in commercial products.
In the Barracuda Embedded Web Server (BA), Lua code is typically activated by an event generated by C code. The following diagram illustrates the sequence of events:
In the above diagram, C code activates Lua code and the Lua code calls Lua C functions made available to the Lua script. C code that is made available to Lua code is referred to as Lua bindings.
The BA library provides a wealth of functions available to Lua script code. You simply provide the device management functionality for your platform. BA and Lua are designed to be extended and it is therefore easy to add your own device management functions. Making C functions available to Lua -- i.e. creating Lua bindings is described in the Lua book. One can also automate this task by using various tools designed to parse a C/C++ header and automatically create the Lua bindings for the C/C++ code. For example, our Online Lua Binding Generator is a tool that automatically creates Lua bindings from your C header files.
Lua Server Pages, or LSP for short, 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. However, LSP can be pre-compiled if desired.
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.
Like CSP, LSP can be loaded from a compressed directory.
LSP pages are available for any target platforms supported by the Barracuda Web Server.
A standalone executable Lua interpreter and a compiler are also provided. The standalone compiler is useful for testing LSP prior to deployment without the aid of a Web server.
Lua also provides a mechanism to construct the virtual directory tree used by the Web Server. This directory tree consists of C/C++ HttpDir objects. A directory structure specified with Lua is similar in performance to a C/C++ constructed tree; however, it is faster and easier to construct with Lua. With the ba.create family of functions, HttpDir derived objects such as WebDAV, HttpResRdr, etc., can be created in either preload scripts or LSP pages. The LSP directory function mirrors the Barracuda C/C++ APIs. All directory types provide a set of common methods. See the directory object for more information.
|
Lua has the concept of environments. Environments provide scoping for collections of variables. The standalone Lua interpreter provides one global environment known as _G. The LSP plugin provides the same global environment _G, but LSP also provides a short lived environment called the "request/response environment". This short lived environment is also known as the "command environment" or CMDE for short. Any global variables created in Lua Server Pages (LSP) and/or directory functions will be saved in this short lived command environment. The command environment is also the environment of any included or forwarded pages. Hence variables set in a LSP page will be available to included or forwarded pages. The diagram to the right, shows the relationship between the command environment, directory functions, LSP pages, the LSP page's private page table, and the LSP page's application table. The command environment is automatically created when the first LSP page or directory function is executed. This environment lives for the lifetime of the request and is automatically terminated when the request completes. Global variables declared in this environment are available to all included and or forwarded pages. The environment is also available to directory functions. Note: The diagram shows a hypothetical request where a LSP page delegates the request to another LSP page, then to a directory function, and so on. The purpose with this diagram is to show you the relations between objects. One would typically not delegate a request as deep as shown in the diagram to the right. |
![]() |
Directory Function
A directory function is a Lua function installed in one of dir, resrdr, dav, or eh. 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 lspappmgr 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/table 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. As an example,a typical use of Session variables would be to maintain a users shopping basket:
<?lsp
-- Using session variables
print("<pre>");
local session = request:session()
if not session then -- no session ?
print"you must first login using a valid username and password"
else
-- no basket? - create it
session.basket = session.basket or {}
-- add an item to the basket table
session.basket[#session.basket +1] = "something new"
end
print("We have ", #session.basket, " items in the session table")
print("</pre>")
?>
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 is not designed for preemptive context switching, but Lua provides a light weight threading mechanism called coroutines. In Barracuda(BA), Lua event generators are mapped to coroutines, thus each event triggered by BA has its own Lua stack. In BA, multiple events may be active simultaneously even though Lua is nonpreemptive. The BA Lua event generators are typically run by native threads on the C side and each native thread is mapped to a Lua coroutine. When a Lua coroutine is running, the thread may yield to another thread when a BA lua binding is called. For example, an LSP page may be preempted when calling response:write().
The following diagram gives an overview of the event and threading mechanism in BA:
The socket event dispatcher fires events when new data is available on a socket or if a non-blocking socket is ready for writing. The socket event dispatcher delegates client HTTP requests to the server thread pool which activates one of the threads. The thread searches for the resource and if found, executes the resource in a command environment. The resource may, for example, be an LSP page or a Lua directory function.
The socket event dispatcher can also fire events directly to Lua code if the auxiliary socket library is included. The Event Handler events are also run in the context of the dispatcher.
It is important that Lua code running directly in the context of the socket event dispatcher does not block as this will prevent the web-server from serving other requests. In general, Lua code should not block unless run in the context of the Server HTTP Thread Pool or the Lua Thread Library. Events run in the context of the dispatcher or the timer thread can delegate jobs to the Lua Thread Pool.
It is also possible to create user defined events, but this requires extensive knowledge of Lua. Please contact Real Time Logic if you plan on designing user defined events or if you create Lua bindings that may block for a prolonged time.
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.
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 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 must decide from the username, the HTTP method type, and the requested relative path if the user has access or not. The function returns true if the user has access and false or nothing if the user does not have access.
The authorizers must decide from username, method, and path if a user has access. You may want to authorize based on other criteria, and you have many options when it comes to authorizing user besides using the authorizer objects. Each LSP page can make its own decision or you can override the directory service function and implement a generic authorizer specifically tailored for 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.
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.
The Do It Yorself (DIY) tutorial bundled with the Barracuda Server's combined demo and tutorial includes a number of interactive authentication 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 clear text 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 following examples show how to create a form response message handler and a 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 clear text.
<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 clear text. 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.
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. local function getSecureUrl(cmd) local host = cmd:header"host" -- Use host header if sent by client, otherwise use IP addr if host then host = string.gsub(host,":%d+","") -- Strip port number, if any else host = cmd:sockname() end -- Create the secure URL local url = "https://"..host return cmd:encoderedirecturl(url..cmd:uri(), true) end -- The username/password callback function. -- Notice how we use the optional _ENV command environment. local function getpassword(username, _ENV) if not request:issecure() then -- Deny login: send redirect request response:sendredirect(getSecureUrl(request)) -- sendredirect 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) -- sendredirect does not return response:sendredirect(getSecureUrl(request)) 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 clear text 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.
Each LSP page is provided with some predefined variables and objects; namely, request, response, cookie, session and page.
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"
-- 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"
-- get some values
custnbr = request:data("custnbr") -- get the value of custnbr
name, num = request:data("name", "num") -- get two values
-- display posted values
for name,value in pairs(request:data()) do
print(name,'=',value)
end
-- 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 ?>
<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. |
The response object is a global variable in the command environment and is available to directory functions and LSP pages. The response object provides methods that can be used when sending messages to the client. Most of the response methods return nil, error-message, when an error is encountered.
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
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.
response:sendredirect"../start.html";
-- Send a permanent redirect request
response:sendredirect("http://realtimelogic.com", true);
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().
local function serviceFunc(dir,func,path)
local ae = request:header("Accept-Encoding")
-- If requesting a 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
local found = func(path) -- Run the resource manager's service function
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
return func(path) -- Service request without using compression
end
-- Create a LSP application
myApp = ba.create.resrdr("MyDir",serviceFunc,0, myIo)
myApp:lspfilter() -- Enable LSP
In the above example, a directory function is created and inserted into the resource manager's constructor. The directory function is used as a filter. The filter function analyzes the request and enables compression if the client supports deflate compression. The resource manager's service function (func) is passed in as the second argument to the filter service function (serviceFunc). We must call this function which runs the requested LSP page. We cannot enable compression for non LSP resources so we check to see if the request is for a LSP page or not -- i.e. we check if the requested resource ends with .lsp.
Calling xrsp:finalize(true) after the resource manager's service function has rendered the response assembles all data and sends the data to the client. The default for the finalize method is to return the data and not send the data to the client. The following example shows a modified service function that fetches the compressed data and sends the data to the client using response:write.
local function serviceFunc(dir,func,path)
local ae = request:header("Accept-Encoding")
-- If requesting a 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
local found = func(path) -- Run the resource manager's service function
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
return func(path) -- Service request without using compression
end
Fetching the data by calling finalize() instead of directly sending the data by calling finalize(true) is useful in an application that implements caching. A service function may be designed such that it knows how to cache dynamically generated LSP data.
As an example, a cache can be implemented by using a table.
local function serviceFunc(dir,func,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 above methods are optimized for this functionality. If you have uses other than for compressing the response, a generic filter function can be installed. The following example installs a filter function by calling response:setresponse(myRespFilter).
local function serviceFunc(dir,func,path)
-- If requesting a 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)
local found = func(path) -- Run the resource manager's service function
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 func(path) -- Service request without using compression
end
In the above example, the myRespFilter callback function is called when the internal web-server buffer is full. The number of times the myRespFilter callback function is called is a function of (LSP data size / internal web-server buffer size). At a minimum, the function is called once when finalize() is called. Note that the internal web-server buffer can be configured by C/C++ code at startup.
Response data is inserted into a table, which is assembled into compressed data if the client supports compression or uncompressed data if the client does not support compression.
In addition to what is shown in the above examples, the HTTP filter also works on resources either included or delegated -- i.e. response:include and response:forward. The filter works with any resource that is dynamically creating the response data such as CSP resources 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("as ","many ", "strings as you like<br />")
prtf("You can format numbers %d<br />", 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. 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" if not cookie then -- If no cookie set for this page -- Create a session cookie cookie = response:createcookie"myCookie" -- Active cookie i.e. send cookie to client cookie:activate() end -- This should never fail assert(cookie == request:cookie"myCookie")
The session object provides a way to identify a user across more than one page request or visit to a Web site and makes it possible to store information about that user.
The HttpSession container uses this class to create a session between an HTTP client and an HTTP server. The session persists for a specified time period, across more than one connection or page request from the user. A session usually corresponds to one user who may visit a site many times. The server can maintain a session in many ways such as using cookies or rewriting URLs.
This interface allows you to:
Variables may be assigned and retrieved from the session object. This is done using normal Lua syntax.
Any attempt to create session variables with names the same as the session methods, will result in an error.
Session variables are destroyed when the session terminates or times out. LSP session variables are not accessable from CSP pages and vice versa.
A user can create a tool that could potentially overflow the session container. The Barracuda authentication system makes sure that a session object is not created before the user is authenticated. It is therefore recommended to create a session object by using the authentication system.
<html>
<!-- example session variable usage -->
<?lsp
print("<h2>session variables</h2>");
print("<pre>");
session = request:session() -- returns nil/false if there is no session
if session then
print("we have a session ", session)
session.counter = (session.counter or 0) + 1 -- count session usage of this page
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())
-- if we have any attributes print them all
if session:attributes() then
table.foreach(session:attributes(), print)
end
end
print("</pre>")
?>
</html>
local s=request:session(true) function s.onterminate() ba.thread.run(function() trace"Do the work here!" end) end
A library that supports JSON encoding and decoding.
To enable this library a require "json" must be issued.
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.
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.
<?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 client designed using the Barracuda Embedded Web Server HTTP client library.
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
A library that provides a number of Barracuda utility and IO 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.
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. The default action is to deflate data using RFC1951 -- i.e. compress without adding the ZLIB deflate header. The correct deflate method is to use RFC1950, but Internet Explorer does not seem to follow the RFC1950 specification, thus IE fails if a ZLIB header is added. All other browsers also accept RFC1951.
A table of functions for creating Barracuda objects:
{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 fields maxUsers, recycle, and inactive are optional.
The following example sets up an authenticator and authorizer that simulates the same authentication and authorization logic as in the security C code example.
-- 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()
Output:
HELLO WORLD!
relative path= xyz
absolute path= /test/xyz
local serverfunctions = {
msg1 = function(cid,...),
msg2 = function(cid,...)
}
The parameters received are the cid (Connection ID) plus any arguments sent by the client.
Example:
local unis, multis = eh:setintf("chat", "chat", serverfunctions)
unis:clientmsg1(cid,param1,param2)
multis: clientmsg1(param1,param2)
The unicast and multicast interfaces are the bindings for the client interface functions. You can call any method in the client interface. The unicast interface takes an initial cid which specifies the client you want to send the message to.
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.
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.
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.
Example:
require"wfs"
fs=ba.create.wfs("fs", ba.openio("root"), ".LOCK")
dir:configure{tmo=2*60*60,helpuri="/my/help/",logouturi="/logout/"}
fs:insert()
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 is specifically designed for the threading design in the Barracuda Embedded Web Server. Calling ba.exec allows other threads to execute while the thread calling ba.exec waits for the child to terminate. Calling io.open blocks all other threads until the child process terminates.
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().
The I/O interface provides a common set of functions for working with files stored in various media types such as data stored on a standard file system, ZIP files, and network files. The Lua I/O interface provides an interface to the C side implementation for the IoIntf. The C startup code can initialize and provide any number of I/O interfaces to the Lua code. The I/O interfaces provided by the C startup code is available to the Lua code via ba.openio(ioname). New I/O interfaces can be created by calling ba.mkio(baseio, path).
The DiskIo and the NetIo are delivered as C code and must be compiled, linked, and installed by the C startup code. Exceptions are the Windows and Linux release, which are delivered with the DiskIo precompiled. The wfs and the lspappmgr C source code examples show how to install the I/O interfaces and make the I/O interfaces available to Lua code. See the C side IoIntf introduction for detailed information.
for name,io in pairs(ba.io()) do print(name, " : ", io:resourcetype()) 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 "apps" as the base directory for the new I/O:
local appsio = ba.mkio(ba.openio("disk"), "apps/")
-- Create a ZipIo by opening a ZIP file in the "disk" (DiskIo) root directory:
local zio = ba.mkio(ba.openio("disk"), "myzip.zip")
-- Create a new NetIo by using the URL http://192.168.1.100/fs/c/apps/ as the base path.
-- Note: Requires that the wfs is running on 192.168.1.100.
local netio = ba.mkio(ba.openio("net"), "http://192.168.1.100/fs/c/apps/")
-- Open the ZIP file c:\myzip.zip on a computer, where the wfs is running, with IP address 192.168.1.100:
local netzipio = ba.mkio(ba.openio("net"), "http://192.168.1.100/fs/c/myzip.zip")
The NetIo is similar to a network file system and makes it possible for the server to access resources on another Barracuda server.
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 |
|---|---|
| invalidname | Name not accepted by file system |
| notfound | Not found |
| exist | Resource exists |
| enoent | Directory not found |
| noaccess | Resource locked by file system |
| notempty | Directory not empty |
| ioerror | Some kind of I/O error |
| nospace | No space left on device |
| mem | No memory for operation |
| buftoosmall | Buf too small |
| noimplementation | Not implemented |
| noaeslib | AES not enabled |
| notcompressed | ZIP not compressed |
| ziperror | Unknown ZIP file format |
| noziplib | ZLIB not installed |
| aesnosupport | Unknown AES encryption |
| nopassword | Resource is encrypted, but no password provided |
| wrongpassword | Wrong password provided |
| aeswrongauth | Wrong password or file compromised |
| aescompromised | AES protected file compromised |
| invalidsocketcon | Invalid socket con |
| gethostbyname | Gethostbyname failed |
| bind | Bind failed |
| socketwritefailed | Socket write failed |
| socketreadfailed | Socket read failed |
| malloc | Malloc failed |
| alreadyinserted | Already inserted |
| toomuchdata | Too much data |
| pagenotfound | Page not found |
| iscommitted | Is committed |
| invalidparam | Invalid param |
| mixingwritesend | Mixing write send |
| toomanyincludes | Too many includes |
| toomanyforwards | Too many forwards |
| includeopnotvalid | Include op not valid |
| cannotresolve | Cannot resolve |
| cannotconnect | Cannot connect |
| invalidurl | Invalid URL |
| invalidresponse | Invalid response |
Parses and returns the Lua Server Page as a Lua chunk. The returned string can for example be loaded by function loadstring().
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 IO object, page table, and application table.
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 = loadstring(data)
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
?>
-- Seed SharkSSL by connecting to remote seed generator
ba.thread.run(function() -- Run the blocking HTTP request in a separate thread
-- Create a HTTP object using the low level HTTP library.
local http=require"httpc".create()
-- Send request and required parameters
-- See http://www.random.org for more information
http:request{
method="GET",
url="http://www.random.org/integers/",
query={
num=1,
col=1,
min=1,
max=1000000000,
base=10,
format="plain",
rnd="new"
}
}
if http:status() == 200 then
local rnd=http:read"*a"
if rnd then
rnd=tonumber(rnd)
if rnd then
-- We received a number
-- Seed SharkSSL
ba.rndseed(rnd)
end
end
end
-- Seed using millisec clock.
-- The HTTP response time creates a random delay.
ba.rndseed(ba.clock())
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())
local s = request:session(true) local id = s:id(true) -- Returns a string suitable for public use assert(id == ba.session(id,request):id(true))
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
Install a Lua error handler. The error handler is called if a Lua script should cause an exception. The callback function gets one argument, the error message. Note: All errors are sent to the Barracuda HttpTrace library, thus a Lua error handler is typically not needed during debugging.
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.
local timer = ba.timer(
function()
-- Do something
end
)
-- One-shot timer example: function timeout() -- do something end -- Create a timer. The timer object must be global. t = ba.timer(timeout) -- Set the timeout to one second. t:set(1000)
-- Interval timer that never stops. 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)
| 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 function myDirectoryService(_ENV,relpath)
if authenticator:authenticate(request, pathpath) 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={'url1','url2','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.
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.
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 IO (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 blua, is found in the SDK's bin directory.
The executable blua is an enhanced 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.
The extended executable includes: