The Auxiliary Lua APIs

The auxiliary Lua APIs provide additional functionality including HTTP(S) client libraries, a mail (SMTP) client, (secure) sockets, and SSL certificate management.

The auxiliary Lua APIs are not part of the standard libraries and must therefore be compiled and integrated with your build. The source code for these APIs can be found in the xrc directory. The wfs and the lspappmgr example servers include all of the auxiliary API's. Refer to one of these two examples for how to include and initialize the optional auxiliary Lua APIs.

In addition to the C code in the xrc subdirectories, the xrc/lua and subdirectories include Lua code that must be included in your build. The makefiles for the wfs and the lspappmgr assemble these files into the ".lua" directory in the lsp.zip ZIP file. The lsp.zip file is integrated into the example's executable and made available as the default I/O by the C startup code. The Lua function "require" is designed such that it searches for Lua modules in the ".lua" directory. As an example, an LSP page calling 'require("mail")' loads mail.lua and other required Lua modules from this directory. In other words, calling "require" unzips the files from the integrated ZIP file and loads the files as Lua modules.

Mail (SMTP) Client Library

The smtp library provides functionality to send e-mail messages. The smtp library supports authentication and secure SMTP. The implementation conforms to the Simple Mail Transfer Protocol, RFC 2821.

The SMTP library provided with the Barracuda Embedded Web Server is an enhanced version of the LuaSocket SMTP library, which also supports SSL/TLS and STARTTLS email encryption. See email encryption protocols for more information.

The SMTP library is designed for blocking sockets and cannot be used with our asynchronous socket API. If you are not using the SMTP library from an LSP page, make sure you run the smtp code in the context of the Lua Thread Library. See threading for more information.

Simplified SMTP Client Library

We have created a wrapper library that simplifies the use of the LuaSocket SMTP library. You can choose to directly use the LuaSocket SMTP library or the simplified SMTP library provided by Real Time Logic. The simplified SMTP client library does not require that you have detailed knowledge of MIME encoding or knowledge in how to use LTN012, Filters sources and sinks. The following documentation is for the simplified SMTP library. See the LuaSocket SMTP documentation if you prefer to use the more technical API.

The Simplified SMTP client library, called "mail", makes it easy to send text and html emails. The library also makes it easy to embed images for the html and to add file attachments. The following example shows how to send an email using a standard smtp server.

require "socket.mail" -- Load mail lib

local mail=socket.mail{server = "smtp.example.com"} -- Create a mail object

-- Send one email
local ok,err=mail:send{
   subject="Hello",
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   body=[[
     Hello Alice,
     This is a test.
   ]]
}

Simplified SMTP Client Library API

socket.mail{
  server = string,
  [port = number,]
  [user = string,]
  [password = string,]
  [shark=SharkSSL object,]
  [starttls=true]
}

Creates a simplified SMTP client object. The socket.mail function takes a table as argument with the following required and optional attributes:

mail:send{
  subject=string,
  from=string,
  to=string|table,
  [replyto=string,]
  [cc=string|table,]
  [bcc=string|table,]
  [txtbody=string|fp,]
  [htmlbody=string|fp,]
  [htmlimg=table,]
  [attach=table,]
  [encoding="QUOTED" | "B64" | "8BIT" | "NONE"]
  [charset=string]
}

In the above, string|fp means either a string with text/binary data or a file pointer. A file pointer can be from opening a file using the standard Lua I/O function or from a Barracuda I/O. Examples: fp=io.open("/path/img.gif"), fp=ba.openio("disk"):open("/path/img.gif").

Email addresses:
Email addresses can be any of the following:

Mail Examples

Creating the mail object

The following example configures and creates a mail object for the secure Google SMTP server:

local mail=socket.mail{
   shark=ba.create.sharkssl(), -- Use TLS
   server="smtp.gmail.com",
   user="YOUR GOOGLE EMAIL ADDRESS",
   password="YOUR GOOGLE PASSWORD",
}

Notice in the code above how we create a SharkSSL object with no Certificate Authority (CA). Though this is valid code, the Google SMTP server will not be validated and a man in the middle attack could compromise the connection. You should therefore install known CA's in the SharkSSL object.

The following example configures and creates a mail object for the STARTTLS version of the hotmail SMTP server:

local mail=socket.mail{
   shark=ba.create.sharkssl(), -- Use TLS
   starttls=true, -- Start as a normal socket and then later upgrade to TLS
   server="smtp.live.com",
   user="YOUR HOTMAIL EMAIL ADDRESS",
   password="YOUR HOTMAIL PASSWORD",
   port="587",
}

A secure SMTP server may require clients to provide a certificate. You can install a valid certificate in the SharkSSL object if your SMTP server requires client certificate authentication.

Sending emails

The following example sends a HTML email to Alice by opening and reading the file "emails/alice.html":

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   htmlbody=io:open"emails/alice.html"
}

It is good practice to embed a plain text version of the HTML email.The following example sends a combined text and HTML email:

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   txtbody="This is the text body",
   htmlbody=[[
        <html>
          <body>
            <h1>This is the html body</h1>
          </body>
        </html>
   ]]
}

The following email shows how to send a combined text and html image. The html includes an embedded image. The email also includes one attachment:

local ok,err=mail:send{
   from='Bob <bob@example.com>',
   to='Alice <alice@example.com>',
   subject="Hello",
   txtbody="This is the text body",
   htmlbody=[[
        <html>
          <body>
            <h1>This is the html body</h1>
            <img src="cid:the-unique-id" alt="Text shown by text only clients">
          </body>
        </html>
   ]],
   htmlimg = {
      id="the-unique-id",
      name="logo.gif",
      source=io:open"logo.gif"
   },
   attach={
      description="A document",
      name="my-document.pdf",
      source=io:open"my-document.pdf"
   }
}

Notice how the image source attribute is set to a unique ID. You must create a special HTML file where each embedded image has its own unique ID.

HTTP(S) Client Libraries

The HTTP client libraries provide a high level easy to use API, a low level API, and the raw API provided by the C code. It is suggested that you do not directly use the raw API -- i.e. directly use the Lua bindings. See the documentation in lhttp.c if you prefer to use the low level raw API. This documentation focuses on the high level and the low level APIs.

The HTTP(S) Client Libraries implementation conforms to the HTTP/1.1 standard, RFC 2616.

Important information:
The HTTP client libraries use blocking socket calls and should therefore run in the context of the Server HTTP Thread Pool or the Lua Thread Library. See threading for more information.

The HTTP high level and low level APIs are loaded by calling:

require"http"

High Level HTTP(S) Client Library

The high level API makes it easy to upload and download data, send HTTP POST requests, and communicate with a server using JSON.

http.create.managed([op])

Returns a high level HTTP(S) client library object. Parameter op is the optional configuration table. The options set when creating the managed object can be overridden when calling the managed methods.

The option table:
  • persistent=bool -- Set/disable persistent HTTP1.1 connections. The Default is to enable persistent connections.
  • method = string -- GET, POST, etc.
  • header = table -- A table with key value pairs. Include additional HTTP headers.
  • query = table -- A table with key value pairs. Include url encoded data in the query component of the request URL.
  • user = string -- Authenticate using username.
  • password = string -- Authenticate using passwor.
  • shark = SharkSSL object -- The SharkSSL object must be set when using secure connections -- i.e. URLs starting with https://
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Force the use of IPv6 address translation. Note, the server must have been compiled with IPv6 support.
  • proxy = string -- Use a HTTPS or SOCKS5 proxy with IP address or domain name "proxy".
  • proxyport = number -- Proxy port number defaults to 8080 for HTTPS proxy and 1080 for SOCKS5 proxy.
  • socks = bool -- Set to true if using a SOCKS5 proxy. The default is to use HTTPS proxy by using HTTP CONNECT.
  • proxyuser = string -- Proxy authenticate using username.
  • proxypass = string -- Proxy authenticate using password.

High Level HTTP Methods:

The methods associated with the object returned by http.create.managed()

All client HTTP methods sending or receiving data return a result on success and nil followed by an error code on failure. The error code is a short text string if the error is caused by socket failure or the HTTP server's response code if the failure is caused by the server sending a HTTP error code.

managed:timeout(ms)

Set the HTTP timeout value to ms milliseconds. The default is 20000, i.e. 20 seconds.

managed:stat(url [,op])

Returns a table of attributes for the named url -- the resource . If the resource does not exist, 'nil' followed by an error code is returned. The fields of the table are named as follows:

This function is by default using the HTTP method "HEAD". You can change the HTTP method by setting op={method="method-type"}

Examples:

managed=http.create.managed()
tab,err=managed:stat("http://myserver/myresource")
tab,err=managed:stat("http://myserver/myresource",
                     { method="GET", query={foo="bar"}})
managed:post(url,tab[,op])

Send a HTTP POST request to a server. This function emulates a HTML form submit in a browser.

The following example is sending url encoded data in the query component of the URL and in the body of the message.

managed=http.create.managed({shark=sharkobj})
ok,err=managed:post("https://myserver/myresource", {foo="Hello"},
                    {query={bar="world"}})
managed:upload(url,io,name[,op][,func])
managed:upload(url,fp,[size][,op][,func])

Upload a file to a server. This function is by default using the HTTP method "PUT". You can change the HTTP method by setting op={method="POST"}

The function can be used in two modes:


Example using mode 1:

ok,err=managed:upload("http://myserver/myuploadresource",
                      ba.openio("disk"), "mypath/firmware.bin")

The HTTP library is monitoring response data from the server while the upload is in progres. The upload is automatically terminated if the server sends an error response. It is for this reason not necessary to do the typical one byte test upload prior to uploading the actual data to test if the server accepts the upload. HTTP 100-continue messages sent from the server are silently consumed.

managed:download(url,io,name[,op][,func])
managed:download(url,fp,[,op][,func])

Download a file from a server and save the file. The function can be used in two modes:

Example using mode 2:

fp=ba.openio("disk"):open("myfile.html","w")
ok,err=managed:download("http://myserver/myfile.html", fp)
-- fp closed by managed:download
managed:json(url,data [,op])

Load JSON-encoded data from the server using a GET HTTP request. This function emulates the jQuery.getJSON() function in the JQuery JavaScript library.

Example:

-- Connect to a Web-File-Manager in a Barracuda server
-- and request a JSON directory listing.
local t,err = x:json("http://localhost/fs/", {cmd="lj"})
if t then
   for _,r in ipairs(t) do -- Iterate all resources
      response:write("name=",r.n,", size=",r.s < 0 and "DIR" or r.s,
                     ", date=",os.date("%c",r.t),"<br>")
   end
else
   response:write(err)
end
managed:url()

Returns the url and query data as a table for the current connection. The url and query data may be different on server response than the values set when connecting since the server can send redirect responses. The redirect response is internally managed by the high level HTTP client library.

managed:certificate()

Returns the peer's certificate chain as a table if the connection is secure.

managed:close()

Close the connection.

Low Level HTTP(S) Client Library

http.create.basic([op])

Returns a low level HTTP client object.

The option table:
  • persistent=bool -- Set/disable persistent HTTP1.1 connections. The Default is to enable persistent connections.
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Force the use of IPv6 address translation. Note, the server must have been compiled with IPv6 support.
  • proxy = string -- Use a HTTPS or SOCKS5 proxy with IP address or domain name "proxy".
  • proxyport = number -- Proxy port number defaults to 8080 for HTTPS proxy and 1080 for SOCKS5 proxy.
  • socks = bool -- Set to true if you are using a SOCKS5 proxy. The default is to use HTTPS proxy by using HTTP CONNECT.
  • proxyuser = string -- Proxy authenticate using username.
  • proxypass = string -- Proxy authenticate using password.

Low Level HTTP Methods:

The methods associated with the object returned by http.create.basic()

basic:timeout(milliseconds)

Set the number of milliseconds before connect, read, and write times out. The default is 20000 i.e. 20 seconds. A value of zero waits indefinitely.

basic:request(op)

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some form of socket error occured. The number is only returned if a proxy is installed. The number corresponds to the HTTP status. Example: 407 Proxy Authentication Required.

The option table:
Required attributes:
  • url = string -- URL to the server resource.
  • method = string -- GET, POST, etc.
  • shark = SharkSSL object -- The SharkSSL object is required when using secure connections -- i.e. URLs starting with https://
Optional attributes:
  • header = table -- A table with key value pairs. Include additional HTTP headers.
  • query = table -- A table with key value pairs. Include url encoded data in the query component of the request URL.
  • user = string -- Authenticate using username.
  • password = string -- Authenticate using password.
basic:status()

Returns the HTTP server response code.

basic:header()

Returns a table with the HTTP header key/value pairs.

basic:headerpairs()

Returns an iterator that will traverse all the HTTP header key/value pairs.

basic:cookie()

Returns an iterator that will extract and traverse all cookies sent by the server.

The server may send multiple "Set-Cookie" headers:

   Set-Cookie: name=value [; expires=date] [; path=path] [;
               domain=domain] [; secure]

The iterator returns the cookie name, value, and an optional table. The table is returned if the cookie returned by the server contains more than just the cookie name. The table provides {key, value} pairs, where the keys can be one of the following: expires, path, domain, and secure.

   for name,value,t in x:cookie() do
      print("cookie name:", name, ",value", value)
      if t then
         for k,v in pairs(t) do
            print(k,v)
         end
      end
   end
basic:read(size)

Read size bytes.

size is one of:
  • "*a" for all data.
  • Number(n) for reading n bytes.
  • Not provided. Makes the read function silently consume and discard response data.
basic:write(n)

Write n bytes.

Returns true on success.
On error: returns nil followed by an error code or a number. The error code is provided if some kind of socket error occured. The number is the HTTP response code. For example, if uploading data fails and the server sends a HTTP response code while the client is uploading, the uploading is aborted immediately, and the HTTP response code is returned to the caller.

The caller can set a "Expect: 100-continue" header prior to uploading data, but this is not necessary since the write function asynchronously detects if the server denied the upload. Any 100 continue messages sent by the server are silently consumed by the write function.

basic:url()

Returns the url and query data as a table for the current connection. The url and query data may be different on server response than the values set when connecting since the server can send redirect responses. The redirect response is internally managed by the low level HTTP client library.

basic:certificate()

Returns the peer's certificate chain as a table if the connection is secure.

basic:close()

Close the connection.

TCP Socket API

The TCP socket API provides easy to use functions for creating client and server TCP connections. The socket API is integrated with our own SSL stack SharkSSL™, thus making it very easy to create secure custom protocols. The socket API enables interceptions of HTTP client and server connections at any point, making it possible to morph a HTTP(S) connection into a HTTP(S) tunnel, which can be used for (secure) custom protocols that can bypass proxies and firewalls.

The socket API can be used in three modes:

Our socket API removes the complexity typically involved in asynchronous socket design by making asynchronous socket send and receive functions appear to be blocking. This is possible thanks to the Lua scripting language, which implements a lightweight threading model called coroutines. Each coroutine has its own stack, and our socket API automatically schedules the coroutines when reading from or writing to non blocking sockets.

The Barracuda socket API is not identical to the standard LuaSocket, but a compatibility layer is included that makes it possible to use code designed for the Lua socket API such as the mail (SMTP) client.

Functions

ba.socket.bind(port [,op])

Creates and returns a TCP server object bound to a local port, ready to accept client connections.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to all interfaces.
  • ipv6 = bool -- Force the use of IPv6 address translation if intf is a name, not an IP address. Note, the server must have been compiled with IPv6 support. One can typically open two connections on the same port and interface if one is an IPv4 address and the other is an IPv6 address.
  • shark = SharkSSL object -- Accept SSL connections.
ba.socket.connect(address, port [,op])

Creates and returns a TCP client object connected to a remote host/address at a given port.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to any interface.
  • ipv6 = bool -- Force the use of IPv6 address translation for host names. Note, the server must have been compiled with IPv6 support.
  • shark = SharkSSL object -- Connect using a SSL connection. You must connect with a server object that accepts secure connections.
ba.socket.req2sock(request)

This function, which can only be called from an LSP page or a directory function, extracts the active socket connection from the request object. This function makes it possible to morph a HTTP(S) connection into a HTTP(S) tunnel, which can be used for (secure) custom protocols. The LSP page response object cannot be used after this function is called, thus if you want to easily send HTTP response headers, the response must be flushed prior to calling this function.

ba.socket.http2sock(httpclient)

This function extracts the active socket connection from a basic HTTP client object. The function is typically called after sending the request, and calling basic:status(). The request must be to an LSP page that calls function ba.socket.req2sock.

The following example connects to a web server running on localhost:80, extracts the socket connection from the HTTP client object, and starts an asynchronous coroutine that reads data until end of stream. End of stream is assumed when the receive function receives no data for 2 seconds.

require"http"

local function recData(s)
   local data,err
   data=true
   while data do
      -- s:read returns nil when no data received for 2 seconds
      data,err = s:read(2000)
      trace(data) -- Print response data to trace.
   end
   if err then trace("Read failed", err) end
   -- Socket is closed on return
end

local c = http.create.basic()
c:request{url="http://localhost",method="GET"}
local s,err=ba.socket.http2sock(c)
if s then
   s:event(recData, "s")
end
ba.socket.toip(address[,ipv6])

Lookup address, by using DNS, and translate address to an IP address. Force the use of IPv6 address translation if the optional parameter is true. Note, the server must have been compiled with IPv6 support.

This function is useful when using asynchronous sockets since DNS lookup may take some time. Lua code can be designed such that the address is resolved by code that is allowed to block. The address can be cached such that the non blocking coroutines can use the IP address when connecting to peers.

ba.socket.event(function[, args])
Run function as a non blocking fully asynchronous socket coroutine. The function is called with the given optional arguments. The Lua function is initially run in the context of the C thread calling socket.event(). The Lua function is resumed in the context of the thread running the Socket Event Dispatcher as soon as the Lua function calls the first socket function. All socket operations performed in the function will appear to be blocking, but most functions are asynchronous behind the scene. The function that may block is ba.socket.connect, when using a domain name that must be resolved to an IP address by DNS lookup. See asynchronous sockets for more information.

Socket Object Methods

Objects returned by ba.socket.bind(), ba.socket.connect(), ba.socket.req2sock(), and ba.socket.http2sock() have the following methods associated with them:

socket:upgrade(shark)

Upgrade a standard socket to a secure socket. The method is typically used by protocols that connect in non-secure mode and then later upgrade to secure communication. STARTTLS is an example of a protocol that upgrades to SSL/TLS.

  local s=ba.socket.connect(myaddress, myport)
  -- Send handshake data
  s:upgrade(ba.create.sharkssl())
socket:event(function [, "r|s" [, args]])
socket:event("r|s")

Enable or disable socket send (s) or receive (r) events. The socket event is enabled if the Lua function is provided as the first argument and disabled otherwise. This method is similar to function ba.socket.event(), but allows the caller to bind or connect a socket before enabling socket events.

The provided function (parameter one) is immediately resumed, with the first parameter being the socket, and subsequent parameters being the optional "args" passed into ba.socket.event.

The provided Lua function can be run as coroutine in two modes:

Calling socket("s") disables non-blocking send mode. Calling socket("r") stops the coroutine.

Parameter two must be "r" for listen sockets i.e. sockets returned by ba.socket.bind(). A listen socket can only receive "accept" events.

See asynchronous sockets for more information.

socket:read([timeout])

Reads data from socket. The function blocks indefinitely if timeout (in milliseconds) is not provided. The function automatically yields if the caller is a socket coroutine and no data is available on the socket.

socket:write(data [, i [, j]])

Send data. The optional arguments i and j work exactly as the standard string.sub Lua function to allow the selection of a substring to be sent.

A blocking socket blocks if the TCP queue is full. A coroutine calling this method in fully asynchronous non blocking mode automatically yields if the TCP queue is full.

socket:accept()

This method, which can only be called by a "bind type" coroutine, waits for remote connections on the server socket object and returns a socket object representing that connection.

socket:certificate()

Returns the peer's certificate chain as a table if the connection is secure.

socket:getpeername()

Returns information about the far side of the TCP connection. Returns a string with the IP address of the peer.

socket:getsockname()

Returns information about the local side of the TCP connection. Returns a string with the local IP address.

socket:setoption(option [, value])

Currently supports one option only: socket:setoption('tcp-nodelay', true|false)

socket:close()

Close the socket connection.

Socket Design

Let's revisit the diagram from the introduction:

Blocking sockets should run in the context of the Server HTTP Thread Pool or the Lua Thread Library to prevent starvation of the Barracuda Socket Event Dispatcher, the timer, or other events.

Socket code called directly in a LSP page or in a Lua directory function always runs in the context of the server thread pool, unless the Barracuda server is configured for single threaded mode. Socket code called in a LSP page should typically be designed to block such that the LSP page can wait for the socket result. When the socket call is complete, the result can be sent to the web server client waiting for the LSP page to complete.

Socket code initiated from the server startup script (.config), the EventHandler, the timer, or any other user defined events should either run in the context of the Lua thread pool or be non blocking - i.e. asynchronous.

Executing socket code in a thread in the Lua thread pool is suggested if you only have one to a few concurrent socket calls since designing asynchronous sockets require that you design the code to not starve the Barracuda Socket Event Dispatcher.

The following example shows how to delegate socket actions on a blocking socket to the Lua thread pool. The example showcases a basic secure web-server listening for SSL connections on port 9443. You can easily test the code below by copying and inserting the code into a LSP page. After running the LSP page one time, a new listening socket is installed and ready to accept connections on port 9443. After running the LSP page one time, navigate to https://localhost:9443.

--Create alias
local fmt=string.format

-- Web server function running in the context of the Lua thread pool.
-- s is the blocking socket.
local function webServer(s)
    -- Read request
   local data,err = s:read(2000)
   -- Ignore request -- i.e. discard data.

   -- Send the same response for any URL requested
   local msg = "Hello World!"
   s:write(fmt(
[[HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: %d

%s]], #msg, msg))
   s:close()
end


-- Accept thread waiting asynchronously for client connections
local function acceptCoroutine(s)
   while true do
      -- Method accept yields and resumes when a client connects.
      local s = s:accept(s)
      -- Delegate execution of the new blocking socket to the thread pool.
      -- Notice how we create an anonymous function that keeps the socket as
      -- an upvalue. When the anonymous function starts, it calls function
      -- webServer() passing in the upvalue.
      thread.run(function() webServer(s) end)
   end
end

-- Create a SharkSSL certificate by using the certificate stored in
-- the internal ZIP file.
local iovm = ba.openio("vm")
local certf=".certificate/demo%s.pem" -- Certificate path in ZIP file.
local cert,err=ba.create.sharkcert(
   iovm, fmt(certf,"cert"), fmt(certf,"key"), "sharkssl")

-- Create a SharkSSL server object by using the above certificate
local sharkobj=ba.create.sharkssl(nil,cert,{server=true})

-- Create a secure (SSL) server listen object.
local s=ba.socket.bind(9443, {shark=sharkobj})
-- Run the socket "accept" function.
s:event(acceptCoroutine,"r")

Asynchronous Sockets

Asynchronous sockets are typically used when a high number of concurrent sockets are required. On some platforms, the Barracuda Socket Event Dispatcher can handle thousands of simultaneous connections.

Although asynchronous sockets appear to be blocking by the Lua code using the socket API, the code is under the hood using coroutine.yield to temporarily freeze the calling coroutine if no data is available on the socket. The code is on the C side run in the context of the Barracuda Socket Event Dispatcher, which is responsible for dispatching socket events to the system, including the web-server. It is therefore important to not block the thread for prolonged time as this will have a negative impact on the web-server, especially if the web-server is serving a high number of concurrent connections. For example, calling function ba.sleep() in a socket coroutine is a bad idea.

The Barracuda Socket Event Dispatcher provides a fairly platform independent layer for dispatching socket events. The Socket Event Dispatcher maps to the BSD function "select" on many systems, and on Linux/BSD the dispatcher typically maps to epoll/ Kqueue. However, many of the non BSD TCP/IP stacks in embedded devices are limited in functionality and not all TCP/IP stacks provide non blocking send. On these platforms, function ba.socket.event() does not exist and socket:event() with parameter "s" is not supported. TCP/IP stacks that do not support non blocking send in the Barracuda server are NetX for the ThreadX RTOS and MQX.

Non blocking client example:
local function mySockCoroutine(address,port)
   local socket,err=ba.socket.connect(address,port)
   if socket then
      local ok=true
      while ok do -- Loop while server is nice
         ok,err=s:write"hello"
         if ok then
            local rsp
            rsp,err = s:read()
            if rsp == "bugger off" then
               ok = false -- exit
            end
         end
      end
      if err then
         trace("socket read/write failed:",err)
      end
   else
      trace("ba.socket.connect failed:",err)
   end
   -- Socket is automatically closed when coroutine exit.
end

ba.socket.event(mySockCoroutine, "192.168.1.100", 8080)

In the above example, function mySockCoroutine is started as an asynchronous coroutine. The coroutine connects to a server on IP address 192.168.1.100 and port 8080. The client keeps sending hello messages until the server has had enough.

It should be needless to say that one cannot use response:write in a socket coroutine. Function trace(), as used in the above example, is useful for reporting error messages when executing code that is not running in an LSP page.

Lua Thread Library (Thread Pool)

Let's revisit the diagram from the introduction.

The Lua Thread Library (the thread pool) is seen in the bottom right corner in the above diagram. The thread pool is typically used when running Lua code that is not part of a typical web-server request/response. One can for example design code to run at regular intervals by using a timer and trigger a function to run for a prolonged time in the thread pool.

thread.run(function)

Delegate the execution of function to one of the threads in the Lua thread pool. The execution is queued if no thread(s) are available -- i.e. if the thread(s) are busy executing other functions. The function remains in a FIFO queue until a thread becomes ready.

There are no time limits on the execution time for the provided function. The thread executing the function becomes ready when the function returns (terminates).

See the Lua socket web server example for more information.

thread.configure([maxthreads])

Set or get the maximum number of allowed threads in the thread pool. The default value is one. Threads never terminate, thus setting a higher value followed by a lower value does not reduce the thread pool size. Threads are not created unless there are no available threads when function thread.run() is called.

SharkSSL

(Secure SSL/TLS communication management)

SharkSSL is an extremely compact SSL/TLS stack, supporting the SSLv3.0, TLSv1.0 and TLSv1.1 cryptographic protocols. SharkSSL provides security for network communications, such as for securing HTTP and SMTP communication. The SharkSSL stack supports both client-side and server-side connections, enabling the client to confirm the server's identity and vice-versa.

When using secure communication such as secure sockets or the HTTP library in secure mode, a SharkSSL object is required. The various API's such as the HTTP client library and the socket library take an option table as argument. This table must have the attribute "shark" set to an instance of SharkSSL when using secure communication.

ba.create.sharkssl(certstore,sharkcert [,op])

Create a SharkSSL object. Parameter certstore, the CA list, is an object created with ba.create.certstore(), parameter sharkcert, the certificate and key, is an object created with ba.create.sharkcert(), and op is an optional configuration table.

Typical use cases:
  1. Create a client SharkSSL object that verifies the server using the supplied CA
    clientObj=ba.create.sharkssl(certstore)
  2. Create a client SharkSSL object that accepts any server, including self signed certificates.
    clientObj=ba.create.sharkssl()
  3. Create a standard server object that does not require client certificates.
    serverObj=ba.create.sharkssl(nil, sharkcert, {server=true})

The above use cases is the typical web client and server configuration.


The option (op) table:
  • server=string -- String is one of "client" or "server". The SSL/TLS protocol is a client server protocol. You must select the correct protocol for client and server connections.
  • insize=number -- The internal SharkSSL start "read" buffer size. The size automatically grows if needed. The default value is 4096 bytes.
  • outsize=number -- The internal SharkSSL "write" buffer size. The default value is 4096 bytes.
ba.create.certstore()

Create a Certificate Authority Store. A Certificate Authority or CA for short is an entity that issues digital certificates for use by other parties. In layman's terminology, a CA is like a certified (government) passport issuer. A CA is typically used by a client SharkSSL object, but can also be used by a server SharkSSL object if the server requires client certificate validation.

The objects returned by ba.create.certstore() have the following method associated with them:

addcert(io,name)
addcert(data)

This function adds a certificate in PEM or p7b format to the CA store. A convenient way to get CA's is to export the certificates from a browser in PEM or p7b format. The p7b format is a container format that can contain many CA's.

The method can add certificates in two modes:

  • Mode 1: Provide a Barracuda I/O interface (io) and the path+name (name) to the location of the CA.
  • Mode 2: Load the file data manually and insert the data.
ba.create.sharkcert(io, certfile, keyfile[, password])
ba.create.sharkcert(certdata, keydata[, password])

A SharkSSL certificate is a combination of the certificate (the public part) and the certificate key (the private part).

The function can create a SharkSSL certificate in two modes:

The password is an optional argument that must be provided if the key is encrypted.

Barracuda Web Server Listen Object

ba.create.servcon(port [,op])

Create a Barracuda Web Server connection listen object and install the object in the Socket Event Dispatcher. The web-server listen object(s) are typically installed by the C startup code, but can optionally be installed by the server startup script (.config). The benefit of having Lua code manage the objects is that it is easier to configure the server listen objects.

See the Lua web-server startup script trunk/examples/wfs/lsp/.config in the Barracuda Embedded Web Server SDK for an example on how to dynamically detect free server port numbers and configure multiple secure and non-secure server connection objects.

For security reasons, objects created with this function are self referencing, which means they will not be collected by the garbage collector if you do not keep a reference to these objects. The Barracuda Socket Event Dispatcher cannot operate without at least one connection. If you close all listen objects, the Socket Event Dispatcher will eventually run out of active connections and malfunction. It is for this reason vital that you keep at least one listen object active at all times.

The option table:
  • intf = string -- Bind to interface-name/IP-address. The default is to bind to all interfaces.
  • ipv6 = bool -- Force the use of IPv6 address translation if intf is a name, not an IP address. Note, the server must have been compiled with IPv6 support.
  • shark = SharkSSL object -- Accept SSL connections.

You can open any number of server connection objects as long as the combination of all of the following is unique: port number, interface, and protocol version (IPv4/IPv6). This means that you can have many server connection objects listening on port 80 if you have more than one interface and/or protocol version.

Objects returned by ba.create.servcon() have the following methods associated with them:

servcon:setport(port [,op])

Change the port number and configuration options in an active server connection object. The parameters are identical to the parameters for function ba.create.servcon().

The function is internally creating a new connection object. The original connection is closed if the new object is created successfully. This function differs from closing an active connection and then creating a new object by leaving any active "accepted" socket connections from the original server connection running.

servcon:close()

Close the active server listening object, remove the listening object from the Socket Event Dispatcher, and remove the self reference on the object. Extreme caution must be taken to not close all server listen objects as this will make the Socket Event Dispatcher malfunction.

forkpty

The forkpty library provides a combined fork, exec, and child process pseudo-terminal. The code is currently available for Linux only such as embedded Linux.

The code can be used for executing and managing Linux executables such as "ls", "kill", etc, or be used as a base for designing a web shell. The forkpty library provides a much more advanced child process management than the more basic function ba.exec

pty,err=ba.forkpty([function,] prog [, args...])

Starts program prog in a separate process and returns a process object that is used for child process management. The pty process object support full duplex read and write operations.

On success, the function returns a pty child process communication object with 4 methods: read, write, close, and terminate:

  1. pty:read: Read child's stdout.
  2. pty:write: Write to child's stdin.
  3. pty:close: Gracefull kill.
  4. pty:terminate: Immediate kill.

Parameter prog is the process to start such as "/bin/bash", "/bin/login", etc. The optional args are passed to the child process.

The external process can be managed in two modes, polling, or asynchronous read. Polling the pty is typically used when designing web-clients that use AJAX for polling the child process for new data. Asynchronous read is typically used together with the EventHandler, when a web-client is using a persistent asynchronous communication channel with the server. The optional "function" must be supplied for asynchronous read operation. The default mode is designed for polling mode. The examples below assume that you do not provide a function. Asynchronous mode is explained later.

PTY Methods

data, err = pty:read(true|milliseconds)

Read stdout,stderr from the child process.

The function returns nil when the child process exits or if any error occurred.

Note:
pty:read returns nil followed by an error code when the child process exits. You must use method pty:close or pty:terminate to cleanup and to get the child exit code. The error code is "terminated" if the process exited, otherwise the error indicates an error with the pty.

Example:
local data,err = pty:read()
if data then
 -- manage data
elseif err then
  if err = "terminated" then
    -- Terminated
  else
    -- pty error
  end
end
ok,err = pty:write(string)

Send data to the child process's stdin.

ok is nil on error. See pty:read for how to manage the error code.

local status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:close([true])

Return the exit code for the <defunc> child process or gracefully kills the active child process.

The function returns immediately unless the optional parameter is set to true. The function waits for the child to terminate if the optional parameter is set to true.

status is the child process exit code.

Status codes:

if status is nil, the second parameter is the error message. The child process is immediately kill if any errors are detected.

Example:
local status, WIFEXITED, WIFSIGNALED, WTERMSIG = pty:close()
if status == nil then
   print("Failed, msg:", WIFEXITED)
elseif status < 0 then
   print("Running")
elseif status == 0 then
   print("Exited");
else
   print("Exited with error code:", status , WIFEXITED, WIFSIGNALED, WTERMSIG);
end

Note, pty:close() is designed to be called repeatedly until the child terminates i.e. until it returns a value that is not less than zero.

local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()

Close the connection to the <defunc> child process or immediately kill the active child process.

Asynchronous PTY Mode

The PTY object is self-referencing in asynchonous mode. Normally, a reference must be kept to the pty objected returned by ba.forkpty to prevent garbage collection (GC). When in asynchronous mode, a reference is automatically created by ba.forkpty to prevent GC. This reference is valid as long as the coroutine executes.

The following example shows how to use asynchronous mode:

-- The asynchronous receive function keeps reading until the process terminates
function recData(pty)
   while true do
      local d,e = pty:read() -- Read data. Arguments to pty:read are ignored.
      if not d then -- Child process terminated
         break
      end
      trace(d,e)
   end
   trace("\nexit coroutine", e and e or "OK")
   -- coroutine automatically cleans up on exit, but we are allowed to call:
   -- local ok, status [, WIFEXITED, WIFSIGNALED, WTERMSIG] = pty:terminate()
end

local pty,err -- Local variables i.e. we do not need a reference to prevent GC
pty,err = ba.forkpty(recData, "/bin/ls", "-l") -- Execute ls -l
trace(pty,err)

In asynchronous mode, a function must be provided as the first argument to ba.forkpty. This function is executed as a Lua coroutine and is automatically yielded by read when no data is available. The function is resumed when there is data available or if the child process terminates.

All pty:xxx functions operate as normal, except for read when called from within the coroutine.

LuaIo

The LuaIo module implements Lua mapping between the Barracuda I/O interface and Lua. The LuaIo API makes it possible to implement a Barracuda I/O interface using Lua.

The purpose with the LuaIo is to provide filtering on existing I/O's or implement new I/O types in Lua. As an example, a SQL mapped LuaIo could provide WebDAV access to a SQL database. The LuaIo was designed for our BarracudaDrive consumer product, and is used by a specialized BarracudaDrive WebDAV server designed for image uploading. The WebDAV server, by using the LuaIo, automatically scales the uploaded images and creates thumb images for the integrated photo album.

Error handling:

The I/O functions return nil or false on error. Functions returning a table return nil on error and functions returning status information return false on error. In addition to returning nil/false, the functions can optionally return one of the following error codes (string):

Example Lua code:
-- Open resource in read or write mode. mode ="r" or "w"
local function open(name, mode)
   -- open resource, return nil on error.
   -- Resource management functions
   local function read(maxsize)
      -- Return data
   end
   local function write(data)
      -- Return true or false
   end
   local function seek(offset)
      -- Return true or false
   end
   local function flush()
      -- Return true or false
   end
   local function close()
      -- Return true or false
   end
   local res={
      read=read,
      write=write,
      seek=seek,
      flush=flush,
      close=close
   }
   return res -- Table with functions: read,write,seek,flush,close
end

-- Directory iterator
local function files(name)
   -- Find resource, return nil if not found.
   local function read()
      -- Iterate to first/next resource, return true if more files or false
      -- if no more files.
   end
   local function name()
      -- return resource name
   end
   local function stat()
      -- return table with mtime,size, and isdir (true/false)
   end
   return {read=read,name=name,stat=stat} -- Return table with functions
end

-- Return resource information
local function stat(name)
   -- return table with mtime,size, and isdir (true/false) if found or
   -- nil if not found
end

-- Create directory
local function mkdir(name)
   -- Return true on success and false on error
end

-- Remove directory
local function rmdir(name)
   -- Return true on success and false on error
end

-- Remove file
local function remove(name)
   -- Return true on success and false on error
end

-- Table with the Lua I/O callback functions
local iofuncs {
   open=open,
   files=files,
   stat=stat,
   mkdir=mkdir,
   rmdir=rmdir,
   remove=remove
}

-- Create a Lua I/O instance
local io=ba.create.luaio(iofuncs)

-- Install the Lua I/O in any Barracuda resource that takes an I/O
-- interface as argument.