Skip to main content
Skip table of contents

Client-Server RPC

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.Service erbt
  • Weiterhin sollte die Klasse keinen eigenen Konstruktor besitzen
  • Die Methoden on_connect() und on_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:
PY
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.registration vorhanden:
PY
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:
PY
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:
PY
sr.root.register('examples.echo_service', 'csrpc.services.examples.EchoServer')
  • Mit diesem entfernt registrierten Dienst kann nun wie oben beschrieben eine Verbindung eingegangen werden:
PY
echo_service = registration.access_service('examples.echo_service')

Verwendung der Dienstfunktionalität

  • Alle veröffentlichten Methoden und Attribute des entfernten Dienstes sind im Verzeichnis root der lokalen Repräsentation vorhanden
  • Ein Methodenaufruf sieht im Beispiel dann wie folgt aus (das Präfix braucht nicht angegeben zu werden!):
PY
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:
PY
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
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.