Lua XML-RPC Services


Note: XML-RPC is a legacy technology that predates modern web standards. While it's largely considered outdated today, we include support and documentation for those maintaining or integrating with older systems that still rely on it.

XML-RPC @ Wikipedia

Example:

-- Load the XML-RPC stack
local xmlrpc = require"xmlrpc"

-- Create the XML-RPC service
local xmlService  =  xmlrpc.new("service name", serviceDescription)

The XML-RPC stack parses the received XML into identical Lua objects. The response is handled in a similar manner, the XML-RPC stack automatically translates the response data to XML.

-- Load the XML-RPC stack
local xmlrpc = require"xmlrpc"

-- The "add" XML-RPC service
local function add(a,b)
   return a + b
end

-- The "subtract" XML-RPC service
local function subtract(a,b)
   return a - b
end

-- The Service Description Object used by
-- the XML-RPC constructors
local serviceDescription = {
   math={
      add=add,
      subtract=subtract,
   }
}

-- Create the XML-RPC service
local xmlService = xmlrpc.new("XML-RPC service", serviceDescription)

-- The directory callback function.
-- Service XML-RPC at relative path 'xml-rpc'
local function webservice(_ENV,path)
   if path == "xml-rpc" then
      xmlService:execute(request, response)
      return true
   else -- No other services
      return false
   end
end

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

In this example, the math service is made available at the following URL:
XML-RPC clients: http://localhost/web-service/xml-rpc

Service API

xmlrpc.new(name, interfaces)

Parameters

Return values

Throws: Throws if the interface argument is not a table, an interface table is empty, or its members are not functions.

-- A flat service exposes add directly, without an interface prefix.
local service = xmlrpc.new("Calculator", {
   add = function(a, b) return a + b end
})

system.listMethods lists the callable names for either form. An unknown interface does not fall back to a flat method with the same name.

service:execute(request, response)

Parameters

Return values: None. The result is sent in the HTTP response.

Each call keeps its own request and response state, so the service object can handle overlapping requests when a method yields. The method is not called if reading the request body fails. A no-argument call may omit the XML params element.

Callback parameters and results: The selected method receives the decoded XML-RPC parameters as positional Lua arguments. Its first return value, result, may be a number, string, boolean, table, or a value prepared by xmlrpc.base64 or xmlrpc.iso8601. To send a fault, return nil, faultCode, faultString, where faultCode is a number and faultString is a string. Additional success return values are ignored.

Throws: Missing request or response arguments throw. Request parsing errors, unknown methods and errors raised by service methods are handled as XML-RPC faults. Errors while converting or sending the response can propagate, including unsupported callback result types.

Decoded callback values

XML typeLua typeDescription
string, or value without a type elementstringDecoded text, including whitespace. Empty text becomes an empty string.
int, i4, doublenumberConverted using Lua's tonumber. Text that cannot be converted produces a fault before the method is called. Numeric precision follows the configured Lua build.
booleanbooleanText 1 or true becomes true; other text becomes false.
arraytableValues in order, starting at index 1.
structtableValues indexed by member-name strings.
nil extensionbooleanBecomes false, preserving the position in an array or argument list.
base64stringDecoded bytes; see the helper below.
dateTime.iso8601integer or stringA timestamp when conversion succeeds; otherwise the original date text. See the conversion rules below.

Strings and tables: Text, struct member names and fault messages are XML-escaped. Strings preserve whitespace and decoded text; XML line-ending normalization still applies to literal carriage returns in incoming documents. Use xmlrpc.base64 for arbitrary binary data. Tables with only numeric keys are encoded as arrays using their sequence from index 1, stopping at the first hole. Other tables are structs with string member names. Repeated references to the same table are serialized independently; a reference back to a table currently being serialized is represented by the XML-RPC nil extension.

Numeric results

A numeric result (Lua number) is encoded as i4 when it is a whole number from -2147483648 through 2147483647. Other finite numbers are encoded as double, which may use scientific notation. NaN and positive or negative infinity are invalid callback results and cause response encoding to throw.

Double output uses BAS's internal number formatter. Its precision is limited to roughly 15 significant decimal digits in the standard double build; exact round trips are not guaranteed. For example, 1/3 is sent as 0.333333333333333. Small values such as 0.0000001 are preserved using scientific notation instead of rounding to six decimal places. Large Lua integers encoded as doubles are subject to floating-point precision.

Clients must accept scientific notation for double values. This is an extension to the original XML-RPC specification's decimal-only notation.

Binary Data and Time Conversion

The two XML-RPC types dateTime.iso8601 and base64 requires special handling.

dateTime.iso8601

xmlrpc.iso8601([value [, decode]])

Parameters

Return values

Throws: Throws for unsupported argument types, fractional numeric timestamps, numbers outside the Lua integer range, or timestamps outside the supported calendar range. An omitted value uses the current time; explicit nil is not a valid value. Unconvertible date text or an out-of-range decoded timestamp with decode=true returns nil.

XML-RPC dates use ISO 8601 text. Lua timestamps count seconds from January 1, 1970 UTC; negative timestamps represent earlier dates.

Receiving:

A dateTime.iso8601 value becomes a Lua number only when the BAS date parser accepts its text. For example, 2024-01-02T03:04:05Z is converted to a timestamp. Conventional XML-RPC text such as 20240102T03:04:05, which has no timezone, currently remains a Lua string. Other text the parser cannot convert also remains a string, including dates whose timestamps do not fit the configured Lua integer type.

Sending:

You can explicitly tell the XML-RPC stack to return a UNIX time i.e. a number as dateTime.iso8601 encoding by calling xmlrpc.iso8601

The following service function illustrates how to return iso8601 encoded data/time:

local function currentTimeService()
   return xmlrpc.iso8601(os.time())
end

base64

xmlrpc.base64(data)

Parameters

Return values

Throws: Throws if data is missing or cannot be read as a Lua string. Encoding is deferred until the service response is generated.

B64 encoding is typically used when sending binary data. A string in Lua may contain any bytes and one can therefore store binary data in Lua strings.

Receiving:

When the XML-RPC stack receives B64 encoded data, the data is automatically converted to a Lua string.

Sending:

When the XML-RPC stack converts the response from a Lua XML-RPC service function to XML, strings are by default converted to the XML-RPC string type. The XML-RPC stack can send a Lua string as B64 encoded data, but you must explicitly tell the XML-RPC stack to do the conversion. The conversion is done by calling xmlrpc.base64

The following two service functions return response data as a XML-RPC string and as XML-RPC base64 encoded data respectively:

local function myService1()
   return "This is a string"
end

local function myService2()
   return xmlrpc.base64("This is a string encoded as B64")
end