Lua JSON-RPC Services

The Barracuda Server's JSON-RPC implementation makes it extremely easy to export Lua functions in the server as JSON-RPC Web Services. The services can be used by any compliant JSON-RPC client stack, including the Barracuda Server JSON-RPC client JavaScript stack.

Exporting Lua Functions

The Barracuda Embedded Web Server can provide an unlimited number of JSON-RPC services. A JSON-RPC service can be installed at any URL location in the server. Any LSP page can be used as an entry point for JSON-RPC, but you must not generate any content in the page. The JSON-RPC object automatically generates the required response.

<?lsp
 -- Lua RPC code here. You must not have anything outside <? And ?>
?>

A JSON-RPC service in Barracuda consists of one or several objects. Each object in turn consists of one or several methods. This is equivalent to an object oriented design where one typically calls a method in an object, example: math.subtract(10,5)

As an example, assume we want a math JSON service. The math service provides a math object and methods such as add, subtract, etc..

<?lsp
-- (1) Load the JSON-RPC service
local jsonrpc=require"jsonrpc"


-- (2) Our math.add Web-Services function
local function add(a,b)
   return a + b
end

-- Our math.subtract Web-Services function
local function subtract(a,b)
   return a - b
end

-- (3) Create the math web service
local mathService = jsonrpc.new("A basic math service",
                         {
                            math={ -- (4) The math Web-Services object
                               add=add,
                               subtract=subtract,
                            }
                         })
-- (5) Execute the web service
mathService:execute(request, response)
?>

(1) Load the JSON-RPC service

The following code snippet shows how to load the JSON-RPC stack:

local jsonrpc=require"jsonrpc"

The file json.rpc contains the Lua JSON-RPC stack. The Lua JSON-RPC stack must be made available such that the VM can load this code. The Lua JSON-RPC code is distributed with the Barracuda Server SDK.

(2) Web-Services functions

JSON-RPC functions are standard Lua functions. The JSON-RPC library automatically decodes the JSON data sent from the client. One can have any number of arguments, and the argument can be of any type. JSON objects are converted to Lua tables.

One can also have a dynamic number of arguments. For example, we can change the "add function" to work with any number of arguments. The following example returns the summation of all its arguments:

local function add(...)
   local s = 0
   for i, v in ipairs{...} do
      s = s + v
   end
   return s
end

We are not doing any type checking on the arguments in (2). The Lua VM may throw an exception if the two arguments cannot be added.

You can add argument validation to the code as follows:
local function add(a,b)
   if type(a) ~= "number" then
      error("The first argument is not a number")
   end
   if type(b) ~= "number" then
      error("The second argument is not a number")
   end
   return a + b
end

Function "error" generates an exception and the exception message is sent back to the client. Exceptions are thrown all the way back to the client. For example, our JSON-RPC JavaScript library throws an exception if calling a server side function fails. See the JSON-RPC client JavaScript stack for more information.

(3) Create the math web service

We now create a JSON-RPC object by calling jsonrpc.new. The function takes two arguments, a string and a table. The Lua table is converted by the JSON-RPC implementation and made available to clients as a Service Description object. Clients supporting introspection, such as our JSON-RPC client JavaScript stack, can automatically provide the functions to the client web-services implementation.

(4) The math Web-Services object

The table must contain at least one sub-table. We mentioned above that a JSON-RPC service in Barracuda consists of one or several objects, where each object in turn consists of one or several methods. This is not an option; you cannot just export the function(s). We have only one object, the math object. The math table contains two functions, add and subtract. A client can now call math.add and math.subtract.

(5) Execute the Web-Service

We have so far just declared the functions and created the JSON-RPC object "mathService". We must now execute the Web-Service. We do this by calling the execute method. The execute method takes two arguments, the request object, and the response object. The request and response object are used by the JSON-RPC stack for reading/parsing the request, and sending the response (return value) from your service function. The client JSON-RPC stack cannot parse the response data if you generate any response data outside of the <? and ?>.

Optimizing the JSON Web-Services

You may have noticed that we created a JSON-RPC object (3) every time the client calls one of our Web-Services functions. This is great during development as the LSP compiler recompiles the LSP page automatically if the page has changed. You can optimize the code when you have completed the development by moving all code, except for (5) to the application object. The application object is created when your LSP application starts. The VM executes the .preload script, if found, when you load an LSP application. You can simply move the code into this file when you are done with the development. The LSP script will then only contain one line of code.

<?lsp   app.mathService:execute(request, response)  ?>

Notice that we must now reference the mathService object via the application object.

Directory JSON-RPC Services

In addition to using LSP pages as JSON-RPC services, a directory can be used:

local jsonrpc=require"jsonrpc"

local function add(a,b)
   return a + b
end

local function subtract(a,b)
   return a - b
end

local mathService = jsonrpc.new("A basic math service",
                         {
                            math={
                               add=subtract,
                               subtract=subtract,
                            }
                         })

local function webservice(dir,func,path)
   if path ~= "" then -- We have only one service
      response:senderror(404)
   else
      mathService:execute(request, response)
   end
end

 -- Create directory: "web-service"
dir = ba.create.dir("web-service", webservice)
ba.dirtop():insert(dir) -- Insert into first root directory