Woopsa Protocol Specifications

Version 1.2 — 12.08.2016

Table of contents

Introduction

Woopsa stands for Web Object-Oriented Protocol for Software and Automation.

It was designed with simplicity and interoperability in mind. The object-oriented part of the name means that data in Woopsa is hierarchical.

Definitions

Server
A Woopsa Server accepts connections from clients and publishes data, making it available through the Woopsa Interface.
Client
A Woopsa Client connects to a Woopsa Server and reads and writes data, invokes functions and explores the metadata using the Woopsa Interface
Metadata
Metadata is a piece of data that represents the hierarchy of an object. It does not contain actual data.

The Woopsa Interface

Because data in Woopsa is hierarchical, an interface must be specified which describes how data is structured and the kind of data that's available. This is called the Woopsa interface and it is described below. This section does not describe how data is serialized or transported.

The Object Model

Full Woopsa interface UML diagram

Data exchanged between Woopsa servers and clients must conform to the above object model. Everything in Woopsa is a IWoopsaElement, which means that everything in woopsa has a Name and thus a path. However, IWoopsaElement and IWoopsaContainer used only for hierarchy's sake and only IWoopsaObjects are ever instanciated.

The Woopsa Value Types

There is a total of 10 different types of data that can be used in Woopsa. Their serialization is discussed in the Serialization section below.

Null
Used to represent absence data — for example, this is the ReturnType for void methods
Logical
Boolean true/false value
Integer
64-bit integer value
Real
Real number, floating-point representation
DateTime
A moment in time
TimeSpan
A duration of time
Text
A chain of characters representing a string.
WoopsaLink
Link to another WoopsaElement in the object tree
JsonData
Any data represented in the JSON format
ResourceUrl
A URL to a resource external to the Woopsa server

Woopsa Values

Woopsa values can be represented internally in many different ways. The implementation is not restricted to any particular method of storing the data. However, every IWoopsaValue needs an AsText representation. This representation should not change if the internal value hasn't changed.

Common to every IWoopsaValue is also its Type and an optional TimeStamp which is meant to represent the date/time at which this value became effective.

Communications protocol

Transport

Communication between a client and a server is always initiated by a client using the HTTP 1.1 protocol. Only the GET and POST methods are used. Woopsa servers are available on a root path (URI) called the route prefix.

All requests by the client are thus made on URLs of the following form:

http://{server-address}/{route-prefix}/{woopsa-verb}/{woopsa-path}

When the client has no data to send to the server (read and meta), requests are made using GET and the URL is the only piece of information needed by the server.

When the client has data to send to the server (write and invoke), requests are made using POST and the HTTP Content-Type must be application/x-www-form-urlencoded. Form data must be represented the way it would be if it were JSON-serialized. This means, for example, that numbers need to use a "dot" (.) as the decimal separator, and must not have any thousand separators.

The Woopsa path is represented as a standard HTTP path URI. The path separator is thus also the / character.

Serialization

The serialization method chosen for Woopsa is JSON.

Only server responses are encoded as JSON. Requests from clients are described above, and use standard HTTP methods for sending data.

The following list details Woopsa types and their JSON representation:

Null
JSON null.
Integer, Real
JSON number.
DateTime
JSON string, representing the date in ISO-8601 as described in section 15.9.1.15.
TimeSpan
JSON number, representing a time duration in seconds (can be fractional).
Text
JSON string.
WoopsaLink
JSON string represented in the following manner: {server-url}#{woopsa-path}, where
{server-url}
The complete URL of the server, including the route prefix. Can be omitted if the element resides on the same server, in which case the # sign must also be omitted.
{woopsa-path}
The complete path of the Woopsa element
JsonData
JSON object, array or any other valid JSON construct.
ResourceUrl
JSON string representing any URL (http://, ftp://, etc...)

Verbs

Verbs are a way in Woopsa for the client to tell the server what to reply. There are only 4 verbs in Woopsa.

Meta

The meta verb allows the client to receive the entire structure of a Woopsa object specified by {path}.

To get the root object, the path must be blank. meta is not recursive: if a client needs to get the entire hierarchy of the object, it must make requests for every inner object.

HTTP Method:
GET
Usage:
/meta/{path}
Return value:
{
  "Name": string,
  "Items": [string],
  "Properties": [{
    "Name": string,
    "Type": string,
    "ReadOnly": boolean
  }],
  "Methods": [{
    "Name": string,
    "ReturnType": string,
    "ArgumentInfos": [{
      "Name": string,
      "Type": string
    }]
  }]
}

Read

The read verb allows the client to get the value of a Woopsa property specified by {path}.

The Value field returned by the server can be any valid JSON construct, usually string or number, but can also be an entire JSON object when Type is JsonData

The TimeStamp field returned by the server is optional.

HTTP Method:
GET
Usage:
/read/{path}
Return value:

A JSON-serialized WoopsaValue:

{
  "Value": number/string/array/object,
  "Type": string,
  "TimeStamp": string
}

Write

The write verb allows the client to write a value to a Woopsa property specified by {path}.

The server replies with the value that was written.

HTTP Method:
POST
Usage:
/write/{path}
HTTP POST Variables:
  • value: The value to write to the property. Must be a string or number. Writing to JsonData is not enforced in any way and its implementation is left to those who might need it.
Return value:
A JSON-serialized WoopsaValue, see read

Invoke

The invoke verb allows the client to invoke (call) a Woopsa method specified by {path}.

The server replies with the return value of the function if the function is non-void. In the case of a void function, no data is returned by the server.

HTTP Method:
POST
Usage:
/invoke/{path}
HTTP POST Variables:
A key/value pair of arguments for the method.
Return value:
A JSON-serialized WoopsaValue, see read

Error handling

Error transport

Some situations can cause errors in Woopsa, for example when trying to write to a read-only property or when an invoked method throws an exception. Errors can happen on any verb, so client libraries must handle these possible situations accordingly.

In the case of an error, the HTTP status sent as a response must not be of HTTP 200 OK. Instead, the status code should be one of the standard HTTP error codes defined in RFC 2616, section 10. The HTTP status text should contain a detailed description of the problem. This allows the client to quickly know there was an error by looking only at the headers.

The content of the response from the server is a JSON representation of the error, including the error type:

{
  "Error": true,
  "Message": string,
  "Type": string
}

Error types

The user is free to create new error types. However, the following types are defined in Woopsa and should not be used for other purposes:

WoopsaException
A generic type for errors that happen inside the Woopsa server.
WoopsaNotFoundException
This error is thrown when the client has tried to access an non-existant property, method or item. HTTP status code will be set to 404 Not Found.
WoopsaInvalidOperationException
This error is thrown when the client has made an invalid request to the server, such as trying to invoke a method with the wrong parameters or trying to write to a read-only property. HTTP status code will be set to 400 Bad Request.
WoopsaNotificationsLostException
This error applies only to the SubscriptionService. Thrown when the inner queue of notifications on the service has been filled up completely and old notifications must be erased to make space for new notifications.
WoopsaInvalidSubscriptionChannelException
This error applies only to the SubscriptionService. Thrown when the client makes a reference to an unexisting or invalid subscription channel. This usually means that the server has been reset between two WaitNotification calls, and that the client must re-create the SubscriptionChannel

Native extensions

Extensions in Woopsa allow to extend the basic functions of the protocol. They are found on the root object and can be methods or entire objects.

Publish/Subscribe with the SubscriptionService Woopsa object

The SubscriptionService is a WoopsaObject that lies at the root of a Woopsa server when it supports notifications. It contains 4 Woopsa methods that allow the client to create a Subscription Channel, add/remove Subscriptions, and receive notifications. If a server does not implement notifications, it should simply not offer this object at the root.

When a Subscription Channel is created, the server creates an internal Notification Queue for that Subscription Channel. The client then registers Subscriptions by specifying which properties it would like to receive notifications for. The server then starts polling the value of the registered properties at the specified Monitor Interval and creates a Notification Queue for each Subscription. When the value of the property changes, a Notification is put into the Notification Queue. These Notifications are then removed from that queue and put into the Subscription Channel's queue at a rate of Publish Interval.

The client can then call WaitNotification on the server. This method is blocking and has a default timeout of 5 seconds. As soon as one or more Notifications are available in the Notification Queue of the Subscription Channel, the function returns them.

CreateSubscriptionChannel(Integer NotificationQueueSize) : Integer

Creates a new Subscription Channel on the server. A Subscription Channel regroups all subscriptions created in it.

Arguments:
  • NotificationQueueSize: Integer, the maximum amount of notifications a server can store before it is forced to send notifications to the client.
Return value:
Integer, a unique numerical identifier for this Subscription Channel, to be passed as an argument to RegisterSubscription, UnregisterSubscription and WaitNotification.

Note: If a Subscription Channel is unused for more than 20 minutes, the server deletes it and all the subscriptions are removed from the server as well.

RegisterSubscription(Integer SubscriptionChannel, WoopsaLink PropertyLink, TimeSpan MonitorInterval, TimeSpan PublishInterval) : Integer

Registers a Woopsa Property specified by PropertyLink to be monitored by the provided SubscriptionChannel. Once a property is registered, changes to that property will add a notification to the channel's queue and thus push notifications to the client.

Arguments:
  • SubscriptionChannel: Integer, the unique numerical identifier of the Subscription Channel returned by a previous CreateSubscriptionChannel call.
  • PropertyLink: WoopsaLink, the path of the Woopsa Property to monitor.
  • MonitorInterval: TimeSpan, the rate at which the server should poll this variable to check for changes. Recommended: 0.1 seconds. When MonitorInterval is defined to 0, property is polled at the rate of PublishInterval, and only the latest value change is returned to WaitNotification.
  • PublishInterval: TimeSpan, the maximum rate at which notifications can be published to the queue. Recommended: 0.1 seconds. When MonitorInterval and PublishInterval are both defined to 0, there will be only 1 notification at the time of the subscription, and the property will not be polled.
Return value:
Integer, a numerical identifier for this Subscription, to be passed as an argument to UnregisterSubscription.

UnregisterSubscription(Integer SubscriptionChannel, Integer SubscriptionId) : Logical

Unregisters a Woopsa Property specified by the passed SubscriptionId. This will stop notifications for that property to be pushed to the client.

Arguments:
  • SubscriptionChannel: Integer, the unique numerical identifier of the Subscription Channel returned by a previous CreateSubscriptionChannel call.
  • SubscriptionId: Integer, the id of the Subscription returned by a previous RegisterSubscription call.
Return value:
Logical, true if the Subscription was unregistered successfully.

WaitNotification(Integer SubscriptionChannel, Integer LastNotificationId) : JsonData

This method blocks until a notification is sent by the server. In case no Notifications have appeared in the queue, this function returns an empty value after 5 seconds. This effectively creates a heartbeat and allows connection problems to be detected in 5 seconds or less.

In order to guarantee that no notifications are lost between a client and a server, each notification sent by the server is identified with a unique incremental number (Id). Upon calling this method, the client must tell the server which Id it was last able to process. Only then will the notifications be removed from the queue on the server.

If the client does not fetch notifications quickly enough, the notifications queue can fill up completely. In that case, the server will delete old notifications and this method will throw a WoopsaNotificationsLostException. The client must acknowledge this error before it can receive new notifications by calling this method with a LastNotificationId value of 0. The first call to this method by a client should also have the LastNotificationId value of 0.

Notification IDs are from 1 to 1'000'000'000 (one billion).

Arguments:
  • SubscriptionChannel: Integer, the unique numerical identifier of the Subscription Channel returned by a previous CreateSubscriptionChannel call.
  • LastNotificationId: Integer, the unique numerical identifier of the last Notification that the client was able to handle. If this number is a valid reference to a Notification in the current queue, the server will delete from this queue all Notifications that have an Id less than or equal to this Id.
Return value:
JsonData of type Array, the complete list of notifications in the server queue.

Note: Notifications are sent in the following format:

{
  "Value": [
    {
	  "Value": /* A JSON-serialized WoopsaValue, see _read_ */,
	  "SubscriptionId": /* The identifier of the subscription for which this notification is raised */,
	  "Id": number /* A unique, incremental identifier for this notification. */
    },
    ..
  ],
  "Type": "JsonData"
}

Thus, notifications returned are actually a JsonData array containing pairs of WoopsaValues and PropertyLinks.

Multiple Requests with the MultiRequest Woopsa method

When a client needs to make many requests to a Woopsa server, doing them individually can be quite costly in terms of network traffic. The MultiRequest method solves this problem by allowing a client to make multiple Woopsa requests at the same time.

A Woopsa server implementing multiple requests will publish a MultiRequest function on the root object.

MultiRequest(JsonData Requests) : JsonData

Execute the requests specified by Requests on the server sequentially.

Arguments:
  • Requests: JsonData, a list of requests to execute on the server sequentially. The client must attribute a unique ID to each request because the return order of the responses is not garanteed by the JSON serialization. The structure of the Requests object must be:
    [
      {
        "Id": integer, // A unique ID generated by the client
    
        "Verb": string, // read, write, meta or invoke
    
        "Path": string, // path for this request
    
        "Value": string, // (allowed and required only for write) the value to write to the property specified by path 
    
        "Arguments": object // (allowed and required only for invoke) JSON key-value pair of arguments with which to invoke the method 
    
      },
      /* ... */
    ]
Return value:
JsonData, An array containing the results of each request specified in Requests. The structure of the return value is as follows:
[
  {
    "Id": integer, /* The unique ID specified by the client for the request this is a response to */
    "Result": object /* The complete serialization of the response, representing a WoopsaValue in case of success, or an Error otherwise */
  },
  /* ... */
]
For methods returning void, "Result" contains the value null.

Note: It is the role of the client to ensure the Ids are unique. The server should not try to do this verification.