WebSocket connections

WebSocket channels created in web-admin are programming language-agnostic, which means that they may be used to invoke services and publish or receive publish/subscribe messages from any application that speaks WebSockets and JSON, e.g. Python, JavaScript or any other.

To utilize the channels, applications (API clients) need to follow a protocol described in this chapter.

  • Upon opening an initial TCP stream, the client needs to obtain a token within a certain time (by default, 5 seconds)
  • Token returned by Zato identifies uniquely this particular client connection down to its TCP socket
  • The token is a random string considered a secret and must not be shared with any other client or connection
  • No other client is allowed to re-use an already issued token and each connection has exactly one token throughout its existence
  • Although the token is a strong random string, it can be used only for client identification and it must not be used on client side for any cryptographic purposes (there are dedicated crypto APIs in Zato for such needs)
  • Each token has a TTL, a Time To Live, which defaults to 864000 seconds = 10 days. Each time the channel is invoked the token’s TTL is extended by that many seconds from current time. If token reaches its TTL, any subsequent invocation will be rejected and the TCP socket will be closed.
  • Clients must send WebSocket Ping frames (per RFC-6455) at least once in 30 seconds. If 5 consecutive pings from a client are missed, the connection is dropped by Zato.
  • Ping frames must not send business data, they are used only for keeping connections alive
  • Applications may both invoke API services as as well take part in publish/subscribe workflows - in the latter case they can act as publishers, subscribers, or both

Connecting, and obtaining a session token

Clients establish an initial WebSocket connection to the address a given channel utilizes, possibly taking into account the fact that there may be a load-balancer performing URL transformations or port mappings in front of Zato servers.

Once a connection is created, clients need to create a session and receive a session token. This step needs to be done even if for a given WebSocket channel no client credentials are needed.

Clients must use the session token in all requests over the same TCP connection. The token cannot be used any other connections, it is specific to one TCP stream.

Right after a TCP connection is established, before logging in, an information entry is stored in server logs. This includes data about from what IP address and domain name the connection comes from as well as the address and name of the WebSocket channel.

INFO - New connection from 127.0.0.1:51738 (localhost) to 127.0.0.1:48902 (my.wsx.channel)

If a client connects but does not initiate the create-session request within the expected time, the TCP connection will be closed with an accompanying warning message in server logs:

WARNING - Peer 127.0.0.1:51738 (localhost) did not create session within 5s,
closing its connection to 127.0.0.1:48902 (my.wsx.channel), cid:`23b3cae088382f62106edd67`

Otherwise, if the client creates a session successfully, an informational message will be saved to logs:

INFO - Client 127.0.0.1:57288 (localhost ws.eee4b49d6fd835b424175ed5)
logged in successfully to 127.0.0.1:48902 (my.wsx.channel)

Request

Element Datatype Optional Notes
meta dict --- A dictionary of metadata for the request
meta.action string --- A constant value of "create-session"
meta.username string Yes Username to authenticate with (if needed)
meta.secret string Yes Password or other channel-specific secret to authenticate with (if needed)
meta.id string --- Client-generated request ID - must be globally unique (e.g. UUID4). Returned in responses in the in_reply_to element to let clients know in reply to which request a given response is returned.
meta.timestamp datetime --- When the message was generated by client, must be in UTC using ISO-8601 YYYY-MM-DDTHH:mm:ss.ssssss
meta.client_id string --- An arbitrary business ID of the client - any value identifying the client can be used, e.g. ID of a business application such as crm.prod.1 or ID of an IoT device connecting to Zato such as printer.mx2.3910
meta.client_name string Yes Human-friendly identifier of the client (in addition to client_id)

Sample:

1
2
3
4
5
6
7
8
9
{"meta": {
  "action": "create-session",
  "username": "user1",
  "secret": "PNVmGkLejnhsAZ5VzzfdHQkvGg",
  "id": "238dc406351444d0869390af9541da59",
  "timestamp": "2018-11-16T15:53:25.717215",
  "client_id": "p.33915",
  "client_name": "Printer #33915, Fifth floor"
  }}

Response

Element Datatype Optional Notes
meta dict --- A dictionary of metadata for the response
meta.status integer --- An overall status code, using HTTP status code, e.g. 200 is OK
meta.timestamp datetime --- When the response was produced by Zato, in UTC
id string --- Response ID - its last element is a correlation ID that can be used to look up details in server log
in_reply_to string Yes In reply to what request ID the response is returned
data any --- Business data related to the response. In case of an error it will be an error message. Otherwise, it will contain an element with the session token.
data.token string Yes A randomly generated session token - returned only if meta status is 200

Samples:

1
2
3
4
5
6
7
{"meta":{
  "status":200,
  "timestamp":"2018-04-11T09:17:16.954645",
  "in_reply_to":"238dc406351444d0869390af9541da59",
  "id":"zato.ws.srv.rsp-auth.39d6e397054e410810afeac2"},
 "data":{
  "token":"ws.token.b76561f8c145cd6baf086d04"}}
1
2
3
4
5
{"meta": {
  "status":403,
  "timestamp":"2018-04-11T09:02:20.918796",
  "id":"zato.ws.srv.msg-err.82608509fb56261fd8c90910"},
 "data":"You are not authorized to access this resource"}

Invoking services

Clients with a session token can invoke the service that is mounted on a WebSocket channel they are connected to.

It means that if a client wants to invoke multiple services, the service from the channel needs to forward messages to the actual ones using self.invoke and send the response back to the calling client by assigning it to self.response.payload. That allows one to implement white-lists where only selected services can be made accessible to the caller and attempts to invoke any other one will be rejected.

There is also a default WebSockets gateway service that can be used to invoke any service, it is documented in this chapter below.

Request

Element Datatype Optional Notes
meta dict --- A dictionary of metadata for the request
meta.action string --- A constant value of "invoke-service"
meta.id string --- Same as in "create-session" action
meta.timestamp datetime ---
meta.token string --- Session token returned by "create-session" action
data any --- Arbitrary data required by the channel's service - can be a string, integer, dict or anything else that the service expects

This sample assumes that service zato.helpers.echo is mounted on the channel - it's a built-in service that echoes back everything in receives on input.

1
2
3
4
5
6
7
8
9
{"meta": {
  "action": "invoke-service",
  "id": "36df91fca2444dcaadc7199691217cfd",
  "timestamp": "2016-11-16T15:53:25.717215",
  "token": "ws.token.7a1729f99acbfe21b0cca337"},
 "data": {
   "customer_id": "123",
   "account_id": "456"
  }}

Response

Element Datatype Optional Notes
meta dict --- Same as in "create-session" action
meta.status integer ---
meta.timestamp datetime ---
id string ---
in_reply_to string Yes
data any --- Business data related to the response. In case of an error it will be an error message. Otherwise, it will contain a response that the channel's service returned - may be a string, integer, dict or any other datatype

Again, the sample shows what zato.helpers.echo may return if it is assigned to a WebSocket channel.

1
2
3
4
5
6
7
8
9
{"meta":{
  "status":200,
  "timestamp":"2018-04-11T11:38:34.400736",
  "in_reply_to":"36df91fca2444dcaadc7199691217cfd",
  "id":"zato.ws.srv.rsp-ok.05308f81590751038cc9167f"},
 "data":{
   "customer_id":"123",
   "account_id":"456"
  }}

Closing sessions and logging out

There is no separate API call to close a session and log out. It suffices to close the WebSocket connection along with its TCP stream - this will be immediately recognized by Zato and all the relevant shutdown and cleanup actions will be performed.

Afterwards, the session token cannot be used anymore even by the same client; a new session needs to be created with its own token instead.

Using the default WebSockets gateway

A built-in service called helpers.web-sockets-gateway can be mounted on a WebSocket channel to make it possible for clients to invoke any other arbitrary service.

Note, however, that clients will truly have access to any service deployed on the server they will connect to, including all the internal ones. If this is not desirable, the gateway service can be subclassed to implement a white-listing logic, i.e. to filter out requests that attempt to invoke a service outside of the list of permitted ones.