Server Application

MonaServer includes a powerfull script-engine (using LUA langage) to create your own server applications. It allows to extend Mona behavior to add your specific needs in a flexible scripting-way.

Create a server application

On start, Mona creates a www folder in its root directory if it doesn’t exist already. In this space, you can create subfolders, each one of them describes one server application. When a client connects to MonaServer, the URL path relates the application to use. For example, the address rtmfp://host:port/myApplication search its corresponding application in MonaServer/www/myApplication folder. First time MonaServer builds and executes the file main.lua presents in this folder. Then, on new comer, the application already built is executed, unless main.lua file has been modified since the last time. Indeed when you edit main.lua file, the corresponding application is rebuilt in a dynamic way, without having to restart the server.

rtmfp://host:port/                   ->     MonaServer/www/main.lua (root application)
rtmfp://host:port/myApplication      ->     MonaServer/www/myApplication/main.lua
rtmfp://host:port/Games/myGame       ->     MonaServer/www/Games/myGame/main.lua

Note

  • The root application is built and started on MonaServer start, whereas other server applications are started on the first client connection.
  • Exceptionaly you can give root rights to a child application with the function children() as shown below. It permits to start other applications at start.
children("childapp")

Each application is identified by its path, which is exactly the path part of the RTMFP URL connection. In the example above, root application has an empty string for path, then the both others have respectively /myApplication and /Games/myGame for path values.

Here a very simple first server application:

function onStart(path)
  print("Server application '"..path.."' started")
end
function onStop(path)
  print("Server application '"..path.."' stopped")
end

Global configurations

MonaServer.ini file allows to give some global configuration values on MonaServer start-up (see Configurations). To access for these values from script, use mona.configs property (see Mona object description to get more details). This system allows to create your own global configuration values and access to them in scripts.

; MonaServer.ini file
port=19350 ; existing config
; New custom config
[myGroup]
myConfig=test
-- A script file
print(mona.configs.port) -- displays "19350"
print(mona.configs.myGroup.myConfig) -- displays "test"

Communication between server applications

One application can access (read or write) to a variable of one other application, and can call one function of one other application. But it goes much further than that, applications can be specialized, and inherit them, exactly like inheritance of classes.

Application inheritance

Principle is simple, for example the /Games/myGame application extends the /Games application, and so all functions and variables available in /Games/main.lua are available in /Games/myGame/main.lua.

-- /Games script application
test = "I am Games"
-- /Games/myGame script application
print(test) -- displays "I am Games"

You can overload an inherited variable or an inherited function, and even dynamically remove the overload if need in putting value to nil.

-- /Games/myGame script application
print(test)          -- displays "I am Games"
test = "I am myGame" -- overloads test variable
print(test)          -- displays "I am myGame"
test = nil           -- remove overloading test variable
print(test)          -- displays "I am Games"

On variable overloading (or function overloading), you can always access for the parent version in prefixing with the parent application name.

-- /Games/myGame script application
print(test)          -- displays "I am Games"
test = "I am myGame" -- overloads test variable
print(test)          -- displays "I am myGame"
print(Games.test)    -- displays "I am Games"

Note

The root server application has for path an empty string.

-- '/' script application (the root server application)
function hello()
  print("I am the root application")
end
-- /Games script application
function hello()
  print("I am /Games application")
end
hello() -- displays "I am /Games application"

Warning

Events are functions called by the system (see Events), if an application doesn’t define onConnection event for example, on new client connection for this application, it’s the parent application which will receive the event. To avoid it, you have to overload the event in child application, and you can call also the parent version if needed.

Note

The keyword super is supported to refer to the the parent application:

-- /Games script application
function onConnection(client,...)
  return super:onConnection(client,...)
end
super:hello() -- displays "I am the root application"

You can use client.path property to check if it’s a client connected for this application or for one child application (see Client object description).

Exchange between unrelated server applications

In class inheritance, parent class has no knowledge of its children. However, here a parent server application can access for an child variable or function in checking before its existence. For example if /Games application would like to call a load function in /Games/myGame application, it have to check myGame existence, if myGame returns nil, it means that myGame doesn’t exist or is not yet started.

-- /Games script application
if myGame then myGame:load() end
-- /Games/myGame script application
function load() end
  ...
end

By the same way, any applications can do the same thing with any other applications, even without hierarchical relationship.

-- /myApplication script application
if Games then
  if Games.myGame then Games.myGame:load() end
end
-- /Games/myGame script application
function load() end
  ...
end

Communication between server and client

Pull data, Remote Procedure Call

You have to define your RPC functions as a member of client object gotten on connection, its signature will be exactly the same on client and server side. It can take multiple parameters, and it can return one result.

function onConnection(client,...)
  function client:test(name,firstname)
    return "Hello "..firstname.." "..name
  end
end

Flash client :

_netConnection.client = this
_netConnection.call("test",new Responder(onResult,onStatus),"François-Marie","Arouet")

function close():void { _netConnection.close() }
function onStatus(status:Object):void {
  trace(status.description)
}

function onResult(response:Object):void {
  trace(response) // displays "Hello François-Marie Arouet"
}

Warning

When you change default client of NetConnection, the new client should have a close() method which closes the connection, because a RTMFP Server can call this function in some special cases

Note that returned result of the scripting function is a writing shortcut for:

function client:test(name,firstname)
  client.writer:writeMessage("Hello "..firstname.." "..name)
end

They both make exactly the same thing.

If the function is not available on the client object, it returns a NetConnection.Call.Failed status event with Method ‘test’ not found in description field. But you can also customize your own error event:

function client:test(name,firstname)
  if not firstname then error("test function takes two arguments") end
  return "Hello "..firstname.." "..name
end
_netConnection.client = this
_netConnection.call("test",new Responder(onResult,onStatus),"François-Marie");

function close():void { _netConnection.close() }
function onStatus(status:Object):void {
  trace(status.description) // displays "..main.lua:3: test function takes two arguments"
}
function onResult(response:Object):void {
  trace(response)
}

Remote Procedure Call with Websocket or HTTP

Websocket supports JSON RPC and HTTP supports either JSON and XML-RPC using the ‘Content-Type’ header. Here are samples using the same lua server part :

Websocket client :

socket = new WebSocket(host);
socket.onmessage = onMessage;
var data = ["test", "François-Marie", "Arouet"];
socket.send(JSON.stringify(data));

function onMessage(msg){
  var response = JSON.parse(msg.data);
  alert(response);
}

HTTP JSON-RPC client :

var xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', "", true);

// Manage the response
xmlhttp.onreadystatechange = function () {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    var response = JSON.parse(xmlhttp.response);
    alert(xmlhttp.response);
  }
}
// Send the POST request
xmlhttp.setRequestHeader('Content-Type', 'application/json');
var data = ["test", "François-Marie", "Arouet"];
xmlhttp.send(JSON.stringify(data));

HTTP XML-RPC client :

var xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', "", true);

// Manage the response
xmlhttp.onreadystatechange = function () {
  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    var response = xmlhttp.response;
    alert(response);
  }
}
// Send the POST request
xmlhttp.setRequestHeader('Content-Type', 'text/xml');
xmlhttp.send("<methodCall><methodName>test</methodName><params><param><value><string>François-Marie</string></value></param><param><value><string>Arouet</string></value></param></params></methodCall>");

Note

Here we use the XML-RPC format which is fully supported by Mona.

See more samples on Samples page.

Push data

Push data mechanism is either possible with Websocket and Flash using client.writer object.

client.writer:writeInvocation("onPushData","Rambo","John")

Flash client :

function onPushData(name:String,firstName:String):void {
}

Websocket client :

socket.onmessage = onMessage;
...
function onMessage(msg){
  var response = JSON.parse(msg.data);
  if (response[0] == "onPushData") {
    name = response[1]
    firstName = response[2]
    ...
  }
}

Note

Push data is not possible with HTTP protocol because it is an old protocol based on pull data only. Long polling is a solution for this but is not implemented yet.

Here an example of push data every two seconds (see onManage() event description):

writers = {}
function onConnection(client,...)
  writers[client] = client.writer
end
function onDisconnection(client)
  writers[client] = nil
end
function onManage()
  for client,writer in pairs(writers) do
    writer:writeInvocation("refresh")
  end
end
function refresh():void {...}

client.writer returns the main flowWriter of this client. A FlowWriter is an unidirectional communication pipe, which allows to write message in a fifo for the client. Each flowWriter has some statistic exchange informations. When you want push a constant flow with a large amount of data, or if you want to get independant exchange statistics without disrupting the main flowWriter of one client, you can create your own flowWriter channel to push data:

writers = {}
function onConnection(client,...)
  writers[client] = client.writer:newFlowWriter()
end
function onDisconnection(client)
  writers[client] = nil
end
function onManage()
  for client,writer in pairs(writers) do
    writer:writeInvocation("refresh")
  end
end
function refresh():void {...}

When you create your own Writer, you can overload its onManage function, allowing you to write the same thing in a more elegant way, which avoid here writers table usage, and make the code really more short (see API page for more details).

function onConnection(client,...)
  writer = client.writer:newFlowWriter()
  function writer:onManage()
    self:writeInvocation("refresh")
  end
end
function refresh():void {...}

If you have need of pushing rate greater than two seconds, use onRealTime event of root application (see Events for more details).

LUA types conversions

Several types are supported for messages received by server or sended to clients :
  • AMF (for flash clients),
  • JSON,
  • XML-RPC,
  • XML (using the fromXML parser),
  • and raw data (obviously it does not needs conversion).

AMF to LUA conversions

  • Used by : RTMP, RTMFP and HTTP POST (Mime-Type: application/amf)
  • Functions : toAMF/fromAMF/toAMF0 (see Mona object for more details)

Primitive conversion types are easy and intuitive (Number, Boolean, String). Except these primitive types, in LUA all is table. Concerning AMF complex type conversions, things go as following:

-- LUA table formatted in Object          // AMF Object
{x=10,y=10,width=100,height=100}          {x:10,y:10,width:100,height:100}

-- LUA table formatted in Array           // AMF Array
{10,10,100,100}                           new Array(10,10,100,100)

-- LUA table mixed                       // AMF Array associative
{x=10,y=10,100,100}                      var mixed:Array = new Array(10,10,100,100);
                                          mixed["x"] = 10;  mixed["y"] = 10;

-- LUA table formatted in Dictionary     // AMF Dictionary
{10="test","test"=10,__size=2}           var dic:Dictionary = new Dictionary();
                                          dic[10] = "test";  dic["test"] = 10;

-- LUA table formatted in ByteArray      // AMF ByteArray
{__raw="rawdata"}                        var data:ByteArray = new ByteArray();
                                          data.writeUTFBytes("rawdata");

-- LUA Table formatted in date           // AMF Date
{year=1998,month=9,day=16,yday=259,      new Date(905989690435)
wday=4,hour=23,min=48,sec=10,msec=435,
isdst=false,__time=905989690435}

On a LUA to AMF conversion, priority conversion order works as following:

  1. If the LUA table given contains the property __raw, it’s converted to a ByteArray AMF object.
  2. If the LUA table given contains the property __size, it’s converted to a Dictionary AMF object.
  3. If the LUA table given contains the property __time, it’s converted to a Date AMF object.
  4. Otherwise it chooses the more adapted conversion (Object, Array, or Array associative).

About __time property on a date object, it’s the the number of milliseconds elapsed since midnight UTC of January 1 1970 (Unix time).

About Dictionary object, LUA table supports an weak keys table feature, and it’s used in AMF conversion with the weakKeys contructor argument of Dictionary AMF type. It means that if you build an AMF Dictionary object with weakKeys equals true and send it to MonaServer, MonaServer converts it in a LUA table with weak keys, and vice versa.

Note

Actually Mona supports all AMF0 and AMF3 format, excepts Vector and XML types.

Error

  • Complex objects are not supported by LUA to AMF conversion,
  • IExternalizable objects (ArrayCollection apart) are not supported too, if you want us to add support for both please contact us (Services & Contacts).

JSON to LUA conversions

  • Used by : WebSocket and HTTP POST (Mime-Type: application/json)
  • Functions : toJSON/fromJSON (see Mona object for more details)

As in AMF primitive, conversion types are easy and intuitive (Number, Boolean, String). For the rest, things go as following:

-- LUA table formatted in Object          // JSON Object
{x=10,y=10,width=100,height=100}          {"x":10,"y":10,"width":100,"height":100}

-- LUA table formatted in Array           // JSON Array
{10,10,100,100}                           [10,10,100,100]

-- LUA table mixed                       // JSON Array + Object
{x=10,y=10,100,100}                      [{"x":10,"y":10},100,100]

Note

  • Order can differ from original type because there is no attribute order in lua,
  • Notice that in JSON mixed tables don’t exist, that’s why we must create an Array with an object containing associative values,
  • For serializations reasons JSON data need to be encapsulated in a JSON array ‘[]’. For example the JSON Array above will be sended by client in this format :
socket.send("[[10,10,100,100]]");

Query String to LUA conversions

  • Used by : HTTP POST (Mime-Type: application/x-www-form-urlencoded)
  • Functions : toQuery/fromQuery (see Mona object for more details)

As in AMF primitive, conversion types are easy and intuitive (Number, Boolean, String). For the rest, things go as following:

-- LUA table formatted in Object          // Query String
{x=10,y=10,width=100,height=100}          x=10&y=10&width=100&height=100

-- LUA table formatted in Array           // Query String
{10,10,100,100}                           10&10&100&100

Note

  • As you can see there is no array type in our Query String format, all values are converted in primitives or in pairs key/value,
  • In the Query String to LUA conversion each sequence of pairs key/value is converted in a lua Object.

XML-RPC to LUA conversions

  • Used by : HTTP POST (Mime-Type: application/xml)
  • Functions : toXMLRPC/fromXMLRPC (see Mona object for more details)

There are a lot of XML format for communication, XML-RPC has been choosen for its simplicity and because it fits well with Mona.

-- LUA table formatted in Object          // XML-RPC Object
{x=10,y=10,width=100,height=100}          <struct>
                                              <member><name>x</name><value><i4>10</i4></value></member>
                                              <member><name>y</name><value><i4>10</i4></value></member>
                                              <member><name>width</name><value><i4>100</i4></value></member>
                                              <member><name>height</name><value><i4>100</i4></value></member>
                                          </struct>

-- LUA table formatted in Array           // XML-RPC Array (size is facultative)
{10,10,100,100}                           <array size="4">
                                            <data>
                                              <value><i4>10</i4></value><value><i4>10</i4></value><value><i4>100</i4></value><value><i4>100</i4></value>
                                            </data>
                                          </array>

-- LUA table mixed                        // XML-RPC Array + Object
{x=10,y=10,100,100}                       <array size="3">
                                            <data>
                                              <value><struct>
                                                <member><name>y</name><value><int>10</int></value></member>
                                                <member><name>x</name><value><int>10</int></value></member>
                                              </struct></value>
                                              <value><int>100</int></value>
                                              <value><int>100</int></value>
                                            </data>
                                          </array>

Note

  • As you see XML-RPC is a bit verbose so we council you to use JSON if possible for faster communication,
  • Order can differ from original type because there is no attribute order in lua,
  • Notice that in XML-RPC mixed tables don’t exist, that’s why we must create an array with an object containing associative values.

XML data compatibility (XML parser)

  • Not used in parsers
  • Functions : toXML/fromXML (see Mona object for more details)

As mentioned above, Mona traduce XMLRPC calls automatically. For other types of XML data only few LUA code lines are needed, using the useful XML parser.

Two methods are available to do this :
  • fromXML
  • and toXML

See Mona for more details.

A sample with SOAP

Let us begin with an RPC addition method :

function onConnection(client,...)
  function client:add(value)
    return value+1
  end
end

We can already call this method by an HTTP GET request (the name and the parameters are given in the URI), a JSON POST, XML-RPC or by AMF. But if we want absolutly to call it from a SOAP client with the following request :

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Add xmlns="http://localhost/">
      <Value>1</Value>
    </Add>
  </soap:Body>
</soap:Envelope>

And the expected response :

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <AddResponse xmlns="http://localhost/">
      <AddResult>
        <Value>2</Value>
      </AddResult>
    </AddResponse>
  </soap:Body>
</soap:Envelope>

Just rebuild the LUA code lines like this :

function onConnection(client,...)

  function client:onMessage(data)
    local xml = mona:fromXML(error,data) -- parse the XML data

    -- Call the method
    local result = self:add(xml["soap:Envelope"]["soap:Body"].Add.Value.__value)

    -- Replace the Add method by AddResult
    local content = xml["soap:Envelope"]["soap:Body"].Add
    content.__name = "AddResponse"
    content.__value = {__name="AddResult",{__name="Value",result}}

    -- Rewrite the XML data and send the result
    return mona:toXML(error,xml)
  end

  function client:add(value)
    return value+1
  end
end

XML to LUA conversions

The XML parser (fromXML/toXML) is freely inspirated by an open source project to give an intuitive traduction of xml in lua variable. Here is an example of xml to lua conversion :

XML:

<?xml version="1.0"?>
<document>
  <article>
    <p>This is the first paragraph.</p>
    <h2 class='opt'>Title with opt style</h2>
  </article>
  <article>
    <p>Some <b>important</b> text.</p>
  </article>
</document>

LUA:

{ xml = {version = 1.0},
  {__name = 'document',
    {__name = 'article',
      {__name = 'p', 'This is the first paragraph.'},
      {__name = 'h2', class = 'opt', 'Title with opt style'},
    },
    {__name = 'article',
      {__name = 'p', 'Some ', {__name = 'b', 'important'}, ' text.'},
    },
  }
}

fromXML usage:

variable = mona:fromXML(XML)

For facility you can access to “This is the first paragraph” by two ways :

  1. variable[1][1][1][1]
  2. variable.document.article.p.__value

LUA extensions and files inclusion

LUA can be extended easily, LUA extensions can be founded for all needs and on all operating systems. With LUA it is a common thing to add some new MonaServer abilities as SQL, TCP sockets, or others. Install your LUA extension library, and add a require line for your script. LUA will search the extension in some common location related with LUA folder installation.

Now if you need to organize your code in different directories for your server application, we have extended the LUA functions require, dofile and loadfile (see this LUA page) to allow inclusion through relative paths. Search begin in the directory of the application itself and recursively in each parents applications. And finally, if the file cannot be found in this way it search in the common locations of LUA. So you can include your scripts like this :

local module = require("module.lua")
require("init.lua")

function onStart(path)
  dofile("start.lua")
end
function onConnection(client,...)
  local file = loadfile("connection.lua")
  pcall(file)
end

Or you can always use mona:absolutePath(path) function (see Mona object for more details) if you want to use the absolute path of the file :

function onStart(path)
  dofile(mona:absolutePath(path).."start.lua")
end
function onConnection(client,...)
  dofile(mona:absolutePath(path).."connection.lua")
end

Warning

If you edit an included file (like start.lua or connection.lua here), change are not taken as far as dofile is not called again or main.lua of this server application is not updated again.

LOGS

LUA print function writes text on the output console of MonaServer in a non-formatted way. Also, in service or daemon usage, nothing is printed of course. The solution is to use logs macros :

  • ERROR, an error.
  • WARN, a warning.
  • NOTE, an important information, displayed by default in Mona log files.
  • INFO, an information, displayed by default in Mona log files.
  • DEBUG, displayed only if you start MonaServer with a more high level log (see /help or –help on command-line startup)
  • TRACE, displayed only if you start MonaServer with a more high level log (see /help or –help on command-line startup)
function onStart(path)
  NOTE("Application "..path.." started")
end
function onStop(path)
  NOTE("Application "..path.." stopped")
end

API

The complete API documentation is available on the API page.