Allgemeines
Information
-
RPC ermöglicht den Zugriff auf entfernte Ressourcen (Prozeduren und Funktionen), üblicherweise über eine Netzwerkschnittstelle
-
Dies wird in PLANTA zum gegenseitigen Zugriff auf Funktionalität und Daten zwischen PLANTA Server und PLANTA Client eingesetzt
-
Die in PLANTA implementierte RPC-Lösung basiert auf der Python-RPC-Implementierung RPyC (in Version 3.3)
-
Zur Nutzung in PLANTA werden die RPyC-Nachrichten über die bestehende Client-Server-Verbindung umgelenkt
-
Hierzu werden die Pakete in XML-Nachrichten verpackt
Achtung
-
Auf client_exec()/server_exec() basierendes Customizing muss vor Deaktivierung dieser Funktionalität umgestellt werden
-
Das bislang verfügbare Standardcustomizing basiert weitgehend auf der ursprünglichen Schnittstelle
Motivation
Ursprünglicher Ansatz
-
Bis zur Serverversion 39.5.15 basierte entfernter Zugriff zwischen PLANTA Server und PLANTA Client darauf, dass gegenseitig Python-Skripte über eine XML-Nachricht zugesandt werden konnten
-
Der Empfänger der Nachricht führte das darin enthaltene Skript ohne weitere Betrachtung aus
-
Dieses Konstrukt erlaubt die Ausführung beliebiger Anweisungen mit den Rechten des Empfängers, was beidseitig hohes Schadpotential birgt
-
Ein weiterer Nachteil des Kommunikationsschemas ist die unidirektionale Kommunikation: das Skript wird abgesetzt, ohne dass auf Beendigung gewartet oder ein Rückgabewert erhalten werden kann
-
Zur Wahrung der Kompatibilität mit kundenspezifischem Customizing bleibt die Funktionalität bestehen, kann (und sollte) aber deaktiviert werden, sobald bestehendes Customizing auf die RPC-Schnittstelle umgestellt ist
RPC-Ansatz mit Dienstarchitektur
-
RPyC erlaubt die Nutzung von Diensten, also klar definierten Funktionalitätseinheiten, die ein Client nutzen kann
-
Durch die Implementierung der Dienste ist somit der Funktionsumfang eingeschränkt
-
Bei der Implementierung muss jedoch sichergestellt werden, dass dadurch keine Lücke geöffnet wird; so dürfen Parameter nicht über
eval()ausgewertet werden, da dies wiederum die Ausführung beliebiger Anweisungen ermöglicht -
RPyC basiert auf bidirektionaler Kommunikation; Hierbei kann eine Dienstfunktionalität sowohl synchron verwendet werden als auch asynchron (auch mit später abfragbarem Rückgabewert)
Benutzung
Definition von Diensten
-
Ein Dienst besteht aus einer Klasse, die von
rpyc.Serviceerbt -
Weiterhin sollte die Klasse keinen eigenen Konstruktor besitzen
-
Die Methoden
on_connect()undon_disconnect()erlauben die Initialisierung und Ressourcen-Freigabe bei Erstellung/Entfernung der Dienstinstanz -
Freigegebene Felder und Methoden werden durch das Präfix
exposed_gekennzeichnet -
In der (empfohlenen, weil sicheren) Standardkonfiguration können nur auf diesem Wege veröffentlichte Dienstteile verwendet werden
-
Das folgende Beispiel wird auch im weiteren zur Erläuterung der Verwendung der Schnittstelle genutzt:
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
Zugriff auf veröffentlichte Dienste
-
Über die Schnittstelle ist die Registrierung und Verwendung beliebig vieler Dienste möglich
-
Um den von einer Nachricht referenzierten Dienst aufzulösen, kommt ein Verzeichnis zum Einsatz, in dem alle selbst veröffentlichten Dienste und verwendete entfernte Dienste eingetragen sind
-
Der Schlüssel zum Zugriff auf dieses Verzeichnis ist ein frei zu vergebender Name; zur Vermeidung von Konflikten bietet sich eine Namensraumbasierte Vergabe an (z.B. "examples.echo_service" für den Beispieldienst)
-
Schnittstellenfunktionen für das Dienstverzeichnis sind im Modul
csrpc.core.registrationvorhanden:
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"""
Registrierung entfernter Dienste
-
Um entfernt vorhandene Dienste verwendbar zu machen, kann ein Client- und Serverseitig verfügbarer Dienst genutzt werden
-
Dieser Dienstregistrierungs-Dienst ist über den Schlüssel "service_registration" erreichbar
-
Die Verbindung zu diesem Dienst muss wie bei anderen Diensten auch hergestellt werden:
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')
-
Die entfernte Dienstklasse, im Beispiel EchoService wird dann folgendermaßen registriert:
sr.root.register('examples.echo_service', 'csrpc.services.examples.EchoServer')
-
Mit diesem entfernt registrierten Dienst kann nun wie oben beschrieben eine Verbindung eingegangen werden:
echo_service = registration.access_service('examples.echo_service')
Verwendung der Dienstfunktionalität
-
Alle veröffentlichten Methoden und Attribute des entfernten Dienstes sind im Verzeichnis
rootder lokalen Repräsentation vorhanden -
Ein Methodenaufruf sieht im Beispiel dann wie folgt aus (das Präfix braucht nicht angegeben zu werden!):
result = echo_service.root.echo('hello world')
-
Bei synchroner entfernter Programmausführung (wie oben) wartet der lokale Aufruf blockierend auf die Rückgabe des entfernten Aufrufes
-
Bei langen Berechnungen oder bei Irrelevanz der Rückgabe können wie folgt asynchrone Aufrufe durchgeführt werden:
import rpyc
alongecho = rpyc.async(echo_service.root.long_echo)
res = alongecho('A really long calculation')
-
Der Aufruf kommt augenblicklich wieder zurück; res ist ein Container, der bei Eintreffen des Rückgabewertes befüllt wird
-
Mit Hilfe des Containers kann auch die Beendigung abgeprüft oder wiederum blockierend auf das Ergebnis gewartet werden