Repository navigation
Server Application
CumulusServer includes a powerfull script-engine (using LUA langage) to create your own server applications. It allows to extend Cumulus behavior to add your specific needs in a flexible scripting-way.
On start, Cumulus 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 CumulusServer, the URL path relates the application to use. For example, the address rtmfp://host:port/myApplication search its corresponding application in [CumulusServer folder]/www/myService folder. First time CumulusServer 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/ -> [CumulusServer folder]/www/main.lua (root application)
rtmfp://host:port/myApplication -> [CumulusServer folder]/www/myApplication/main.lua
rtmfp://host:port/Games/myGame -> [CumulusServer folder]/www/Games/myGame/main.lua
Note The root application is built and started on CumulusServer start, whereas other server applications are started on first client connection for this application.
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)
endOne 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.
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, and is reached by the name www.
-- '/' 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"
www.hello() -- displays "I am the root application"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
...
endBy 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
...
endYou can call a server function from client, in giving multiple parameters, and function can return one result.
function test(client,...)
name,firstname = unpack(arg)
return "Hello "..firstname.." "..name
end _netConnection.client = this
_netConnection.call("test",new Responder(onResult,onStatus),"Client","Test")
function close():void { _netConnection.close() }
function onStatus(status:Object):void {
trace(status.description)
}
function onResult(response:Object):void {
trace(response) // displays "Hello Test Client"
}Warning When you change default client of NetConnection, the new client must 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 test(client,...)
name,firstname = unpack(arg)
client.writer.writeAMFResult("Hello "..firstname.." "..name)
endBoth make exactly the same thing. If the function is not available, it returns a NetConnection.Call.Failed status event with Method 'test' not found in description. But you can also customize your own error event:
function test(client,...)
name,firstname = unpack(arg)
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),"Client");
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)
}Push data mechanism is also possible in using client.writer object.
function pushData(client)
client.writer.writeAMFMessage("onPushData","Application","Test")
end function onPushData(name:String,firstName:String):void {
}Here an example of push data every two seconds (see Events part of Server Application, API page for 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.writeAMFMessage("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 (qualityOfService, see Objects part of Server Application, API page for more details). When you want push a constant flow with a large amount of data, or if you want to get independant exchange statistics without disrupt 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.writeAMFMessage("refresh")
end
end function refresh():void {...}When you create your own flowWriter, 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 Objects part of Server Application, API page for more details).
function onConnection(client,...)
writer = client.writer.newFlowWriter()
function writer:onManage()
self.writeAMFMessage("refresh")
end
end function refresh():void {...}A conversion between AMF and LUA types allows to read AMF data from client, and write AMF data to client. Primitive conversion types are easy and intuitive (Number, Boolean, String). But concerning complex types like Array and Object, things go as following:
-- LUA Table, map usage // AMF Object
{x=10,y=10,width=100,height=100} {x:10,y:10,width:100,height:100}
-- LUA Table, array usage // AMF Array
{10,10,100,100} new Array(10,10,100,100)
-- LUA Table, mixed usage // 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, dictionary usage // AMF Dictionary
{10="test","test"=10,__size=2} var dic:Dictionary = new Dictionary();
dic[10] = "test"; mixed["test"] = 10;
-- LUA Table, byteArray usage // AMF ByteArray
{__raw="rawdata"} var data:ByteArray = new ByteArray();
data.writeUTFBytes("rawdata");
-- LUA Table, date format // 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}