Zato-based WebSockets are a great choice for high-performance API integrations. WebSockets have minimal overhead, which, coupled with their ability to invoke services in a synchronous manner, means that large numbers of clients can easily connect to Zato API servers.

Introduction

The crucial distinction between WebSockets and typical REST-based APIs is that clients based on the former protocol always establish long-running TCP connections and, once connected, the overhead they incur is practically negligible.

With a great number of clients a series of questions naturally appears. What are the clients currently connected? What if I want to force one to disconnect? What topics and message queues are they subscribed to? How can I communicate with the WebSockets directly from web-admin?

This blog post answers all these questions and then some more.

WebSocket channels

As a refresher, recall that all WebSocket clients connect to Zato through their channels. Each channel encapsulates basic information about what is expected from each client, e.g. their credentials or which service is responsible for their requests.

Screenshots

Listing connections

With a desired channel in place, we can start a few clients and then go straight to the listing of connections, as in the screenshow below:

Screenshots

Screenshots

By default, all connections for a given channel are listed but it is possible to filter them out by external client ID - each WebSocket identifies with a unique client ID, as assigned by the system on whose behalf the WebSocket connects. This makes it easy to find connections even if they go through a series of networks.

Each WebSocket is identified by a series of attributes, Client, Remote, Local and Connection time.

Each Client connection has a few of identifiers:

  • Unique connection ID assigned by Zato, changed each time a client connects
  • Client ID - unique ID assigned by the remote end, persists across connections
  • Client name - similar to Client ID but there is no requirement that it be unique

Remote TCP end has two attributes:

  • IP address as observed from a Zato server's perspective
  • FQDN (domain name) of that IP address

Local server to which a WebSocket is connected:

  • Its IP address and port number
  • Server name and server process ID (PID) to which the WebSocket is attached

Connection time is by default presented in current user's timezone but clicking it changes the format to UTC.

Checking pub/sub subscriptions

Screenshots

WebSockets may participate in pub/sub processes and it is possible to look up all the topics a particular connection is subscribed to. Note that subscription times may predate connection times - this will be the case if a WebSocket connects, subscribes to a topic, then disconnects and connects again. In such a case, the subscription time will be earlier than the last connection time.

Invoking WebSockets directly

Screenshots

It is possible to send requests straight to a WebSocket, waiting up to timeout seconds for the reply. This lets one communicate with the remote connection directly, which is of great assistance in many low-level diagnostic scenarios.

Disconnecting API clients

Screenshots

Each WebSocket can be disconnected separately - on the protocol level, it will send a Close event to the remote end, afterwards cleaning up all the internal resources taken up by the connection.

Summary

API integrations with WebSockets offer an alternative to REST whose greatest advantage is reduced runtime processing overhead. Zato offers built-in GUI tools to create and manage WebSockets, including searching, listing and direct communication with each WebSocket straight from a browser's window.

To learn more about how to integrate APIs with Zato, visit the tutorial and downloads sections of the extensive documentation which cover everything needed to get started with the platform.

Since version 3.0, it is possible to directly connect Zato clusters and exchange messages as though remote services where running in a local instance. This makes it an ideal choice for environments split into multiple parts.

Introduction

The reasons to have more than one cluster, each with one or more servers, may vary:

  • For HA and performance, environments may be broken out geographically into a setup with one cluster per continent or a region of the world
  • CPU-extensive operations may be carried out in one cluster with another making use of the results the former produces to offer a set of APIs
  • For legal reasons, it may not be allowed to run all integration services in one cluster, using the same hardware and software infrastructure

The new feature in Zato 3.0 which allows for efficient communication between clusters are WebSocket connections - one of clusters will create a channel through with other clusters may invoke its services via their outgoing connections.

WebSockets (WSX for short) have essentially no overhead in practice but they can be used for bi-directional communication hence they are a great choice for such scenarios.

From a Zato programmer's perspective, all the communication details are hidden and a couple of lines of code suffices to invoke services or receive messages from remote clusters, for instance:

# Obtain a handle to a remote connection
with self.out.wsx.get('My Connection').conn.client() as client:

    # Invoke a remote service - expects a Python dict on input
    # and returns a Python dict on response. All the serialization
    # and network connectivity is handled automatically.
    response = client.invoke(msg)

Architecture and configuration

Screenshots

  • Each cluster which is to become a recipient of messages from other clusters needs to have a new WebSocket channel created with service helpers.web-sockets-gateway mounted on it. A security definition should also be attached as required.

  • Each cluster that should invoke another one needs to have an outgoing WebSocket connection created - make sure Is remote end Zato checkbox is on and that credentials are provided, if required by the other side.

  • If the cluster with an outgoing connection is interested in receiving publish/subscribe messages, all topics it wants to subscribe to should be listed, one in each line. Make sure the cluster with a channel has a correct pub/sub endpoint configured for that channel.

  • The cluster which establishes the connection (here, cluster1) may also want to subscribe to events of interest via hooks services - more about it below.

  • Once an outgoing connection is created, internal tasks will start on cluster1 to establish a remote connection to server2. If successful, authentication will take place automatically. Finally, if configured, a hook service will fire to let cluster1 know that a new connection was established. Afterwards, cluster1 may start to invoke remote services.

  • There are no other steps involved, at this point everything is configured and ready to be used.

Screenshots

Screenshots

From a programmer's perspective

  • To invoke remote Zato services, programmers use WebSockets outgoing connections methods - providing a dictionary of input data to the invocation and receiving a dictionary of data on input. Note that the invocation is synchronous, your service is blocked until the remote cluster responds.
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function, unicode_literals

from zato.server.service import Service

class MyService(Service):
    def handle(self):

        # Message to send - needs to be a dictionary with name
        # of the service to invoke as well as its input data, if any is required.
        # In this case, we are invoking an echo service
        # which writes back to output anything it receives on input.
        msg = {
             'service':'zato.helpers.echo',
             'request': {
                 'elem1': 'value1',
                 'elem2': 'value2',
             }
         }

        # Name of the connection to send messages through
        conn_name = 'My WSX Outconn'

        # Obtain a client from the connection pool
        with self.out.wsx.get(conn_name).conn.client() as client:

            # Send the message and read its response
            response = client.send(msg)

            # Or, client.invoke can be used with Zato WebSocket connections,
            # this method is an alias to client.send
            response = client.invoke(msg)

            # Log the response received
            self.logger.info('Response is `%s`', response.data)
INFO - Response is `{u'elem2': u'value2', u'elem1': u'value1'}`
  • To receive messages, hook services are used. There are three events for which hooks can be triggered - they can be handled by different services or the same one, it is up to users:
  1. Upon connecting to a remote cluster, including reconnects (on_connect)
  2. Upon receiving messages from remote clusters (on_message)
  3. Once a connection to the remote cluster is shut down (on_close)
  • The on_message hook can be combined with publish/subscribe topics and queues - each time the remote cluster (the one with a WSX channel) publishes a message that the local cluster (the one with a WSX outgoing connection) is interested in, the on_message hook will be called to handle it, in this manner making it possible for remote clusters to deliver messages to clusters subscribing to topics.

  • Each hook is just a Zato service with a specific SimpleIO signature, as in the on_message example below:

# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function, unicode_literals

from zato.server.service import Opaque, Service

class OnMessageHook(Service):
    class SimpleIO:
        input_optional = (Opaque('ctx'),)

    def handle(self):

        # Object describing incoming data
        ctx = self.request.input.ctx

        # Message type
        msg_type = ctx.type

        # Data received
        data = ctx.data

        # Log message type
        self.logger.info('Msg type: `%s`', msg_type)

        # Log actual data
        self.logger.info('Data received: `%s`', data.data)

        # Log metadata - ID and timestamp
        self.logger.info('Meta: `%s` `%s`', data.id, data.timestamp)

Now, we can use web-admin to publish a test message and confirm that the on_message service receives it:

Screenshots

In the on_message service's server logs:

INFO - Msg type: `message`
INFO - Data received: `[
  {u'delivery_count': 0,
   u'msg_id': u'zpsme26726911ffbe8cba2cca278',
   u'expiration_time_iso': u'2086-09-21T14:03:05.285470',
   u'topic_name': u'/customer/new',
   u'pub_time_iso': u'2018-09-03T10:48:58.285470',
   u'priority': 5,
   u'expiration': 2147483647000,
   u'has_gd': True,
   u'data': u'This is a sample message',
   u'sub_key': u'zpsk.websockets.6ef529f7cab64a71d8bd2878',
   u'mime_type': u'text/plain',
   u'size': 24}
  ]`
INFO - `6fd296ecf78493a3a0ce7570` `2018-09-03T10:49:00.540024`

Summary

Zato 3.0 has just been released - this is a major release that brings a lot of immensely interesting API integration features.

Zato is an enterprise open-source Python-based ESB/SOA/API integration platform and backend application server.

It is designed specifically for construction of online, mobile, IoT, middleware and backend Python systems running in banking, telecommunications, health-care, insurance, education, public administration or other environments that require integrations of multiple applications.

Highlight of the release are:

Here is the documentation, downloads and full changelog.

Screenshots

Screenshots

Screenshots

Screenshots

Screenshots

Screenshots

Screenshots

Screenshots

As an enterprise integration platform and backend, API-oriented, application server, Zato 3.0 ships with Single Sign-On and User Management APIs whose many exciting features are detailed in this blog post.

Screenshots

  • No need for maintaining one's own user database

  • Everything is API-based - user creation, updates, logging in, logging out, checking access, creating sessions, validating sessions, search, there is an API call for everything

  • Strong encryption and safe data storage assist in achieving compliance with regulations such as HIPAA or EU GDPR

  • APIs exist for both REST and Python calls which means that everything is also available to user-based services communicating through additional protocols, such as AMQP, WebSockets, ZeroMQ, IBM MQ or any other that Zato supports

  • Comes with a built-in workflow for user signup, including user approval and welcome messages - just fill in the email templates

  • Personally Identifiable Information (PII) can be optionally encrypted and decrypted without any programming needed

  • Both users and their sessions can be given arbitrary key/value tags, also optionally encrypted and decrypted on the fly

  • Users can be required to log in from selected applications only

  • Users can be required to access APIs from selected IP addresses only

  • Passwords are always hashed (PBKDF2) and, by default, encrypted as well (Fernet)

  • PBKDF2 parameters can be easily fine-tuned in each environment separately

  • Configurable warnings of an approaching password expiry

  • Password strength enforcement, including length checks and blacklisting of the most commonly used ones

  • Audit log keeps track of who accesses personal information and for what purpose

  • Clearly defined roles - regular users and admins (super-users)

  • Convenient command line tools for scripted management of user accounts, including typical tasks such as resetting a user's password or locking and unlocking an account

  • Extensive documentation covering the functionality, including dozens of REST and Python examples

The functionality is a major addition to Zato in version 3.0 and can be expected to expand with each new release, including support for additional authentication methods and interoperability with existing authentication protocols, yet in its initial form it can already handle a lot of use-cases and processes.

In particular, if you are creating applications that would not otherwise need a full server nor a database, e.g. single-page apps or mobile ones, be sure to check the new APIs out!

With the addition of WebSocket channels in Zato 3.0, a question arises of how to combine both HTTP and WebSocket channels in Zato environments. This article shows how a Docker container running in AWS can be configured to expose a single port to handle both kind of requests, including health-status checks from Amazon's ALB (Application Load Balancer).

Configuration

The configuration we would like to arrive at is in the diagram below:

Screenshots

  • There is ALB (Application Load Balancer) which sends HTTP pings to Zato, expecting an HTTP 200 OK response if everything is fine
  • There are WebSocket-based clients (WSX) that send their own business requests, independent of regular HTTP ones
  • A Docker container holds a Zato environment, publishing port 11224 to the outside world, to ALB and WSX
  • Inside the container, Zato's load-balancer, based on HAProxy, distributes requests individual TCP sockets of a Zato server
  • If the request begins with a well-known prefix (here, /ws), it is forwarded to a WebSocket channel on port 48902
  • All other requests, including the one from ALB, will go the default HTTP port at 17010

WebSocket channel

First step is to ensure that a WebSocket channel's path begins with the desired prefix, such as /ws, as in the screenshot.

Screenshots

HAProxy

Now, HAProxy needs to be reconfigured in its source code view, from web-admin:

Screenshots

  • The changes required need to go the stanza called 'front_http_plain' - right after its current content add the lines with logic that will separate incoming requests into WebSocket ones or any other.

  • Next, add a new HAProxy backend pointing to the WebSocket channel created earlier.

  • Now, stop and start the load-balancer again

# ##############################################################################

frontend front_http_plain

    ...

    acl is_websocket path_beg /ws
    acl is_websocket hdr(Upgrade) -i WebSocket
    use_backend websockets if is_websocket

# ##############################################################################

backend websockets
    mode http
    option http-server-close
    option forceclose

    server http_plain--server1 127.0.0.1:48902 check inter 2s rise 2 fall 2

# ##############################################################################

That's it

There are no more steps as far Zato is concerned - if a Docker container is now started with port 11224 mapped to 11223 inside the container, all the external applications will be able to access Zato-based services using both HTTP and WebSockets through a single port:

$ docker run -p 11224:11223 zato