gepard

1.9.4 • Public • Published

gepard

General purpose communication and synchronization layer for distributed applications / Microservices / events, semaphores, locks and messages for JavaScript, Java, Python and PHP

Overview

Gepard is a system consisting of a broker and connected clients. The communication is done via sockets or web-sockets. The sockets are always open so that any partner of a connection may be informed if this connection ended. This is very useful in the area of semaphores and locks.

Basic Client Creation

Up to now a client is a standalone JavaScript program, a JavaScript app inside a browser, a Java program, a Python program or a PHP program.

A client uses only one socket for all interactions with the broker. Thus a program needs only 1 client for all features. In order to use only 1 Client instance it is suggested to use the static method

  • JavaScript:
gepard = require  ( "gepard" )  :
var client = gepard.getClient ( [ port [, host ] ] ) ;
  • Java:
import org.gessinger.gepard.Client ;
Client client = Client.getInstance ( [ port [, host ] ] ) ;
 
  • Python:
import gepard
client = gepard.Client.getInstance ( [ port [, host ] ] )
 
  • PHP:
<?php
namespace Gepard;
require ( 'vendor/autoload.php' );
use Gepard;
$client = Client::getInstance ( [ port [host ] ] ) ;

Most Simple Usage with emit and listen

  • JavaScript:
gepard.getClient().emit ( "ALARM", { write: function() { this.end() } } ) ;
 
gepard.getClient().on ( "ALARM", (e) => { console.log(e) } ) ;
  • Java:
Client.getInstance().emit ( "ALARM" ) ;
 
Client.getInstance().on ( new String[] { "ALARM""BLARM" }(e) -> { System.out.println(e);} ) ;
  • Python:
gepard.Client.getInstance().emit ( "ALARM" )
 
def on_ABLARM ( event ):
    print ( event )
gepard.Client.getInstance().on ( ["ALARM","BLARM"], on_ABLARM )
  • PHP:
Client::getInstance()->emit("ALARM") ;
 
Client::getInstance()->on ( ["ALARM","BLARM"], function($e) { echo($e); } ) ;
 

Starting the Broker

The broker can be instantiated from a JavaScript program but the most common and simplest way to use it is to start it detached as a daemon.

The appropriate command is:

node_modules/.bin/gp.broker.web

This starts the Broker and the corresponding web-socket-proxy
If you want to start the broker alone:

node_modules/.bin/gp.broker

There is a separate program for administration purposes:

node_modules/.bin/gp.info

or

node_modules/.bin/gp.admin [ --help ]

There is a special command for service-lookup:

node_modules/.bin/gp.lookup --gepard.zeronconf.type=<type-name>
e.g.:
node_modules/.bin/gp.lookup --gepard.zeronconf.type=test-gepard

This command lists all service-instances with the service-type test-gepard in the local subnet.

Install

npm install gepard

or the newest stable but development version:

npm install git+https://github.com/gessinger-hj/gepard

Getting Started

Here are some kind of "Hello World" examples.

All commands are in the directory: node_modules/.bin or node_modules/.bin/gepard

Up to now the JavaScript, the Python and the Java classes are implemented.
The examples show the nice and easy interaction between programs written in these different languages.

Base

  1. gp.broker.web
    Start the gepard broker with websocket proxy

  2. gp.shutdown
    Send a shutdown event to all clients and stop the broker

  3. gp.info
    Show basic information from the broker

JavaScript

  1. gp.listen --name=hello
    Start a listener for events named hello. If you want to listen to all events with name starting with hello use a wildcard: gp.listen "--name=hello__*__"

  2. gp.sem
    Acquire a semaphore

  3. gp.lock
    Acquire a lock

  4. If you want to play with the web-client implementation use the appropriate files in: node_modules/gepard/xmp/webclient

To simplyfy this the command

gp.http.simple [options]

is supplied starting a simple js webserver detached. Options are:

  • --port=<port>, default=8888
  • --root=<web-root>, default=node_modules/gepard/xmp/webclient
  • --index=<index-file>, default=index.html

Start your browser and go to: localhost:8888

  1. gp.http.simple.shutdown
    Stop the simple webserver.

  2. gp.http.simple.is.running
    Check if the simple webserver is running.


In order to try out the examples goto node_modules/gepard/xmp.
The following examples exist:
  • Listener.js
  • FullArmedListener.js
  • Emitter.js
  • EmitterWithBody.js
  • EmitterWithStatusInfo.js
  • Requester.js
  • Responder.js
  • Locker.js
  • AsyncSemaphore.js

Java

In order to try out the examples goto node_modules/gepard/java. All examples are included in lib/Gepard.jar. With the following command all examples can be executed:

java [-D<name>=<value>] -cp lib/Gepard.jar:lib/gson-2.3.1.jar org/gessinger/gepard/xmp/Listener

Listener may be replaced by:

  • Listener
  • Emitter
  • EmitterWithBody
  • EmitterWithStatusInfo
  • Requester
  • Responder
  • Locker
  • AsyncSemaphore
  • BlockingSemaphore

The class-version in the existing Gepard.jar is 1.6, so you need to have at least java 1.6 installed. There is an ant file to build your own jar.

Options, e.g. for the event-name must be set in the common Java format: -Dname=hello

Python

In order to try out the examples goto node_modules/gepard/python/xmp.

The following examples exist:

  • Listener.py
  • FullArmedListener.py
  • Emitter.py
  • EmitterWithBody.py
  • EmitterWithStatusInfo.py
  • Requester.py
  • Responder.py
  • Locker.py
  • AsyncSemaphore.py
  • BlockingSemaphore.py

PHP

The following examples exist:

  • Listener.php
  • Emitter.php
  • Requester.php
  • Responder.php

In order to try out the examples goto installation directory and call

php vendor/gepard/gepard-php/xmp/Listener.php

The other examples can be executed accordingly.

Configuration

The communication is based on sockets. Thus only the port and optional the host must be specified to use Gepard. The defaults are:

  • port=17501
  • host=localhost
  • web-socket port=17502

The port, host and logging directory can be set either by supplying these items

  1. within creating an instance of Client or Broker in your code.

  2. as startup arguments of your program as:

    • -Dgepard.port=<port>
    • -Dgepard.host=<host>
    • -Dgepard.log=<log-dir>
  3. with environmant variables of the form:

    • export ( or set ) GEPARD_PORT=<port>
    • export ( or set ) GEPARD_HOST=<host>
    • export ( or set ) GEPARD_LOG=<log-dir>

What is new

Release 1-9-3 Webclient adapted to ReactJS (Mobile) and single page Apps

The JavaScript Web-client is separated from gepard core to use it with mobile applications, standard single page web-applications and standalone environments like WebKit / Electron. For more information See npm gebard-web.

The Java version of the gepard-client (Gepard.jar and Google's gson-2.3.1.jar) can be used seamlessly in any Android environment. No special wrapper-classes should be created because experience shows that nothing can be simpler than the gepard-api used directly.

Release 1-9-2 Bugfix Release

See change log details

Release 1-9-0 New PHP Client implementation and published on Packagist

In this release a basic featured PHP client is included. The implementation is pure generic PHP code. The features are:

  • emit event
  • listen to events
  • request / result ( messages )

Due to lack of multi-threading semaphores and locks are not implemented.

The new PHP client can be easily installed with the composer.

Package and installation details can be found at packagist

Release 1-8-4 Python Flavour now ist pubished on pypi

The python client now can be easily installed with the command:

pip install gepard-python

Release 1-8-3 Bugfix Release

See change log details

Release 1-8-2 Enhance GPWebClient to use a Standard Webserver (nginx, ...)

The GPWebClient now accepts a full qualified URL as connection attributes. This is useful in case a standard web-server is used as a proxy.

Details see

Release 1-8-1 Maintenance and Bugfix

Maintenance release. Details

Release 1-8-0 Java Connect Retry on First Connect

If re-connect is requested and there is no reachable Broker then the Java client tries to connect every 5 seconds to establish a valid connection. This is the same behaviour as the JavaScript client and the WebClient used in a browser.

Release 1-7-9 WebClient New Reconnect Mechanism

If re-connect is requested with

client.setReconnect ( true )

the client acts as follows:

  1. If a running WebSocketEventProxy accepts a connection: connect.
  2. If ther is no running WebSocketEventProxy try to connect every 5 seconds forever.

If a re-connect is set but the client should exit if there is no running WebSocketEventProxy then an error listener can be registered. In this listener-function the re-connect setting can be reset.

Example:

        c.on('error', function(e)
        {
            c.setReconnect ( false ) ;
        });

In addition if a WebSocket-connection dies a re-connect is tried every 5 seconds. In this case all event-listener are registered again.

Release 1-7-8 WebClient Enhancement / Bugfix

  • GPWebClient: protected event-names for method on ( name, callback): "open", "close", "error", "shutdown", "end"
  • WebSocket: use protocol wss only for https:
  • GPWebClient: new method: close(). Can be used with the browser's event onbeforeunload to close a connection.

Release 1-7-6 WebClient Enhancement

Enable GPWebClient to optional use another target domain (host). Mainly the method gepard.getWebClient ( port ) now accepts an optional doman (host) as second parameter like: gepard.getWebClient ( 12345, "my.domain.com" ) ;

Release 1-7-5 JavaScript Enhancements

If re-connect is requested with

  1. Environment: export GEPARD_RECONNECT=true
  2. Option as argument: --gepard.reconnect=true
  3. client.setReconnect ( true )

the client acts as follows:

  1. If a running Broker accepts a connection: connect.
  2. If ther is no running Broker try to connect every 5 seconds forever.

If a re-connect is set but the client should exit if there is no running Broker then an error listener can be registered. In this listener-function the re-connect setting can be reset. Example:

        c.on('error', function(e)
        {
            c.setReconnect ( false ) ;
        });

Release 1-7-0 mDNS Zeroconf for Python

This release introduces the zeroconf mechanism for Python. The mechanism is set up as close as possible to the JavaScript version. Before you can use this mechanism the python-module zeroconf must be installed. This is done with the command: pip install zeroconf. On OSX and Linux/Unix this is a trivial task. On Windows it is more complicated so you have to read the installation documentation for this module carefully.

Zero-configuration networking (zeroconf) is a set of technologies that automatically creates a usable computer network based on the Internet Protocol Suite (TCP/IP) when computers or network peripherals are interconnected. It does not require manual operator intervention or special configuration servers. Zeroconf mDNS plus DNS-SD: that is, multicast DNS plus DNS service discovery. This technology is perfect to enhance the gepard based app communications. With Gepard it is such easy:

  1. gp.broker --gepard.zeroconf=test-gepard,0
    type: test-gepard
    socket: 0 (zero) means: any free port of the host

  2. python Listener.py --gepard.zeroconf.type=test-gepard --gepard.reconnect=true
    Finds any Broker in the subnet which advertizes the service-type test-gepard
    If currently no service is available waits for service coming up.
    If service goes down a new lookup is done and all listeners are re-registered to the new found Broker.

See details in the chapter Zeroconf Usage in Detail

Release 1-6-0 mDNS Zeroconf

Zero-configuration networking (zeroconf) is a set of technologies that automatically creates a usable computer network based on the Internet Protocol Suite (TCP/IP) when computers or network peripherals are interconnected. It does not require manual operator intervention or special configuration servers. Zeroconf mDNS plus DNS-SD: that is, multicast DNS plus DNS service discovery. This technology is perfect to enhance the gepard based app communications. With Gepard it is such easy:

  1. gp.broker --gepard.zeroconf=test-gepard,
    type: test-gepard
    socket: 0 (zero) means: any free port of the host

  2. node xmp/Listener.js --gepard.zeroconf.type=test-gepard --gepard.reconnect=true
    Finds any Broker in the subnet which advertizes the service-type test-gepard
    If currently no service is available waits for service coming up.
    If service goes down a new lookup is done and all listeners are re-registered to the new found Broker.

See details in the chapter Zeroconf Usage in Detail

Release 1-5-0 Channels

This release introduces Channels as a meta-layer to organize different realms in a very simple and effective manner.
Each listener-client can subscribe for event-names in one or more channel(s).
An emitter- / requester-client can emit events containing a channel identifier. The Broker filters the listening-clients with this channel and sends the event only to clients on this channel.

Using Channels

Event-listener

There are 3 different possibilities to subscribe to one or more channels:

  1. The method client.setChannel ( )
    The <channel-name> is of the form: id{,id-2, ... id-n}
    Example: client.setChannel ( "A,B,C" )
    All events which are not emitted with one of these channel-ids are filtered-out by the broker.
    If a client subscribes for any event-name all name-matching and channel-matching events are propagated to the registered listening-function.

  2. Regardless of the client's channel-membership a shortcut for registering a listener for a specific channel is:
    client.on ( <channel-name>::<event-name>, <callback> )
    As seen the channel-name and the event-name are concatenated with to colons (::).
    In this case the broker matches the given channel only in combination with the given event-name.
    Example: client.on ( "A::alarm" )

  3. Simple external channel assignment:

    • Start the appropriate application with the option
      --gepard.channel=<channel-name>
      or
    • Set the environment-variable in the scope of the process:
      export GEPARD_CHANNEL=<channel-name>

Event-emitter

There are 2 different possibilities to emit an event on a channel:

  1. The method client.setChannel ( ) The <channel-name> is of the form: id{,id-2, ... id-n}
    The event can only be sent on one channel the so-called main-channel.
    By default the main-channel is the first in the comma-list of channel-names. This may be changed by prefixing the appropriate channel-name with an asterisk (*)
    Example: client.setChannel ( "A,B,*C" )
    With this definition the channel C is the main-channel.

  2. Regardless of the client's channel-membership a shortcut for emitting an event on another channel is:
    client.emit ( <channel-name>::<event-name> )
    Example: client.emit ( "A**::**alarm" )

  3. Simple external channel assignment:

    • Start the appropriate application with the option
      --gepard.channel=<channel-name>
      or
    • Set the environment-variable in the scope of the process:
      export GEPARD_CHANNEL=<channel-name>

Channel Examples

Suppose you need access to 2 databases named DB-A and DB-B with different content.
It is easy to use the identical client-programs to connect with appropriate credentials and parameters.
The 2 micro-services register themselfes with

  • client.setChannel ( "DB-A" )
    client.on ( "db-request" )

    and

  • client.setChannel ( "DB-B" )
    client.on ( "db-request" )

A interested client only needs to know the appropriate channel and requests

  • client.request ( "DB-A::db-request", callback )

    or

  • client.request ( "DB-B::db-request", callback )

Release 1-4-5 Registered Event-names may contain Wildcards (RegExp)

Up to now an event-handler is registered with one or more exact event-names, e.g.

client.on ( "config-changed", <function-reference> )

Now it is possible to use wildcard pattern for registering a listener, e.g.

client.on ( "*-changed", <function-reference> )

In this case all events matching the regular expression .*-changed are routed to this listener.

In general a regular-expression pattern is derived from the given string if it containes some indicators.

  1. an * (asterisk) or a ? (question-mark)
    Before the regular-expression is compiled the astrisk is replaced by a .* and the ? is replced by a . (dot)
  2. at least one .*
    The whole string is used as is to compile the appropriate regular-expression.
  3. the string starts and ends with a slash: "/A.*/"
    The string between the slashes is used as is to compile the appropriate regular-expression.

Release 1-4-5 Simplified Handling of JSON Trees

Creating and editing JSON objects in Python and Java is a little bit unhandy. The class JSAcc in Python, JavaScript and Java simplifies the handling of these objects.
The main methods of this class are:

  • jsacc.add ( <path>, object )
  • jsacc.get ( <path>, [ <default-return-object> ] )
  • jsacc.remove ( <path> )

The <path> is a slash delimited combination of string-keys in the appropriate nodes of the JSON-tree.

Example:

  • Python:
e = Event ( "XXX" )
jsacc = JSAcc ( e.getBody() )
 
type = jsacc.get ( "request/header/type" )
jsacc.remove ( "request" )
jsacc.add ( "result/header/status"True )
  • JavaScript:
var e = new Event ( "XXX" ) ;
var jsacc = new JSAcc ( e.getBody() ) ;
 
var type = jsacc.get ( "request/header/type" ) ;
jsacc.remove ( "request" ) ;
jsacc.add ( "result/header/status", true ) ;
  • Java:
Event e = new Event ( "XXX" ) ;
JSAcc jsacc = new JSAcc ( e.getBody() ) ;
 
String type = (String) jsacc.get ( "request/header/type" ) ;
jsacc.remove ( "request" ) ;
jsacc.add ( "result/header/status"true ) ;

Path-elements which do not exist are created if needed.

Release 1-4-3 Logging

  • Client Logging into central Log-File on Broker side by calling the method
    client.log ( {object} o )
    The above {object} is converted to a user-friendly readable string, sent to the Broker which logs it to the default log-file.
    For all exception type objects an appropriate stack-trace is generated.

    Examples:

    This function can be rejected by overwriting the system method in the ConnectionHook class.
    Event.getName() is 'system'
    Event.getType() is 'log'

  • Logging with the concept of trace-points for Broker in/out and client in/out.
    The TracePoint logging features is configurable at run-time.
    In addition to the built-in TracePoints EVENT_IN and EVENT_OUT on the client-side application-specific trace-points can be easily defined and used:

JavaScript:

    var tracePoint = client.registerTracePoint ( "MY_TRACE_POINT" ) ;
    tracePoint.log ( "Action ended" ) ;

Python:

    tracePoint = client.registerTracePoint ( "MY_TRACE_POINT" )
    tracePoint.log ( "Action ended" )

Java:

    TracePoint tp = client.registerTracePoint ( "MY_TRACE_POINT" ) ;
    tracePoint.log ( "Action ended" ) ;

Each TracePoint can be activated and deactivated at runtime. This function can be rejected by overwriting the ConnectionHook.prototype.system(connection,event) method in the ConnectionHook class. Event.getName() is 'system' Event.getType() is 'log'

Release 1-4-0 New Heartbeat Protocol to ensure the Availability of Connections

Gepard is based on fast communication by means of always-open sockets. Therefore it is crucial to monitor these connections. This is achieved by a mechanism which exchanges packets between the broker and all connected clients in fixed time intervals defined by the broker. This interval is transmitted to clients as they connect.

The broker sends a PING message to the connected clients in each interval to which all clients are expected to respond with a PONG message within the three next intervals. If no such response is received by the end of the third interval, the broker closes the connection-socket.

On the other end, after dispatching a PONG message, the client waits for the next PING from the broker to arrive within 3 intervals. In case the subsequent PING is not received, the client closes the connection socket and emits a "disconnect" event to signal the status to the application.

If the client is configured to re-connect, it will try to establish a new connection to the broker in a pre-defined interval. On success, the client will emit a "reconnect" event to the application. All gepard-event-listeners which had been registered at the time of disconnect will then automatically be registered with the broker again.

Example time-out conditions are:

  • Broker restart after maintenance
  • Backup time of a virtual machine
  • Restart of a firewall

Parameter, details and example

Release 1-3-3 New FileContainer class for Python, JavaScript and Java to simplify file-transfer.

An instance of the FileContainer class may be inserted at any place inside the body of an Event.
If the client runs on the same machine as the broker only the full path-name of the file will be transferred.
If the broker runs on a different machine the content of the file is read in as a byte-array and transferred as payload to the broker.
If the broker detects a target on a different machine the file is read in and put into the event's body before sending the data.
This is done on a per connection basis.
See details

Release 1-3-0 Let's talk about Python

In this release a full featured Python client is included. The implementation is pure generic Python code. The features are:

  • emit event
  • listen to events
  • request / result ( messages )
  • semaphores ( synchronously / asynchronously )
  • locks

Controlling Connections and Actions with a Hook

In order to control connections and actions a default hook class is provided: ConnectionHook

This class contains several methods which are called in appropriate cases:

connect ( connection )
shutdown ( connection, event )
getInfoRequest ( connection, event )
addEventListener ( connection, eventNameList )
sendEvent ( connection, eventName )
lockResource ( connection, resourceId )
acquireSemaphore ( connection, resourceId )
clientAction ( connection, resourceId )
system ( connection, event )

Each of these methods must return an answer wether to allow or reject the corresponding action.
The answer must be either a boolean value or a Thenable which means a Promise object of any kind.
The default for shutdown is to return a false value if the incoming connection is not from localhost. In all other cases the default returns a true
The parameter can be used to test the allowance in a deeper way.
For example using a Promise for shutdown enables an asynchronous check with help of a database configuration.
To configure this hook a subclass of ConnectionHook must be implemented and defined as user-hook in an JSON configuration file:

{
    "connectionHook": "<path-to-javascript-code>/XmpConnectionHook"
}

This hook file is required with the start of the broker.
In this case the command to start the broker is:

gp.broker --config=<full-config-file-name>

An example for a user defined hook is the XmpConnectionHook.js file:

var util = require ( "util" ) ;
var ConnectionHook = require ( "gepard" ).ConnectionHook ;
var XmpConnectionHook = function()
{
    XmpConnectionHook.super_.call ( this ) ;
};
util.inherits ( XmpConnectionHook, ConnectionHook ) ;
XmpConnectionHook.prototype.connect = function ( connection )
{
    console.log ( "connection.getRemoteAddress()=" + connection.getRemoteAddress() ) ;
    return true ;
};
module.exports = XmpConnectionHook ;

If you prefer to start the broker from within your own JavaScript-program the configuration object can be set like:

    var b = new Broker() ;
    b.setConfig ( <config-object or path to config-json-file> ) ;
    b.listen() ;

The parameter connection in the above method-signatures is an internal used object with mainly the public useful methods:

  1. connection.isLocalHost()
  2. connection.getRemoteAddress()
  3. connection.getHostName()
  4. connection.getLanguage()
  5. connection.getApplicationName()
  6. connection.getApplication()
  7. connection.getId()

Perfect load balanced message handling.

The use-case for request/respond is enhanced to a perfect load balancing.
Suppose there are n message-listeners offering the same service-name ( event-name )
m messages come in to the broker with m = n + 1
The following is done:

  1. the first n messages are sent to the n free listener for processing the request.
  2. the m-th message is stored inside the broker waiting for any of the listeners sending back the response.
  3. after receiving the first message-response from any listener the waiting m-th + 1 message is sent to the now free listener.

As long as a sent message is not returned the Broker stores it in relation to the worker connection. If this connection dies the stored message is sent back to the originator marked with the fail state and appropriate text. The status can be tested with event.isBad() which returns true or false.

Java bindings for all features:

  • emit event
  • listen to events
  • request / result ( messages )
  • semaphores
  • locks

With this it is very easy to communicate or synchronize between JavaScript programs or webapps in a browser with a Java server or Java program.

The conversion from JSON to Java and vice versa is done with the Gson Google library for Java.

If you need special serialization / deserialization you may set the appropriate Gson instance in the Event-class statically with the method Event.setGson() ;

The Event-class may convert the json to map Java's byte[]-array to NodeJS's Buffer and vice versa. This can be set statically by Event.mapByteArrayToJavaScriptBuffer ( boolean state ). The default is true.

Use Cases

Configuration Changes (Events)

Suppose you have 1 program that changes configuration-entries in a database-table. After the new entries are committed the program sends an event with:

client.emit ( "CONFIG-CHANGE" ) ;

Several clients do their work based on these data.

All clients including web-clients setup a listener for example with

client.on ( "CONFIG-CHANGE", function callback(e) {} ) ;

Concurrent editing of a Dataset (Semaphores)

Two users with their web browser want to edit the same user-data in a database. In this case a Semaphore is very useful.


Both do
gepard.port = 17502 ;
var sem = new gepard.Semaphore ( "user:4711" ) ;
this.sem.acquire ( function sem_callback ( err )
{
    // we are owner
    fetch data, edit and save
 
    then:
 
    this.release() ; // with this statement the second user's browser app is callbacked
}) ;

Synchronization of file processing (Locks)

Suppose there are many files in a directory waiting to be processed.
Let's name the directory: foo/bar/input
In order to speed up the overall processing several identical programs should work together.
In this case Locks are very useful. The following should be done:

var fs     = require ( "fs" ) ;
var gepard = require ( "gepard" ) ;
var lock ;
 
var array = fs.readdirSync ( "foo/bar/input" ) ;
for ( var i = 0 ; i < array.length ; i++ )
{
    lock = new gepard.Lock ( array[i], function()
    {
        try
        {
            if ( ! this.isOwner() ) return ;
            .............. process file ................
        }
        finally
        {
            this.release() ;
        }
    } ) ;
}

A Nice Exotic Mixture of Programming Languages

Suppose the following: There are a couple of JavaScript and Python programs to interact with a database. The database changes. And it would be nice to not change modules for database access.
Especially if the new database is an Oracle database. Perhaps on Linux.
Everybody who had ever tried to install the appropriate NodeJS or Python module ended up in a mess of build, configuration and installation problems.
One of the nicest Java features is the total abstraction of the database handling via the JDBC api.
It is clear what to do: Use a Java Gepard client connected to a database and execute all simple REST kind of actions via request/respond on the basis of Gepard.
In this combination changing a database vendor is only a 10 second job changing the connection url and restart the Java client. Ok: another 5 seconds. But that's it.
No compilation, no installation no problems.

The Event Body

This member of an event is the holder for all payload-data. In all languages this is a hashtable with the restriction that the key must be of type string.

  • Java: Map<String,Object>
  • JavaScript: {} or in long form: Object
  • Python: {} which is in fact an object of type dict
  • PHP: [] which is in fact an object of type Array

Setter and getter are the appropriate methods Event.putValue(name,value) and Event.getValue(name).
value is either a scalar type, a hashtable with strings as keys and valid object types, a list containing valid object types or a a combination of all valid types. Thus a set of data like a tree can be used.
Note: Gepard's data exchange mechanism is NOT intended to transport serialized objects between clients.
The valid types are:

  • scalar type objects: string, int, double, number
  • array type objects:
    • Array ( [] )
    • List ( [] )
  • hashtable type objects:
    • Java: Map<String,Object>
    • Python: dict ( {} )
    • JavaScript: Object ( {} )

There are 2 type of objects which are treated by gepard in a special way:

  • dates
    • JavaScript: Date
    • Java: Date
    • Python: datetime.datetime
    • PHP: Date
  • bytes
    • JavaScript: Buffer
    • Java: byte[]
    • Python: bytearray, bytes ( bytes should not be used to send because in python < 3 bytes is a subclass of str, typeof byte == 'str' and thus cannot be detected by this mechanism)

In these cases an object is transferred from a generic class of a sender to the generic class of the receiver which means it is reconstructed in the target programming language.
Note on Python:
The built-in date class in python is not able to parse or format ISO date strings. In order to enable full interoperability related to dates the gapard module tries to import the well known dateutils module. This in turn imports the six module. If these modules are in python's module path the generic python date class can be used.
Details in:

Examples

Ready to use examples for JavaScript are located in .../gepard/xmp
Ready to use examples for Java are located in .../gepard/gepard/java/org.gessinger/gepard/xmp and compiled in .../gepard/java/lib/Gepard.jar
Ready to use examples for Python are located in .../gepard/python/xmp

Examples Short

Event listener

Adding a event-listener with the on() method may be done with a single event-name or a list of event-names.

JavaScript: client.on ( "ALARM", callback )
or client.on ( [ "ALARM", "BLARM" ], callback )

Java: client.on ( "ALARM", callback )
or client.on ( new String[] { "ALARM", "BLARM" }, callback )

Python: client.on ( "ALARM", callback )
or client.on ( [ "ALARM", "BLARM" ], callback )

PHP: client->on ( "ALARM", callback )
or client->on ( [ "ALARM", "BLARM" ], callback )

The callback will be called with an Event object of the appropriate name ( e.getName() or $e->getName() )

Application

    var gepard = require ( "gepard" ) ;
    var client = gepard.getClient() ;

Browser

    var client = gepard.getWebClient ( 17502[, host] ) ;

Code

client.on ( "ALARM", function event_listener_callback(e)
{
    console.log ( e.toString() ) ;
});

Java

import org.gessinger.gepard.Client ;
import org.gessinger.gepard.EventListener ;
import org.gessinger.gepard.Event ;
Client client = Client.getInstance() ;
 
client.on ( "ALARM"new EventListener()
{
    public void event ( Event e )
    {
        System.out.println ( e ) ;
    }
} ) ;

Python

import gepard
client = gepard.Client.getInstance()
 
def on_ABLARM ( event ):
    print ( "on_ALARM" )
    print ( event )
 
client.on ( "ALARM"on_ABLARM )

PHP

namespace Gepard;
require ( 'vendor/autoload.php' );
use Gepard;
$client = Client::getInstance();
$client->on ( 'ALARM', function($event) {
    echo ( $event ) ;
}) ;
 
``
 
Details in:
 
* JavaScript: [gepard/xmp/Listener.js](https://github.com/gessinger-hj/gepard/blob/master/xmp/Listener.js)
* Java: [gepard/java/org.gessinger/gepard/xmp/Listener.java](https://github.com/gessinger-hj/gepard/blob/master/java/src/org/gessinger/gepard/xmp/Listener.java)
* Python: [Listener.py](https://github.com/gessinger-hj/gepard-python/blob/master/python/xmp/Listener.py)
* PHP: [Listener.php](https://github.com/gessinger-hj/gepard-php/blob/master/xmp/Listener.php)
 
### Event Emitter
 
Application
 
```js
var gepard = require ( "gepard" ) ;
var client = gepard.getClient() ;
client.emit ( "ALARM",
{
    var thiz = this ;
    write: function()
    {
        thiz.end() ; // close connection after written
    }
});

Browser

var client = gepard.getWebClient ( 17502[, host] ) ;
client.emit ( "CONFIG-CHANGED" ) ;

Java

import org.gessinger.gepard.Client ;
Client client = Client.getInstance() ;
client.emit ( "ALARM" ) ;

Python

import gepard
client = gepard.Client.getInstance()
client.emit ( "ALARM" )

PHP

namespace Gepard;
require ( 'vendor/autoload.php' );
use Gepard;
$client = Client::getInstance();
$client->emit ( 'ALARM' );

Details in:

Locks

Application

    var gepard = require ( "gepard" ) ;
    var lock = new gepard.Lock ( "resid:main" ) ;

Browser

gepard.port = 17502 ;
var lock = new gepard.Lock ( "resid:main" ) ;

Code

lock.acquire ( function ( err )
{
    console.log ( this.toString() ) ;
    if ( this.isOwner() )
    {
        .........
        this.release() ;
    }
} ) ;

Java

import org.gessinger.gepard.Lock ;
Lock lock = new Lock ( "resid:main" ) ;
lock.acquire() ;
if ( lock.isOwner() )
{
    .........
    lock.release() ;
}

Python

import gepard
 
lock = gepard.Lock ( "resid:main" )
lock.acquire()
 
if lock.isOwner():
    ......................
    lock.release()
 

Details in:

Semaphores

Application

    var gepard = require ( "gepard" ) ;
    var sem = new gepard.Semaphore ( "user:4711" ) ;

Browser

gepard.port = 17502 ;
var sem = new gepard.Semaphore ( "user:4711" ) ;

Code

sem.acquire ( function ( err )
{
    console.log ( this.toString() ) ;
 
        .....................
 
    this.release() ;
} ) ;

Java

Asynchronously

import org.gessinger.gepard.Semaphore ;
import org.gessinger.gepard.SemaphoreCallback ;
final Semaphore sem = new Semaphore ( "user:4711" ) ;
sem.acquire ( new SemaphoreCallback()
{
    public void acquired ( Event e )
    {
        System.out.println ( sem ) ;
        .....................
        sem.release() ;
    }
}) ;

Synchronously

import org.gessinger.gepard.Semaphore ;
final Semaphore sem = new Semaphore ( "user:4711" ) ;
// with or without a timeout 
sem.acquire(5000) ;
 
if ( sem.isOwner() ) // if not timeout occured 
{
        .....................
    sem.release() ;
}

Python

Asynchronously

import gepard
 
def on_owner(sem):
    ................
    sem.release()
 
sem = gepard.Semaphore ( "user:4711" )
sem.acquire ( on_owner )

Synchronously

 
import gepard
 
sem = gepard.Semaphore ( name )
 
sem.acquire ( 5 ) # with or without a timeout 
 
if sem.isOwner():
    ...........
    sem.release()

Details in:

Request / Result

Send request

Application:

    var gepard = require ( "gepard" ) ;
    var client = gepard.getClient() ;

Browser:

    var client = gepard.getWebClient ( 17502[, host] ) ;

Code:

client().request ( "getFileList"
, function result(e)
    {
        console.log ( e.getBody().list ) ;
        this.end() ;
    });

Java

import org.gessinger.gepard.Client ;
import org.gessinger.gepard.ResultCallback ;
import org.gessinger.gepard.Util ;
import java.util.List ;
final Client client = Client.getInstance() ;
client.request ( "getFileList"new ResultCallback()
{
    public void result ( Event e )
    {
        if ( e.isBad() )
        {
            System.out.println ( "e.getStatusReason()=" + e.getStatusReason() ) ;
        }
        else
        {
            List<String> list = (List<String>) e.getBodyValue ( "file_list" ) ;
            System.out.println ( Util.toString ( list ) ) ;
        }
        client.close() ;
    }
}) ;
 

Python

import gepard
 
client = gepard.Client.getInstance()
 
def getFileList ( e ):
    if e.isBad():
        print ( e.getStatusReason() )
    else:
        print ( e.getValue ( "file_list" ) )
    e.getClient().close()
 
client.request ( "getFileList"getFileList )
 

Details in:

Send result

Application:

    var gepard = require ( "gepard" ) ;
    var client = gepard.getClient() ;

Browser:

    var client = gepard.getWebClient ( 17502[, host] ) ;

Code:

var list = [ "one.js", "two.js", "three.js" ] ;
client.on ( "getFileList", function ( e )
{
    e.getBody().list = list ;
    e.sendBack() ;
});

Java

import org.gessinger.gepard.Client ;
import org.gessinger.gepard.EventListener ;
import org.gessinger.gepard.Event ;
final Client client = Client.getInstance() ;
client.on ( name, new EventListener()
{
    public void event ( Event e )
    {
        String[] fileList = new String[] { "a.java""b.java""c.java" } ;
        e.putBodyValue ( "file_list", fileList ) ;
        e.sendBack() ;
    }
} ) ;

Python

import gepard
client = gepard.Client.getInstance()
 
fileList = [ "a.py", "b.py", "c.py" ] ;
def on_getFileList ( event ):
    print ( "Request in" ) ;
    print ( "File list out:" ) ;
    print ( fileList ) ;
    event.body["file_list"] = fileList ;
    event.sendBack() ;
 
client.on ( "getFileList"on_getFileList )

Details in:

Examples Long

Event listener

In Application

var gepard = require ( "gepard" ) ;
var c = gepard.getClient() ;
 
var eventName = "ALARM" ;
console.log ( "Listen for events with name=" + eventName ) ;
c.on ( eventName, function(e)
{
    console.log ( e ) ;
});
c.on('end', function()
{
    console.log('socket disconnected');
});
c.on('error', function ( event)
{
    console.log( event );
});
c.on('shutdown', function()
{
    console.log('broker shut down');
});

In Browser

<script type="text/javascript" src="Event.js" ></script>
<script type="text/javascript" src="MultiHash.js" ></script>
<script type="text/javascript" src="GPWebClient.js" ></script>
        var wc = gepard.getWebClient ( 17502 ) ;
        this.wc.on ( "open", function onopen()
        {
        }) ;
        this.wc.on ( "error", function onerror()
        {
        }) ;
        this.wc.on ( "close", function onclose()
        {
        }) ;
        wc.on ( "ALARM", function event_listener_callback ( e )
        {
            console.log ( e.toString() ) ;
        }) ;

Event Emitter

In Application

var gepard  = require ( "gepard" ) ;
var c = gepard.getClient() ;
 
var event = new gepard.Event ( "CONFIG-CHANGED" ) ;
event.setBody ( { "config-name" : "app.conf" } ) ;
c.emit ( event,
{
    write: function() // close connection after write
    {
        c.end() ;
    }
});

In Browser

<script type="text/javascript" src="Event.js" ></script>
<script type="text/javascript" src="MultiHash.js" ></script>
<script type="text/javascript" src="GPWebClient.js" ></script>
var wc = gepard.getWebClient ( 17502 ) ;
var event = new gepard.Event ( "CONFIG-CHANGED" ) ;
event.setBody ( { "config-name" : "app.conf" } ) ;
wc.emit ( event ) ;

File Transfer with the FileContainer Class

The basic usage of this class is as follows:

FileSender

JavaScript:
See also: gepard/xmp/FileSender.js

var gepard  = require ( "gepard" ) ;
var client = gepard.getClient() ;
var event = new gepard.Event ( "__FILE__" ) ;
var file = "<full-file-name>" ;
event.putValue ( "DATA", new gepard.FileContainer ( file ) ) ;
client.request ( event, function ( e )
{
    if ( e.isBad() )
    {
        console.log ( e.getStatus() ) ;
    }
    else
    {
        console.log ( "File " + file + " sent successfully." )
    }
    this.end() ;
}) ;

Python:
See also: gepard/python/xmp/FileSender.py

client = gepard.Client.getInstance()
 
event = gepard.Event ( "__FILE__" )
 
file = "<full-file-name>" ;
event.putValue ( "DATA"gepard.FileContainer ( file ) )
 
def result ( e ):
    if e.isBad():
        print ( e.getStatusReason() )
    else:
        print ( "File " + file + " sent successfully." )
    e.getClient().close()
 
print ( "Sending " + file )
client.request ( eventresult )

Java:
See also: gepard/java/org.gessinger/gepard/xmp/FileSender.java

        final Client client = Client.getInstance() ;
 
        Event event = new Event ( "__FILE__" ) ;
        final String file = "<full-file-name>"
        event.putValue ( "DATA"new FileContainer ( file ) ) ;
        client.request ( event, new ResultCallback()
        {
            public void result ( Event e )
            {
                if ( e.isBad() )
                {
                    System.out.println ( e ) ;
                }
                else
                {
                    System.out.println ( "File " + file + " sent successfully." ) ;
                    System.out.println ( "code: " + e.getStatusCode() );
                    System.out.println ( "name: " + e.getStatusName() );
                    System.out.println ( "reason: " + e.getStatusReason() );
                }
                client.close() ;
            }
        }) ;

FileReceiver

JavaScript:
See also: gepard/xmp/FileReceiver.js

    var client = gepard.getClient() ;
    client.on ( "__FILE__", function(e)
    {
        var data = e.removeValue ( "DATA" ) ;
        console.log ( data.getName() + " received." ) ;
        var fname = data.getName() + ".in" ;
        try
        {
            data.write ( fname ) ;
            console.log ( fname + " written." ) ;
        }
        catch ( exc )
        {
            e.control.status = { code:1, name:"error", reason:"could not write: " + fname } ;
            console.log ( exc ) ;
        }
        e.sendBack() ;
    });

Python:
See also: FileReceiver.py

client = gepard.Client.getInstance()
 
def on___FILE__ ( e ):
    data = e.removeValue ( "DATA" )
    print ( data.getName() + " received." ) ;
    fname = data.getName() + ".in"
    try:
        data.write ( fname ) ;
        print ( fname + " written.")
    except Exception as exc:
        print ( exc )
    e.sendBack() ;
 
client.on ( "__FILE__", on___FILE__ )

Java:
See also: gepard/java/org.gessinger/gepard/xmp/FileReceiver.java

final Client client = Client.getInstance() ;
client.on ( "__FILE__"new EventListener()
{
    public void event ( Event e )
    {
        try
        {
            FileContainer fileContainer = (FileContainer) e.removeValue ( "DATA" ) ;
            String fname = fileContainer.getName() + ".in" ;
            fileContainer.write ( fname ) ;
            System.out.println ( fname + " written." );
            e.setStatus ( 0"success""File accepted." ) ;
        }
        catch ( Exception exc )
        {
            e.setStatus ( 1"error""File not saved." ) ;
            System.out.println ( Util.toString ( exc ) ) ;
        }
        try
        {
            e.sendBack() ;
        }
        catch ( Exception exc )
        {
            System.out.println ( Util.toString ( exc ) ) ;
        }
    }
} ) ;
 

Heartbeat and Reconnection Capability Parameterization

Broker Side

The default ping interval for the broker is 180000 milli-sec or 3 minutes. This value can be changed in different ways:

  1. Startup parameter: --gepard.heartbeat.millis=<nnn>
  2. Evironment variable: GEPARD_HEARTBEAT_MILLIS=<nnn>
  3. Variable in configuration-file: { "heartbeatMillis":<nnn> }
  4. In program: broker.setHeartbeatMillis ( <nnn> )

Client Side

The default is reconnect=false

This value can be changed in different ways:

  1. Startup parameter: --gepard.reconnect=true
  2. environment variable: export GEPARD_RECONNECT=true
  3. client.setReconnect ( true ) before any socket connection is active
  4. gepard.setProperty ( 'gepard.reconnect', 'true' )

If the boker is shut down all clients receive a shutdown event. If reconnect==true the appropriate callback in the client is called and the client api tries to reconnect.
At this place a call to client.setReconnect(false|False) initiates a Client-shutdown without reconnection.
There are two new callbacks available signaling the state-change to the owner-application:

  • Java

    1. client.onReconnect ( InfoCallback icb )
    2. client.onDisconnect ( InfoCallback icb )
  • Python

    1. client.onReconnect ( <callback> )
    2. client.onDisconnect ( <callback> )
  • JavaScript

    1. client.on ( "reconnect", <callback> )
    2. client.on ( "disconnect", <callback> )

Example to test

  1. open a terminal, execute: node_modules/.bin/gp.broker or node node_modules/gepard/src/Broker.js
  2. open a terminal, execute: node node_modules/gepard/xmp/Listener.js
  3. open a terminal, execute: python node_modules/gepard/python/xmp/Listener.py
  4. open a terminal, goto node_modules/gepard/java
    execute: java -cp lib/Gepard.jar:lib/gson-2.3.1.jar org.gessinger.gepard.xmp.Listener

Then goto terminal one and kill the Broker either with ^C ( ctrl+C ) or with kill -9 <pid> or with the taskmanger on windows.
Appropriate output is visible.
Then start the Broker again and all clients reconnect again. Check with gp.info that all event-listener are registered again.

The TracePoint Concept

TracePoints or short TP are used to monitor a data flow at specific places in an application. The combination of all traced-data helps to analyze and control the behaviour especially within a distributed system..
It is very important to use these TracePoints immediately in a running system without changing static configuration and system-restart.
All defined TracePoints in a Gepard-based distributed application can be switched on/off and reconfigured at runtime on behalf of the Admin programm. TracePoint commands are sent to the running Broker and forwarded to all or selected clients.

TracePoints in the Broker

There are 2 predefined TPs in the Broker. Each TP in the context of a program has a unique name to address commands to.

  1. EVENT_IN If this TP is switched on all incoming events are logged to the Broker's log-file.
  2. EVENT_OUT If this TP is switched on all outgoing events are logged to the Broker's log-file.

The status of these TPs can be viewed with the command: gp.tplist
The output reads as:

{ tracePointStatus:
     { name: 'broker',
         list:
            [ { name: 'EVENT_IN', active: false },
                { name: 'EVENT_OUT', active: false } ],
         output: 'local' } }

Switching the TPs is done with the commands:

  • gp.tpon [ <tp-name> { , <tp-name> }] switch on all or given TPs
  • gp.tpoff [ <tp-name> { , <tp-name> }] switch off all or given TPs
  • gp.tp toggle all TPs

In all cases the current status of the TPs is shown.

TracePoints in the Client

There are 2 predefined TPs in each client. Each TP in the context of a program has a unique name to address commands to.

  1. EVENT_IN If this TP is switched on all incoming events are logged.
  2. EVENT_OUT If this TP is switched on all outgoing events are logged.

The status of these TPs can be viewed with the command: gp.client.tplist
The output reads as:

{ tracePointStatus:
     { name: 'broker',
         list:
            [ { name: 'EVENT_IN', active: false },
                { name: 'EVENT_OUT', active: false } ],
         output: 'local' } }

Switching the TPs is done with the commands:

  • gp.client.tpon [ <tp-name> { , <tp-name> }] [ --output=remote|local ] [ --sid=<sid-of-specific-client> ]
    switch on all or given TPs
    optional: direct output to local or remote to the Broker.
  • gp.client.tpoff [ <tp-name> { , <tp-name> }] [ --output=remote|local ] [ --sid=<sid-of-specific-client> ]
    switch off all or given TPs
    optional: direct output to local or remote to the Broker.
  • gp.client.tp [ --output=remote|local ] [ --sid=<sid-of-specific-client> ]
    toggle all TPs
    optional: direct output to local or remote to the Broker.

In all cases the current status of the TPs is shown.

Examples with 2 clients:

  • gp.client.tplist
{ tracePointStatus:
     { name: 'client',
         list:
            [ { name: 'EVENT_IN', active: false },
                { name: 'EVENT_OUT', active: false },
         output: 'local' },
    sid: '::ffff:127.0.0.1_44109_1452621748330',
    applicationName: 'Listener' }
{ tracePointStatus:
     { name: 'client',
         list:
            [ { name: 'EVENT_IN', state: false },
                { name: 'EVENT_OUT', state: false } ],
         output: 'local' },
    sid: '::ffff:127.0.0.1_44246_1452621873697',
    applicationName: 'org.gessinger.gepard.xmp.Listener' }
  • gp.client.tplist --sid=::ffff:127.0.0.1_44246_1452621873697
{ tracePointStatus:
     { name: 'client',
         list:
            [ { name: 'EVENT_IN', state: false },
                { name: 'EVENT_OUT', state: false } ],
         output: 'local' },
    sid: '::ffff:127.0.0.1_44246_1452621873697',
    applicationName: 'org.gessinger.gepard.xmp.Listener' }
  • gp.client.tp --sid=::ffff:127.0.0.1_44246_1452621873697 --output=remote
{ tracePointStatus:
     { name: 'client',
         list:
            [ { name: 'EVENT_IN', state: true },
                { name: 'EVENT_OUT', state: true } ],
         output: 'remote' },
    sid: '::ffff:127.0.0.1_44246_1452621873697',
    applicationName: 'org.gessinger.gepard.xmp.Listener' }

Zeroconf Usage in Detail

Zeronconf on the Broker's Side

If configured the gepard Broker publishes a service in the local subnet and can be discovered by any interested client.
The fully qualified domain name (FQDN) for the service consists out of 3 parts:

  1. name, e.g. Broker

  2. type, e.g. gepard

  3. protocol, tcp.local

The FQDN is derived from this parameters as: __Broker.gepard.tcp.local
This name and type can be defined by

  1. an entry in the broker's json config
    The form is either a comma separated list like

    {
        "zeroconf": [<name>,]<type> [ ,<port>|0]
    }

    or

    {
        "zeroconf": { "name":<name> , "type": <type> [ , "port": <port>|0] }
    }
  2. a startup parameter of the form: --gepard.zeroconv=[<name>,]<type>[,<port>]

  3. an environment variable of the form: export GEPARD_ZEROCONF=[<name>,]<type>[,<port>]

  4. calling the method broker.setZeroconfParameter ( [<name>,]<type> [ ,<port>] )

If only the <type> is given the <name> is choosen to be:

Gepard-[H:<hostname>]-[P:<port>]

This postfix -[H:<hostname>]-[P:<port>] is always appended to make the name unique.
If the <port> is not given the standard definitions are used. If the port is exactly 0 a random free port is choosen. Thus no special arrangement is needed for running several brokers on one machine. The TXT segment contains a comma-list of all registered event-names as TOPIC entry and a comma-list of all channels as CHANNELS entry. With this an interested client can choose a a Broker depending on this information e.g. with the methods

  • service.topicExists(<topic-name>) and

  • service.channelExists(<channel-name>)

This convention is expanded because of the lack of proper parsing the TXT segment of the advertised service in the python zeroconf-module. In addition to the TXT segment the advertised service-name contains the comma-lists of topics and channels if given. The format is:

[T:topic1,topic2,...] and [C:channel1,channel2,...]

With this information a client can make a profound decision whether to connect to a found broker or ignore it and search another instance.

Zeroconf on the Client's Side

Up to now only the JavaScript and Python flavors works out of the box. There is no reliable pure Java implementation available.

Suppose the broker is started with test-gepard,0. (service-type is test-gepard and port is arbitrary)

gp.broker --gepard.zeroconf=test-gepard,0

An interested listener would do the following:

JavaScript

var gepard = require ( "gepard" ) ;
var client = gepard.getClient ( { type: 'test-gepard' }, function acceptService ( service )
{
    // optional e.g.: ignore service which is not on localhost with:
    // if ( ! service.isLocalhost() ) return false ;
    return true ;
} ) ;
client.setReconnect ( true ) ; // This is for re-connect if broker dies.
                               // in this case the above function <b>acceptService</b> is re-used.
client.on ( "ALARM", (e) => console.log ( e ) ) ; // The "ALARM" listener is registered.

or simpler:

var client = gepard.getClient ( 'test-gepard' ) ;
client.setReconnect ( true ) ;
client.on ( "ALARM", (e) => console.log ( e ) ) ;

Example: ZeroconfListener.js

Python

import gepard
 
def acceptService ( client, service ):
    # optional e.g.: ignore service which is not on localhost with:
    # if not service.isLocalhost() return False
    return True
 
def on_ALARM ( event ):
    print ( event )
 
client = gepard.Client.getInstance('test-gepard',acceptService)
client.setReconnect ( True )
client.on ( "ALARM", on_ALARM )

or simpler:

def on_ALARM ( event ):
    print ( event )
gepard.Client.getInstance('test-gepard').setReconnect ( True ).on ( "ALARM", on_ALARM )

Example: ZeroconfListener.py

If a client uses no connection parameter it can be parametrised by

  • start-parameter: --gepard.zeroconf.type=[localhost:]<type> or

  • environment-parameter: export GEPARD_ZEROCONF_TYPE=[localhost:]<type>

localhost: is optional and indicates to include only Broker running on the same host.

In this case the code to write is minimal:

JavaScript

var client = gepard.getClient() ;
client.setReconnect ( true ) ;
client.on ( "ALARM", (e) => console.log ( e ) ) ;

Python

def on_ALARM ( event ):
    print ( event )
 
client = gepard.Client.getInstance()
client.setReconnect ( True )
client.on ( "ALARM", on_ALARM )

Thus the behaviour of a client can be easily changed with only external parameters without any code-change.

An interested emitter would do the following:

JavaScript

var gepard = require ( "gepard" ) ;
var client = gepard.getClient ( 'test-gepard', function acceptService ( service )
{
    if ( service.getTopics().indexOf ( "ALARM" ) < 0 ) // ignore if listener does not exist
    {
        return ;
    }
    client.emit ( "ALARM",
    {
      write: function() // The event is sent -> end connection and exit
      {
        client.end() ;
      }
    });
    return true ;
} ) ;

Python

def acceptService ( client, service ):
    print ( service )
    if service.isLocalHost():
        client.emit ( "ALARM" )
        client.close()
        return True
    return False
 
client = gepard.Client.getInstance ( 'test-gepard', acceptService )

or with external parametrisation use simply the simple Emitter.py in python/xmp and start it as:

python Emitter.py --gepard.zeroconf.type=test-gepard

Example: ZeroconfEmitter.js,ZeroconfEmitter.py

If no timout is specified the service lookup never ends if no valid broker is found.
If a listener connects a broker renews its service advertisement. This leads to a recall of the interested client-callback and the event can be sent.

The optional timout in milli-seconds is given as:

var client = gepard.getClient ( { timeout:10000, type:'test-gepard' }, <callback> )

The service (JavaScript,Python) parameter can be used to get

  • service.getTopics()
    list of registered event-names
  • service.getChannels()
    list of registered channels
  • service.getHost()
    host-name where the found broker is running.
  • service.isLocalHost()
    whether the found broker is on the same host as the client.

If the client is configured to reconnect automatically in case the broker dies all existing event-names are re-registered upon connection to another discovered broker.
This service- / broker-lookup is done as soon as the old broker has gone.
Conclusion: The set-up with one broker is no more a single point of failure.
In case of broker-failure all clients search another broker and register their listeners automatically.
The method gepard.findService ( { type:<type> }, callback ) can be used to discover all services in the subnet of given type. If the callback returns true the search ends.
To monitor services the file MDNSLookup.js can be used.
Example to find any service for a given type:

gepard.findService ( { type:<type> }, (service) => {
    if ( service.host === os.hostname() )
    {
        console.log ( service ) ;
      return true ;
    });

How to use a Standard Webserver as a Proxy for the WebSocketEventProxy

If you use a standard web-server like apache, nginx or iis the web-socket communication is iniated with a so called Upgrade request based on the http protocol.

This request must be re-directed to the running WebSocketEventProxy which in turn is connected to a running Broker. Re-directions must be configured in the appropriate web-server's configuration. Examples:

WebSocket Configuration Example for nginx

The default localhost port 80 is passed to port 8888 of the example http-server in gepard's example. The web-socket upgrade request is passed to a running WebSocketEventProxy. In this case the WebClient's url must be: ws://localhost/ws:

var client = gepard.getClient ( "ws://localhost/ws")

If the connection is based on https then the url is wss://localhost/ws

The sample are prepared to detect a proxy and use the appropriate web-socket parameters. A sample nginx config file is located in: .../node_modules/gepard/xmp/nginx.conf

http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
 
    ## --------------- nginx as proxy for node based http server --------------------
    map $http_upgrade $connection_upgrade {                                         #
        default upgrade;                                                            #
        '' close;                                                                   #
    }                                                                               #
    ## --------------- nginx as proxy for gepard based websocket proxy --------------
    upstream websocket {                                                            #
        server localhost:17502; # WebSocketEventProxy listens here                  #
    }                                                                               #
 
    server {
        ## ------------------- standard port for incoming requests ------------------
        listen       80;
        server_name  localhost;
 
        location / {
            ## ------------------- forwarding to HttpSimple -------------------------
            proxy_pass http://localhost:8888;                                       #
            proxy_http_version 1.1;                                                 #
            proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for_by_nginx;    #
            proxy_set_header Host $host;                                            #
        }
        location /ws {
            ## ------------------- forwarding to WebSocketEventProxy ----------------
            proxy_pass http://websocket;                                            #
            proxy_http_version 1.1;                                                 #
            proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for_by_nginx;    #
            proxy_set_header Host $host;                                            #
            proxy_set_header Upgrade $http_upgrade;                                 #
            proxy_set_header Connection $connection_upgrade;                        #
        }
 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Technical Aspects of the Client

NodeJS clients use the powerful but simple framework for asynchronously callbacks.
In Java and Python this asynchronicity is modeled by threads. There is a single thread reading from the socket connected to the Broker. Incoming events are dispatched and the appropriate callbacks are executed. The main thread is not affected.
Thus any number of event listener may be registered or removed in the main thread.
Synchronous callbacks are needed with Locks and Semaphores. In this case an id-based blocking message queue is used for communication between the threads.
Incoming events for asynchronous processing are propagated to separate running worker threads via a blocking fifo queue. Thus callbacks do not block each other. This applies to Java and Python.
By default 2 threads are running. This can be changed with the method Client.setNumberOfCallbackWorker(). Maximum is 10.
From this it is clear that all callback methods run in the context of one of the internal worker-threads and not in the context of the main thread.
Per default the internal thread is not a daemon thread. If needed this can be changed by calling the method

  • Python: Client.setDaemon([True|False])
  • Java: Client.setDaemon([true|false])

before the first action on a Client instance because the internal thread is started when the first connection is needed.

Found a bug? Help us fix it...

We are trying our best to keep Gepard as free of bugs as possible, but if you find a problem that looks like a bug to you please follow these steps to help us fix it...

  • Update Gepard and make sure that your problem also appears with the latest version of Gepard.

  • Goto the issue tracker to see if your problem has been reported already. If there is no existing bug report, feel free to create a new one. If there is an existing report, but you can give additional information, please add your data to this existing issue. If the existing issue report has already been closed, please only re-open it or comment on it if the same (or a closely related issue) re-appears, i.e., there is a high chance that the very same bug has re-appeared. Otherwise, create a new issue report.

  • Whenever you create a new issue report in our issue tracker, please make sure to include as much information as possible like exceptions with text and stack trace or other log information.
    Having all the required information saves a lot of work.

https://github.com/gessinger-hj/gepard/blob/master/CHANGELOG.md

Contributors

  • Hans-Jürgen Gessinger
  • Paul Gessinger

Features

  • High performance
  • Minimal configuration with
    • GEPARD_PORT
    • GEPARD_HOST
  • All JavaScript client features like event listener, event emitter, semaphores, locks and messages are available in any web-browser apps.
  • All client features are also available for Java and Python

Changelog

See change log details

Package Sidebar

Install

npm i gepard

Weekly Downloads

20

Version

1.9.4

License

MIT

Last publish

Collaborators

  • gessinger-hj