Client/Server RPC
General
Information
- RPC enables access to removed resources (procedures and functions), usually via a network interface.
- This is used for mutual access to functionality and data between PLANTA Server and PLANTA Client.
- The RPC solution implemented in PLANTA, is based on the Python RPC implementation RPyC (in version 3.3
- For use in PLANTA, the RPyC messages are redirected via the existing client-server connection.
- For this purpose, the packets are packed in XML messages
Caution
- Customizings based on client_exec()/server_exec() must be changed before deactivating this functionality
- The standard customizing available up to now is mainly based on the initial interface.
Motivation
Initial Approach
- Up to server version 39.5.15, remote access between PLANTA Server and PLANTA Client was based on the ability to mutually send Python scripts via XML message.
- The recipient of the message then ran the script contained in the message without further consideration.
- This construct enables the execution of an arbitrary number of statements with the recipient's rights, which entails high mutual risks.
- Another disadvantage of this communication schema is the unidirectional communication: the script is aborted without waiting for termination or receipt of a return value.
- In order to preserve compatibility with customer-specific customizing, the functionality remains in place but it can (and should) be deactivated as soon as the existing customizing has been changed to RCP interface.
RPC Approach with Service Architecture
- RPyC enables you to use services, i.e. clearly defined functionality units that a client can use.
- The implementation of the services comes with a restriction of the range of functions.
- Upon implementation, however, you have to make sure that no gap is opened. Hence, parameters may not be evaluated via
eval()
since this again enables the execution of arbitrary statements. - RPyC is based on bidirectional communication; Here, a service functionality can be used synchronously as well as asynchronously (also with return value that can be inquired later)
Use
Definition of Services
- A service consists of a class that inherits from
rpyc.Service
. - Furthermore, the class shall not possess its own constructor.
- The
on_connect()
andon_disconnect()
methods allow for initialization and resource approval during creation/removal of the service instance - Approved fields and methods are marked by the
exposed_
prefix. - In the (recommended, because secure) standard configuration, only service parts which are made available in this way can be used.
- The following example is also used below in order to explain the use of the interface:
import rpyc
import time
class EchoService(rpyc.Service):
def on_connect(self):
pass
def on_disconnect(self):
pass
def exposed_echo(self, somevalue):
return somevalue
def exposed_long_echo(self, somevalue):
time.sleep(30) # simulate a longer calculation...
return somevalue
Access to Released Services
- Via the interface, the registration and use of an arbitrary number of services is possible.
- In order to resolve the service referenced by a message, a directory is used in which all published services and used remote services themselves are entered.
- The key for accessing this directory is an arbitrary name. In order to prevent conflicts, a namespace based allocation is recommendable (e.g. "examples.echo_service" for the example service)
- Interface function for the service directory are available in the
csrpc.core.registration
module:
def get_service_directory():
"""Gibt das Dienst-Verzeichnis zurück"""
def get_service(name):
"""Gibt den Dienst zurück, der unter [name] registriert ist; wirft ServiceDirectoryError, wenn der Schlüssel [name] nicht vorhanden ist"""
def register_service(name, service=None):
"""Registriert den durch die Klasse [service] definierten Dienst unter dem Namen [name]; wirft ServiceDirectoryError im Konfliktfall"""
def access_service(name):
"""Baut eine Verbindung zum entfernt unter [name] registrierten Dienst auf; entspricht Aufruf von register_service(name)"""
def deregister_service(name):
"""Entfernt Dienst/Verbindung aus dem Verzeichnis; wirft ServiceDirectoryError, wenn der Schlüssel [name] nicht vorhanden ist"""
Registration of Remote Services
- In order to render remotely available services usable, a service on the client and server side can be used
- This service registration service can be reached via the "service_registration" key
- The connection to this service must be established as is done for other services as well:
from csrpc.core import registration
from csrpc.core.exceptions import ServiceDirectoryError
try:
sr = registration.access_service('service_registration')
except ServiceDirectoryError:
sr = registration.get_service('service_registration')
- The remote service class, in this example EchoService, will then be registered as follows:
r.root.register('examples.echo_service', 'csrpc.services.examples.EchoServer')
- With this remotely registered service, a connection can now be established as described above:
echo_service = registration.access_service('examples.echo_service')
Using the Service Functionality
- All published methods and attributes of the removed service are available in the
root
directory of the local representation - In the example a method call will look as follows (the prefix does not need to be specified!):
result = echo_service.root.echo('hello world')
- For synchronous remote program execution (as above), the local call waits in a blocking manner for the return of the removed call
- For long calculations or in the case of irrelevance of the return, asynchronous calls can be carried out as follows:
import rpyc
alongecho = rpyc.async(echo_service.root.long_echo)
res = alongecho('A really long calculation')
- The call immediately returns; res if it is a container that is filled upon arrival of the return value
- With the help of the container, the cancelation can also be checked or it can be waited for the result in a blocking manner.