Java, .NET and native code
A Java jar, a .NET assembly or a native binary run as a supervised helper process.
When the client for a system is a Java jar the vendor ships, a .NET assembly or a native binary, start_process runs it as a helper process next to the server. The Connector SDK starts the process with the connection, supervises it, restarts the connection if the process dies unexpectedly, and stops the process when the definition is deleted. Services use the connection like any other.
The connector module
The example wraps a Java inventory component - a jar that serves requests over a local socket:
# -*- coding: utf-8 -*-
# stdlib
import socket
import time
# Zato
from zato.common.sdk import Connector, Field
# How long to wait for the helper process to start accepting connections, in seconds.
_startup_timeout = 15
class InventoryClient:
""" A client for the inventory system, which is a Java component - the connector runs its jar
as a helper process and this client talks to it over a local socket, one connection per request.
"""
def __init__(self, host:'str', port:'int') -> 'None':
self.host = host
self.port = port
def send(self, data:'str') -> 'str':
# Connect for the duration of one request ..
with socket.create_connection((self.host, self.port)) as conn:
conn.sendall(f'{data}\n'.encode('utf8'))
# .. and read the response line back.
with conn.makefile('r', encoding='utf8') as reader:
response = reader.readline()
return response.strip()
class InventoryConnector(Connector):
""" Wraps a Java inventory component - a jar run and supervised as a helper process
that services access through self.out.inventory.
"""
type = 'inventory'
# Configuration schema
jar_path = Field.Text()
def create_client(self) -> 'InventoryClient':
# Run the jar as a supervised helper process - the '{port}' placeholder holds
# the local port allocated for it.
process = self.start_process(['java', '-jar', self.config.jar_path, '{port}'])
client = InventoryClient('127.0.0.1', process.port)
# The JVM needs a moment before it accepts connections.
deadline = time.monotonic() + _startup_timeout
while True:
try:
client.send('hello')
except OSError:
if time.monotonic() > deadline:
raise Exception(f'The inventory helper did not start within {_startup_timeout}s')
time.sleep(0.2)
else:
break
self.logger.info('Inventory helper started for `%s` (pid %s)', self.name, process.pid)
return client
def ping(self, client:'InventoryClient') -> 'None':
client.send('ping')
def get_stock(self, item:'str') -> 'str':
return self.client.send(f'stock {item}')
In the code above:
start_processis provided by the platform, never overridden - it allocates a local port, substitutes it for any{port}placeholder in the command and starts the process- The returned object has four attributes -
pid,port,is_runningandstop - Supervision means the platform watches the process - if it dies unexpectedly, the whole connection is rebuilt with backoff, which re-runs
create_clientand so starts a new helper - The helper stops with the connection - deleting or editing the definition stops the process too
- A .NET assembly or a native binary works exactly the same way - only the command changes
Creating a definition
Definitions are managed with enmasse under a key derived from the connector's type - custom_ plus the type name: