SOAP Services in Barracuda


Note: SOAP (Simple Object Access Protocol) is an older web service protocol that uses XML for structured messaging. While it's no longer the preferred approach for most modern web applications, SOAP is still used in certain enterprise and legacy systems. We include support and documentation to assist those who need to maintain or integrate with existing SOAP-based services.

Barracuda SOAP services are defined and written in Lua, in a simple text format.

These service definitions would normally be embedded in your server, but can be loaded dynamically or uploaded remotely if required, without rebuilding the server.

Barracuda dynamically generates and publishes the appropriate WSDL (Web Service Description Language) document from your installed service definitions. This means that the WSDL always reflects the currently installed services, so there is no possibility of a mismatch between the service implementation and its WSDL.

Simple service definition

The following is all that is required to define a SOAP service called Info, with an operation called Date that returns the server's date:

soap_services = {
  Info = {
    Date = {
      output = {name = "return", type = "date"},
      call = function() return os.date("!%Y-%m-%d") end,
    },
  }
}

Note the type="date" declaration. The WSDL uses the declared XML Schema type. The Lua conversions are listed below; the SOAP module does not perform full XML Schema validation.

Here is a VBScript program that accesses the service defined above, assuming that Barracuda SOAP is configured to publish SOAP services on localhost/soap:

dim SOAPClient
set SOAPClient = createobject("MSSOAP.SOAPClient")

SOAPClient.mssoapinit("http://localhost/soap/info.wsdl")

wscript.echo "SOAP Server date is :", SOAPClient.Date()

Service Definitions in Detail

Barracuda SOAP services are defined as a Lua table called soap_services. This table defines one or more services; each service can support any number of operations.

An operation is a single remote procedure call (rpc).
Each named element in the soap_services table is a SOAP service definition, and must contain a list of operations supported by this service.
note: SOAP service, operation and parameter names must be valid XML names, as per the W3C XML spec.

Each operation is a table with the following defined members : input, output, call, and lifetime

input (table) (optional)
If specified, input is a table (list) defining any input parameters to be passed to the call function.
Each parameter definition is a table with the following members:
name (string) (mandatory)
a string defining the name of this parameter;
type (string or table) (optional)
The data type defaults to "string". type can be one of the following:
  • A string defining an XML Schema type, for example "integer".
  • A string defining an array of simple elements by appending the word "Array" to a simple type, for example "stringArray".
  • A table defining a structured type.
  • Structured types are defined as a list (table) of members, each with a name and type element. They can contain simple types and arrays, but not sub-structures.
For convenience, if only a single input parameter is specified, it does not need to be wrapped in a list. For example, input={name="x", type="double"} is equivalent to input={{name="x", type="double"}}.

output (table) (optional)
If specified, output is a table (list) defining any parameters returned by the call function.
The structure of output parameters are identical to input, but since most client applications only support a single return value, multiple values are usually returned as a structure or array.

call (function) (mandatory)
The call member is the Lua rpc function.
It will be called with the parameters defined by input, and is expected to return values as defined by output. Failure to return the results specified will result in an error message being sent to the client.
As a special case, if the call function returns nil followed by an error message, the server will return a SOAP error to the caller, with the error message supplied.
Parameters and return values: Arguments are passed in the order listed by input; results are returned in the order listed by output. The parameter definition's name names the corresponding XML value. Lua values follow the conversion table below. Arrays use sequence tables; structures use tables with fields named by their member definitions. An omitted type defaults to string. With no input, the callback receives no arguments; with no output, return no values on success.
Throws: Errors raised by the callback are caught and sent to the client as SOAP faults. Return nil, error, where error is a string, to report an operation failure without raising a Lua error.

lifetime (number) (optional)
A number defining how many seconds the response remains valid. This value is used for cache-control headers.
(The default response lifetime is configured in the server.)

Lua value conversions

Declared typeLua typeDescription
integernumberInput uses Lua numeric conversion; output uses integer formatting. Supply whole numbers within the Lua integer range.
decimal, float, doublenumberUse Lua numbers. Decimal values therefore have Lua numeric precision, not arbitrary decimal precision.
booleanbooleanXML input accepts true, false, 1 and 0. XML output uses 1 or 0.
string, or omitted typestringXML text and CDATA are combined in document order without inserted spaces. Whitespace-only values are preserved. Output text is XML-escaped. Empty text is an empty Lua string.
Other simple typesstringPassed as XML text, including date/time, base64Binary, hexBinary and derived numeric types such as int and unsignedInt. Supply the type's XML representation; these types are not automatically decoded to binary or converted to Lua numbers.
Simple type followed by ArraytableA sequence starting at index 1, with each element converted using the simple type. Empty sequences are supported, including array members of structures.
Structure definition tabletableFields use the declared member names and types. Input also provides the members at sequential numeric indices. Nested structures are not supported.

Example service definitions

Note: two dashes (--) indicate a comment in Lua; strings are enclosed in quotes (" "); tables are enclosed in braces ({ }); and table entries are separated by commas (,). Trailing commas are permitted. For the full Lua syntax, see the Lua documentation.

Client & Server Info

 -- File: soap/.services/date.lua
soap_services = {

  Info = {

    HTTPVersion = {   -- returns the HTTP request version as a string.
      output = {name = "return",type = "string"},
      lifetime = 0, -- never cache
      call = function() return request:version() end,
    },

    Header = { -- returns a string with the value of the named HTTP header.
      input = {name = "name",type = "string"},
      output = {name = "value",type = "string"},
      lifetime = 0, -- never cache
      -- return empty string if no header
      call = function(s) return request:header(s) or "" end,
    },

    Date = { -- returns the current server date, in UTC
      lifetime = 5, -- date is valid for 5 seconds
      output = {name = "return",type = "date"},
      call = function() return os.date("!%Y-%m-%d") end,
    },

   }
}

a simple calculator :


 -- File: soap/.services/math.lua
soap_services = {

  Math = { -- service definition

    Add = { -- operation
      input = {{name = "x", type = "double"},{name = "y", type = "double"}},
      output = {name = "return",type = "double"},
      call = function(x,y) return x+y end
    },

    Subtract = { -- operation
      input = {{name = "x", type = "double"},{name = "y", type = "double"}},
      output = {name = "return",type = "double"},
      call = function(x,y) return x-y end
    },

    Multiply = { -- operation
      input = {{name = "x", type = "double"},{name = "y", type = "double"}},
      output = {name = "return",type = "double"},
      call = function(x,y) return x*y end
    },

    Divide = { -- operation
      input = {{name = "x", type = "double"},{name = "y", type = "double"}},
      output = {name = "return",type = "double"},
      call = function(x,y) return x/y end
    },

    Sum = { -- operation to sum an array of doubles
      input = {name = "t", type = "doubleArray"},
      output = {name = "return",type = "double"},
      call = function(t)
             local tot = 0
             for i,v in ipairs(t) do tot = tot+v end
             return tot
           end
    },

    RandomInt = { -- range must be a nonnegative integer
      input = {name = "range", type = "integer"},
      output = {name = "return",type = "integer"},
      call = function(range) return math.random(0,range) end,
    },

  } -- end of Math service definition
} -- end of soap_services definition

Barracuda Implementation


SOAP is implemented in Barracuda via a directory object, constructed by calling ba.create.soapdir(). The directory object can then be attached to the directory tree like any other Barracuda directory, with the same semantics and address resolution rules.

ba.create.soapdir(name, serviceloader [, wsdl_life [, rpc_life]])

Parameters

Loader callback

Both forms defer loading until a request needs the service definitions. A cached WSDL response does not reload them. RPC requests reload them on each request. Use soap_shared to retain service state between requests. For a file in an application's own IO rather than the VM resource IO, use the loader function form.

require"basoap" -- Install the SOAP constructor.
-- services/math.lua must exist in ba.openio("vm").
local mathdir = ba.create.soapdir("math", "services/math.lua")
dir:insert(mathdir, true) -- Attach to the application's directory.
local function serviceDefinitionLoader()
   -- io is the application's IO
   return function(env)
      local fnsource,err = io:loadfile("math.lua",env)
      if not fnsource then error("failed to load SOAP services: "..err) end
      return fnsource
   end
end
  

Return values

Throws

Throws for an invalid directory name or loader argument. Construction does not load or execute the service definitions, so it does not report missing files or file syntax errors. These failures occur later while handling a request: the filename loader raises a load error, which the server's request error handler reports. A custom loader may also raise an error. Errors raised by the service definition chunk, or a missing or non-table soap_services value, are reported as HTTP 500 responses.

For each defined service, Barracuda publishes a WSDL file at name/service name and name/service name.wsdl.
SOAP requests are sent to name/service name.rpc.
name is relative to the parent directory object.

Sample Implementation

SOAP message format: The generated WSDL uses SOAP 1.1 RPC/literal messages. The operation element must use the service namespace published in the WSDL's soap:body namespace attribute. Either a prefix or a default namespace may identify the operation; its parameter elements are unqualified. Responses use the same namespace and the operation name followed by Response, for example AddResponse. Unqualified operations and operations in another namespace are rejected with a SOAP fault. Use the current WSDL when configuring or generating a client.

Malformed envelopes, empty bodies and missing required input values are reported as SOAP faults. Errors raised by service callbacks, including non-string Lua error values, are converted to fault messages.

To implement the sample services above, create a text file with the service definitions as described above.
Then, in the Barracuda config file, create a SOAP service directory using
ba.create.soapdir("directory name", service-definition-loader), and attach it to the directory tree like any other Barracuda http dir.

ba.create.soapdir() creates a SOAP service directory. The service definitions are loaded when a request needs them, rather than during construction.
The SOAP directory will have an entry for each service defined in the service configuration file.
In the case of this sample config, and assuming that the directory name was "soap", Barracuda will publish a WSDL file at "soap/Math" and "soap/Math.wsdl",
and the address for soap requests will be at "soap/Math.rpc".

note :Barracuda SOAP rpc and wsdl requests are handled the same regardless of case, ie soap/Math and soap/math will return the same reponse.

You can test for the existence of the SOAP service by pointing a web browser at the WSDL URL.

The following complete example loads the two service definition files above, "date.lua" and "math.lua", creates two soap directories, and installs the directories in the server:

-- The following code creates the two SOAP services from our documentation: 
-- https://realtimelogic.com/ba/doc/en/lua/soap.html

require"basoap" -- Install ba.create.soapdir

-- The applications I/O: We set the io as local variable since the
-- variable is used as an upvalue by function serviceloader
local io=io 

--Lua SOAP service loader
local function serviceloader(fname)
   -- io is the applications IO
   return function(env) -- Create Closure
      local fnsource,err = io:loadfile(fname,env)
      if not fnsource then error("failed to load soap services : "..err) end
      return fnsource
   end
end

local soapdir=ba.create.dir"soap"
local datedir=ba.create.soapdir("date",serviceloader"soap/.services/date.lua")
local mathdir=ba.create.soapdir("math",serviceloader"soap/.services/math.lua")

-- Install SOAP services in the Barracuda virtual file system
dir:insert(soapdir, true)
soapdir:insert(datedir, true) -- ./soap/date/
soapdir:insert(mathdir, true) -- ./soap/math/

Copyrights

Excel, MSWord, VB Script and .NET are copyright(C) Microsoft Corporation.