Java, .NET and native code
Call Java, .NET or native clients from services as named connections.
When the client for a system is a Java jar the vendor ships, a .NET assembly or a native binary, start_process from the Connector SDK runs it as a helper process next to the server. The process starts and stops with the connection, the platform supervises it and services use the connection like any other.
The connector module
The connector 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 calls 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)
# Wait until the JVM 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}')
The platform provides start_process - it allocates a local port, substitutes it for the {port} placeholder in the command and starts the process. The returned object has four attributes - pid, port, is_running and stop.
The platform supervises the process. When the process exits unexpectedly, the platform rebuilds the whole connection with backoff, which re-runs create_client and starts a new helper. Deleting or editing the definition stops the process together with the connection.
A .NET assembly or a native binary works the same way - only the command changes.
Create a definition
Create the definition in an enmasse file, under the custom_inventory key - custom_ followed by the connector's type:
The jar runs and stops with the connection and services call it like any other connection type.
See also
| Page | What it covers |
|---|---|
| SDK reference | Field types, lifecycle methods and the behavior of every invocation |
| Command-line tools | One-shot commands and long-lived tools like ngrok as connections |
| Python libraries in a separate interpreter | Run a Python library in an interpreter of its own and call it from services |