Wiki

Clone wiki

luci2 / Luci networking or how to write a connection library in your preferred language

TCP / Websockets

Luci is primarily intended to connect machines in a LAN. Still, it allows for web apps to connect to it using javascript and websockets. It comes with a Javascript API that abstracts this for regular web development. Just include it in your webpage with <script src="luciConnect.js"></script>. The two networking types share the JSON formatting while binary attachments are handled differently.

##JSON Luci's communication protocol is JSON based. It's designed for asynchronous execution of remote procedures (services). Unlike in RPC protocol, client requests don't include an ID. IDs (callIDs) are globally created by Luci and sent back as the first answer to any request as {'newCallID':1}. A client can ask for execution of a service by sending a request like {'run': 'ServiceA'} or it can ask for execution of a predefined task that is represented as a node in Luci's flow diagram editor. A task is identified by a taskID. Therefore a request to run a task looks like {'run': 5} or like {'run':[5,6]} if you want to run more than one task at once, e.g. all tasks represented by the most left nodes in the flow diagram. The first answer to every request is {'newCallID':x}. The second answer is one or many progress notifications that refer to the callID. It might indicate the progress with a number (integer) between 0 and 100 and contain intermediate results. The last answer is the final result also referring to callID. Error messages can be returned any time after the newCallID answer. Error messages are not followed by any subsequent answers.

Requests:

  • run: {'run': 'ServiceA'}
  • cancel: {'cancel': callID (integer)}

Answers:

  • Result: {'result':{},'callID':integer,'serviceName':string,'taskID':integer (0 if the service has been run not as a task but has been called "directly")}
  • Progress: {'progress':{<intermediate result>},'callID':integer, 'serviceName':string,'taskID':integer,'percentage':integer}
  • Error: {'error':string}

Refer to luci.core.ServiceWriterComplete#doNotifications to get a precise idea of Luci's answers.

Luci sends a progress answer whenever a service is being started with percentage being 0. This message is also being sent for tasks being chained to each other in a flow diagram. This is how a client is being notified of service execution start events.

###JSON input/output parameter description While services expect inputs to be json formatted (with binary attachments being described below) its specification is described using an additional rules set on top of json as follows:

  • JSON keys can have one of the modifiers XOR, OPT, ANY.
    • Keys preceeded by XOR allow only one of the marked keys to be given as an input.
    • Keys preceeded by OPT are optional keys.
    • Keys preceeded by ANY allow to use any name as a key. ANY can be used only once in a json object.
  • Type specification: Input/Output specifications are allowed to consist of arbitrary levels of hierarchy (both nested objects as well as nested arrays). The leafs nevertheless must denote one of the types json, list, string, number, boolean, attachment, jsongeometry, any or a list with a selection of these types like [attachment, jsongeometry].

##Attachments (TCP) Luci messages can contain binary data. It is attached to messages as byte arrays. While every message must contain a JSON "header", attachments are optional. And while the UTF8 encoded JSON header is human-readable, a complete luci message consists not only of a json string but also of a thin binary wrapper around the json header: 16 big endian bytes before the json header at the very beginning of every Luci message.

luci_message.png

The first 8 bytes encode the byte length of the header, the second 8 bytes encode the length of the rest, the attachment part. The attachment part itself starts with 8 bytes encoding the amount of attachments and every attachment byte array is preceded by additional 8 bytes encoding the length of the following attachment. Attachments must be referenced in the json header by a json object that is structured as follows:

#!javascript
{   'format':'string',
    'attachment':{
        'length':'number',
        'checksum':'string',
        'position':'integer (starting at 1; 0 = undefined position)'
    }
    'OPT name':'string',
    'ANY key':'string'
}

Attachments can be referenced multiple times in a JSON header.

##Attachments (Websockets) Since the websocket protocol distinguishes between binary and text messages and since it is intended for many relatively small messages like chat messages Luci expects websocket attachments to be regular HTTP POST requests and provides attachments as regular file downloads (HTTP GET requests). the download URL is composed as http://<(local)host>/<checksum>.<format>. The POST requests must/can contain special headers as follows:

  • name: the name of the attachment (to preserve filenames)
  • guid: A globally unique ID that is included by the json message sent on websockets if it references attachments. It is the same for all attachments being referenced by the same header.
  • position: An integer indicating the position in the sequence of attachments. While this is essential for TCP attachments, for web attachments it is used to identify attachments that were sent without a checksum.
  • checksum: Optional header that is set by the Javascript API if the attachment to be sent is an ArrayBuffer object. If the attachment is a file, it is sent without the checksum, leaving it up to the webserver to calculate it. In this case the attachment reference in the JSON header looks as follows:
    #!javascript
    {   'format':'string',
        'POST':'number'
    }
    
    With POST being the same number as position in a TCP attachment.

IN CONTRAST TO TCP ATTACHMENTS, ALL WEB ATTACHMENTS ARE BEING DOWNLOADED TO LUCI FIRST BEFORE THEY GET TRANSMITTED TO ANY REMOTE SERVICES. TCP attachments - if they need to be forwarded to only one service - are forwarded directly to remote services, i.e. LUCI will not wait until the whole attachment is being transferred to LUCI before sending it to a remote service.

##JSONGeometry Similar to attachments a JSON header can also contain JSON encoded geometry like GeoJSON. Since there are several JSON based geometry formats (e.g. TopoJSON) a JSONGeometry object must follow this structure:

#!javascript
{   'format':'string',
    'geometry':{<format specific json object>},
    'OPT name':'string',
    'OPT crs':'string',
    'OPT attributeMap':'json',
}

##Remote Services Luci allows a client to be registered as a service. Once a client is registered as a service it is being treated like all other services registered to / locally loaded in Luci. While normal clients have to be able to process result, progress, error answers, clients registered as a service additionally have to be able to procress run and cancel messages:

  • run: {'run':'ServiceBRemote'}; being sent by clients and eventually forwarded by Luci to the remote service.
  • cancel: {'cancel':callID}; being sent by clients and eventually forwarded by Luci to the remote service.

Updated