The basic format of ZiNC messages is a three-part JSON structure:
{
"subscription": id,
"request": { ... },
"payload": { ... }
}
However, individual messages will generally not have all three parts, but only those parts that are necessary to their function.
Unlike HTTP, streaming communications are fundamentally bi-directional; messages can flow in both directions and can be initiated by either end. Note however that ZiNC only specifies the format of the communication; the semantics are determined by the operations understood at each end of the communication channel. It is important that the two ends understand what the other is capable of before beginning communication.
Either end of the communication channel can initiate a request on the other end.
The simplest type of request just asks the other end to perform an operation and does not send any additional data or want a response. For example, a heartbeat operation might look like this:
{
"request": {
"method": "invoke",
"operation": "heartbeat"
}
}
A simple request for a resource requires both a request object and a subcription handle. The handle will be sent back with a response object. The handle is generated by the requestor and should be a unique integer. It is the requestors responsibility to remember what to do with any responses which are delivered to it with that handle.
{
"subscription": 14,
"request": {
"method": "get",
"resource": "/path/to/resource"
}
}
Unlike HTTP, which is strictly one-request, one-response, streaming protocols can have any number of responses to a request. The previous two examples have had 0 and 1 responses respectively, but it is also possible to "subscribe" to a resource, that is, to request it and all future versions of it.
{
"subscription": 14,
"request": {
"method": "subscribe",
"resource": "/path/to/resource"
}
}
The resource is often a complete description of the object to be watched. However, there are cases where additional parameters are useful. Unlike HTTP, these are generally not attached to the resource string, but contained in a separate block.
{
"subscription": 14,
"request": {
"method": "subscribe",
"resource": "/path/to/resource",
"options": {
"name": "henry",
"age": 42
}
}
}
It's also possible to send data to a server. To write to an existing
resource, you can use the put
method:
{
"request": {
"method": "put",
"resource": "/path/to/resource"
},
"payload": {
// payload as defined in JSONAPI
}
}
Because so much of what goes on with streaming protocols is
updating small portions of objects, the update
method is also
supported. This does not overwrite an existing resource, but
only sets those portions which are specified.
{
"request": {
"method": "update",
"resource": "/path/to/resource"
},
"payload": {
// object deltas formatted as defined in JSONAPI
}
}
If you have created a subscription, it is possible to cancel it as follows:
{
"subscription": 14,
"request": {
"method": "unsubscribe",
}
}
Unlike requests, all responses have exactly the same format. Each response is in response to a specific request and must include the subscription handle. It must also contain the payload of the response.
Thus all responses are of the form:
{
"subscription": 14,
"payload": {
// payload as defined in JSONAPI
}
}
It is immaterial in the definition of a response what method was used to request the payload (get, subscribe, create, etc). All that matters is that a contract has previously been agreed by which a response is expected.
The channel between two processes is open for communication in both directions. At any time either end may send a request or a response and the receiving end must be ready to handle any valid message. The "client" may not expect that it sends a request and the next message to come back is the single response to that message; rather, all requests for which a response is desired must contain a subscription ID and then the client must watch for response objects coming back with that subscription ID.
The subscription IDs are maintained for a single requestor on a single channel. This being the case, it is entirely possible that requests and responses are sent on the same channel in opposite directions with the same subscription ID.
Logically, a request must be sent before any response with the request's subscription ID. If multiple requests are sent with the same subscription ID there is no way to determine which request the response was intended for; this is NOT recommended.
As with HTTP, ZiNC has a number of "methods" or "verbs" that can be used on each request. These are described below.
Request an object and start subscribing to it. The request should specify a resource to handle the subscription request; in the absence of a resource definition, they will be handled by the default request handler.
The request MUST specify a subscription ID.
The request may also specify arbitrary options to control what is to be watched. In many instances, simply specifying the resource is enough information; in other cases, this merely tells the server how to consider the options.
No payload may be sent.
Cancel a previously defined subscription.
The request MUST specify a subscription ID.
No other fields will be recognized.
The get
operation is a convenience for those cases where it is
desired to obtain a resource that either will never change (and thus it is
a waste of resources to track) or for which seeing it change would be
inconvenient. In general, this is just not a good idea; you should use
subscribe
instead.
The get
operation is processed as close to subscribe
followed
by unsubscribe
the moment the response is sent as possible.
Store information on the server associated with a resource. The means in which this is carried out is server and resource dependent.
This request requires a resource and a payload and may also include options associated with the request.
The resource does not necessarily identify the object to be updated, but instead may identify the element in the server responsible for doing the updating. In such cases, there should be a field either in the options or in the payload which identifies the object uniquely.
The update
operation is a variant of the put
operation.
Unlike the put
operation which writes the entirety of the payload
to the data store, the update
operation considers the payload to
be a delta and updates the affected objects with the fields that
are specified in the payload. Unspecified fields are left unchanged.
Create a new object on the server.
In order for this to work, the server must have a handler for the resource which knows how to create objects and the specified options and payload must conform to its definition of how to create objects.
If a subscription id is specified, the new object will be sent as a response to the client. A subscription will also be established for its value.
Delete an object on the server.
In order for this to work, the server must respect deletion semantics, must have a handler for the resource which knows how to delete objects and the options and payload must combine to specify a valid object to delete.
In some cases, the resource may be sufficient information by itself to know what object to delete; in this case no options or payload are required.
Invoke an operation on the server.
The resource is used to identify a handler to carry out the operation and the options and payload are used to determine the operation and its arguments.
If the operation has a result and a subscription is specified, the result is sent to the client. If the operation is of a continuous nature, the subscription may receive multiple results.
An establish method is sent as the first message after a connection is established. One such message should be sent in each direction.
There are two required options on the establish
method: type
and address
. Both indicate information
about the other end.
The type
is either server
or client
indicating
mainly whether or not the sending end could be considered
to be "permanent" or "transitory".
The address
is a unique, repeatable address for the
sending end. For servers, it should be the address on
which they are listening. For clients, it may either
be some unique address which is stored in browser local
storage or null
to indicate that the client is
simply transitory.
It is possible for connections to simply "go away".
However, if one end wishes to cease communications and
end the relationship permanently, it can explicitly
tear down the connection using the teardown
method.
No options or payload are needed, and this method does not have a subscription ID associated with it.