34.6 XML-RPC
Der Standard XML-RPC[ 153 ](XML Remote Procedure Call) ermöglicht den entfernten Funktions- und Methodenaufruf über eine Netzwerkschnittstelle. Dabei können entfernte Funktionen aus Sicht des Programmierers aufgerufen werden, als gehörten sie zum lokalen Programm. Das Übertragen der Funktionsaufrufe und insbesondere der Parameter und des Rückgabewertes wird vollständig von der XML-RPC-Bibliothek übernommen, sodass der Programmierer die Funktionen tatsächlich nur aufzurufen braucht.
Neben XML-RPC existieren weitere mehr oder weniger standardisierte Verfahren zum entfernten Funktionsaufruf. Da aber XML-RPC auf zwei bereits bestehenden Standards, nämlich XML und HTTP, basiert und keine neuen binären Protokolle einführt, ist es vergleichsweise einfach umzusetzen und daher in vielen Programmiersprachen verfügbar.
Da XML-RPC unabhängig von einer bestimmten Programmiersprache entwickelt wurde, ist es möglich, Client und Server in zwei verschiedenen Sprachen zu schreiben. Aus diesem Grund musste man sich bei der XML-RPC-Spezifikation auf einen kleinsten gemeinsamen Nenner einigen, was die Eigenheiten bestimmter Programmiersprachen und besonders die verfügbaren Datentypen anbelangt. Sie werden feststellen, dass Sie bei einer Funktion mit einer XML-RPC-fähigen Schnittstelle bestimmte Einschränkungen zu beachten haben.
Im Folgenden werden wir uns zunächst damit beschäftigen, wie durch einen XML-RPC-Server bestimmte Funktionen nach außen hin aufrufbar werden. Danach widmen wir uns der Clientseite und klären, wie solche Funktionen dann aufgerufen werden können.
34.6.1 Der Server
Zum Aufsetzen eines XML-RPC-Servers wird das Modul xmlrpc.server benötigt. Dieses Modul enthält im Wesentlichen die Klasse SimpleXMLRPCServer, die einen entsprechenden Server aufsetzt und Methoden zur Verwaltung desselben bereitstellt. Der Konstruktor der Klasse hat die im Folgenden beschriebene Schnittstelle.
SimleXMLRPCServer(addr, [requestHandler, logRequests, allow_none, encoding, bind_and_activate])
Der einzige zwingend erforderliche Parameter ist addr; er spezifiziert die IP-Adresse und den Port, an die der Server gebunden wird. Die Angaben müssen in einem Tupel der Form (ip, port) übergeben werden, wobei die IP-Adresse ein String und die Portnummer eine ganze Zahl zwischen 0 und 65535 ist. Technisch wird der Parameter an die zugrunde liegende Socket-Instanz weitergereicht. Der Server kann sich nur an Adressen binden, die ihm auch zugeteilt sind. Wenn für ip im Tupel ein leerer String angegeben wird, wird der Server an alle dem PC zugeteilten Adressen gebunden, beispielsweise auch an 127.0.0.1 oder localhost.
Über den optionalen Parameter requestHandler legen Sie ein Backend fest. In den meisten Fällen reicht die Voreinstellung des Standard-Handlers SimpleXMLRPCRequestHandler. Die Aufgabe dieser Klasse ist es, eingehende Daten in einen Funktionsaufruf zurückzuverwandeln.
Über den Parameter logRequest können Sie bestimmen, ob einkommende Funktionsaufrufe protokolliert werden sollen oder nicht. Der Parameter ist mit True vorbelegt.
Der vierte Parameter, allow_none, ermöglicht es, sofern hier True übergeben wird, None in XML-RPC-Funktionen zu verwenden. Normalerweise verursacht die Verwendung von None eine Exception, da kein solcher Datentyp im XML-RPC-Standard vorgesehen ist. Weil dies aber eine übliche Erweiterung des Standards darstellt, wird allow_none von vielen XML-RPC-Implementationen unterstützt.
Über den fünften Parameter encoding kann ein Encoding zur Datenübertragung festgelegt werden. Standardmäßig wird hier UTF-8 verwendet.
Der letzte optionale Parameter bind_and_activate bestimmt, ob der Server direkt nach der Instanziierung an die Adresse gebunden und aktiviert werden soll. Das ist interessant, wenn Sie die Serverinstanz vor dem Aktivieren noch manipulieren möchten, wird aber in der Regel nicht benötigt. Der Parameter ist mit True vorbelegt.
Für gewöhnlich reicht zur Instanziierung eines lokalen XML-RPC-Servers folgender Aufruf des Konstruktors:
>>> from xmlrpc.server import SimpleXMLRPCServer
>>> srv = SimpleXMLRPCServer(("127.0.0.1", 1337))
Nachdem eine Instanz der Klasse SimpleXMLRPCServer erzeugt wurde, verfügt diese über Methoden, um beispielsweise Funktionen zum entfernten Aufruf zu registrieren. Die wichtigsten Methoden einer SimpleXMLRPCServer-Instanz werden im Folgenden erläutert.
s.register_function(function, [name])
Diese Methode registriert das Funktionsobjekt function für einen RPC-Aufruf. Das bedeutet, dass ein mit diesem Server verbundener XML-RPC-Client die Funktion function über das Netzwerk aufrufen kann.
Optional kann der Funktion ein anderer Name gegeben werden, über den sie für den Client zu erreichen ist. Wenn Sie einen solchen Namen angeben, kann dieser aus beliebigen Unicode-Zeichen bestehen, auch solchen, die in einem Python-Bezeichner eigentlich nicht erlaubt sind, beispielsweise einem Bindestrich oder einem Punkt.
s.register_instance(instance, [allow_dotted_names])
Diese Methode registriert die Instanz instance für den entfernten Zugriff. Wenn der verbundene Client eine Methode dieser Instanz aufruft, wird der Aufruf durch die spezielle Methode _dispatch geleitet. Die Methode muss folgendermaßen definiert sein:
def _dispatch(self, method, params):
pass
Bei jedem entfernten Aufruf einer Methode dieser Instanz wird _dispatch aufgerufen. Der Parameter method enthält den Namen der aufgerufenen Methode und params die dabei angegebenen Parameter.
Eine konkrete Implementierung der Methode _dispatch, die die tatsächliche Methode der registrierten Instanz mit dem Namen method aufruft und die Parameter übergibt, kann folgendermaßen aussehen:
def _dispatch(self, method, params):
try:
return getattr(self, method)(*params)
except (AttributeError, TypeError):
return None
Diese Funktion gibt sowohl dann None zurück, wenn keine Methode mit dem Namen method vorhanden ist, als auch dann, wenn die Methode mit der falschen Zahl oder einem unpassenden Parameter aufgerufen wird.
[»] Hinweis
Wenn Sie für den optionalen Parameter allow_dotted_names den Wert True übergeben, sind Punkte im entfernten Methodenaufruf möglich. Dadurch können Sie auch Methoden von Attributen über das Netzwerk aufrufen. Beachten Sie unbedingt, dass es damit einem Angreifer möglich gemacht wird, auf die globalen Variablen des Programms zuzugreifen und möglicherweise schädlichen Code auszuführen. Sie sollten allow_dotted_names nur innerhalb eines lokalen, vertrauenswürdigen Netzes auf True setzen.
s.register_introspection_functions()
Diese Methode registriert die Funktionen system.listMethods, system.methodHelp und system.methodSignature für den entfernten Zugriff. Diese Funktionen ermöglichen es einem verbundenen Client, eine Liste aller verfügbaren Funktionen und Informationen zu einzelnen dieser Funktionen zu bekommen.
Näheres zur Verwendung der Funktionen system.listMethods, system.methodHelp und system.methodSignature erfahren Sie in Abschnitt 34.6.2, »Der Client«.
s.register_multicall_functions()
Diese Methode registriert die Funktion system.multicall für den entfernten Zugriff. Durch Aufruf der Funktion system.multicall kann der Client mehrere Methodenaufrufe bündeln, um so Traffic zu sparen. Auch die Rückgabewerte der Methodenaufrufe werden gebündelt zurückgegeben.
Näheres zur Verwendung der Funktion system.multicall erläutert Abschnitt 34.6.3, »Multicall«.
Beispiel
Nachdem die wichtigsten Funktionen der Klasse SimpleXMLRPCServer erläutert wurden, soll an dieser Stelle ein kleines Beispielprogramm entwickelt werden. Bei dem Programm handelt es sich um einen XML-RPC-Server, der zwei mathematische Funktionen (genauer gesagt, die Berechnungsfunktionen für die Fakultät und das Quadrat einer ganzen Zahl) bereitstellt, die ein verbundener Client aufrufen kann.[ 154 ](Dieses Szenario ist durchaus sinnvoll, wenn man sich vorstellt, der Server liefe auf einem Rechner, der für diese mathematischen Operationen besonders geeignet ist. Clients könnten diese Berechnungen dann an den Server delegieren. )
from xmlrpc.server import SimpleXMLRPCServer as Server
def fak(n):
""" Berechnet die Fakultaet der ganzen Zahl n. """
erg = 1
for i in range(2, n+1):
erg *= i
return erg
def quad(n):
""" Berechnet das Quadrat der Zahl n. """
return n*n
srv = Server(("127.0.0.1", 50000))
srv.register_function(fak)
srv.register_function(quad)
srv.serve_forever()
Zunächst werden die beiden Berechnungsfunktionen fak und quad für die Fakultät bzw. das Quadrat einer Zahl erstellt. Danach wird ein auf Port 50000 horchender XML-RPC-Server erzeugt. Dann werden die soeben erstellten Funktionen registriert. Schließlich wird der Server durch Aufruf der Methode serve_forever gestartet und ist nun bereit, eingehende Verbindungsanfragen und Methodenaufrufe entgegenzunehmen und zu bearbeiten.
Der hier vorgestellte Server ist natürlich nur eine Hälfte des Beispielprogramms. Im nächsten Abschnitt werden wir besprechen, wie ein XML-RPC-Client auszusehen hat, und schließlich werden wir am Ende des folgenden Abschnitts einen Client entwickeln, der mit diesem Server kommunizieren kann.
34.6.2 Der Client
Um einen XML-RPC-Client zu schreiben, wird das Modul xmlrpc.client der Standardbibliothek verwendet. In diesem Modul ist vor allem die Klasse ServerProxy enthalten, über die die Kommunikation mit einem XML-RPC-Server abläuft. Hier sehen Sie zunächst die Schnittstelle des Konstruktors der Klasse ServerProxy:
ServerProxy(uri, [transport, encoding, verbose, allow_none, use_datetime])
Hiermit wird eine Instanz der Klasse ServerProxy erzeugt, die mit dem XML-RPC-Server verbunden ist, den die URI[ 155 ](Ein URI (für Uniform Resource Identifier) ist die Verallgemeinerung einer URL. ) uri beschreibt.
An zweiter Stelle kann wie bei der Klasse SimpleXMLRPCServer ein Backend festgelegt werden. Die voreingestellten Klassen Transport für das HTTP-Protokoll und SafeTransport für das HTTPS-Protokoll dürften in den meisten Anwendungsfällen genügen.
Wenn für den vierten Parameter, verbose, der Wert True übergeben wird, gibt die ServerProxy-Instanz alle ausgehenden und ankommenden XML-Pakete auf dem Bildschirm aus. Dies kann zur Fehlersuche hilfreich sein.
Wenn Sie für den letzten Parameter use_datetime den Wert True übergeben, wird zur Repräsentation von Datums- und Zeitangaben anstelle der xmlrpc.client-internen Klasse DateTime die Klasse datetime des gleichnamigen Moduls (Abschnitt 17.2) verwendet, die einen wesentlich größeren Funktionsumfang besitzt.
Die Parameter encoding und allow_none haben dieselbe Bedeutung wie die gleichnamigen Parameter des Konstruktors der Klasse SimpleXMLRPCServer, der zu Beginn des letzten Abschnitts besprochen wurde.
Nach der Instanziierung der Klasse ServerProxy ist diese mit einem XML-RPC-Server verbunden. Das bedeutet insbesondere, dass Sie alle bei diesem Server registrierten Funktionen wie Methoden der ServerProxy-Instanz aufrufen und verwenden können. Es ist also keine weitere Sonderbehandlung nötig.
Zusätzlich umfasst eine ServerProxy-Instanz drei Methoden, die weitere Informationen über die verfügbaren entfernten Funktionen bereitstellen. Beachten Sie jedoch, dass der Server diese Methoden explizit zulassen muss. Dies geschieht durch Aufruf der Methoden register_introspection_functions der SimpleXMLRPCServer-Instanz.
Im Folgenden sei s eine Instanz der Klasse ServerProxy.
s.system.listMethods()
Diese Methode gibt die Namen aller beim XML-RPC-Server registrierten entfernten Funktionen in Form einer Liste von Strings zurück. Die Systemmethoden listMethods, methodSignature und methodHelp sind nicht in dieser Liste enthalten.
s.system.methodSignature(name)
Diese Methode gibt Auskunft über die Schnittstelle der registrierten Funktion mit dem Funktionsnamen name. Die Schnittstellenbeschreibung ist ein String im Format:
"string, int, int, int"
Dabei entspricht die erste Angabe dem Datentyp des Rückgabewertes und alle weiteren den Datentypen der Funktionsparameter. Der XML-RPC-Standard sieht vor, dass zwei verschiedene Funktionen den gleichen Namen haben dürfen, sofern sie anhand ihrer Schnittstelle unterscheidbar sind.[ 156 ](Dies wird auch Funktionsüberladung genannt. ) Aus diesem Grund gibt die Methode system.methodSignature nicht einen einzelnen String, sondern eine Liste von Strings zurück.
Beachten Sie, dass der Methode system.methodSignature nur eine tiefere Bedeutung zukommt, wenn der XML-RPC-Server in einer Sprache geschrieben wurde, bei der Funktionsparameter jeweils an einen Datentyp gebunden werden. Solche Sprachen sind beispielsweise C, C++, C# oder Java. Sollten Sie system.methodSignature bei einem XML-RPC-Server aufrufen, der in Python geschrieben wurde, wird der String "signatures not supported" zurückgegeben.
s.system.methodHelp(name)
Diese Methode gibt den Docstring der entfernten Funktion name zurück, wenn ein solcher existiert. Wenn kein Docstring gefunden werden konnte, wird ein leerer String zurückgegeben.
Beispiel
Damit ist die Verwendung einer ServerProxy-Instanz beschrieben. Das folgende Beispiel implementiert einen zu dem XML-RPC-Server des letzten Abschnitts passenden Client:
from xmlrpc.client import ServerProxy
cli = ServerProxy("http://127.0.0.1:50000")
print(cli.fak(5))
print(cli.quad(5))
Sie sehen, dass das Verbinden zu einem XML-RPC-Server und das Ausführen einer entfernten Funktion nur wenige Code-Zeilen benötigt und damit fast so einfach ist, als befände sich die Funktion im Clientprogramm selbst.
34.6.3 Multicall
Das Modul xmlrpc.client enthält eine Klasse namens MultiCall. Diese Klasse ermöglicht es, mehrere Funktionsaufrufe gebündelt an den Server zu schicken, und instruiert diesen, die Rückgabewerte ebenfalls gebündelt zurückzusenden. Auf diese Weise minimieren Sie bei häufigen Funktionsaufrufen die Netzlast.
Die Verwendung der MultiCall-Klasse wird an folgendem Beispiel verdeutlicht. Das Beispiel benötigt einen laufenden Server, der die Funktionen fak und quad für den entfernten Zugriff bereitstellt, also genau so einen, wie wir ihn in Abschnitt 34.6.1, »Der Server«, vorgestellt haben. Zusätzlich muss der Server den Einsatz von Multicall durch Aufruf der Methode register_multicall_functions erlauben.
from xmlrpc.client import ServerProxy, MultiCall
cli = ServerProxy("http://127.0.0.1:50000")
mc = MultiCall(cli)
for i in range(10):
mc.fak(i)
mc.quad(i)
for ergebnis in mc():
print(ergebnis)
Zunächst stellen wir wie gehabt eine Verbindung zum XML-RPC-Server her. Danach erzeugen wir eine Instanz der Klasse MultiCall und übergeben dem Konstruktor die zuvor erzeugte ServerProxy-Instanz.
Ab jetzt läuft die gebündelte Kommunikation mit dem Server über die MultiCall-Instanz. Dazu können die entfernten Funktionen fak und quad aufgerufen werden, als wären es lokale Methoden der MultiCall-Instanz. Beachten Sie aber, dass diese Methodenaufrufe keinen sofortigen entfernten Funktionsaufruf zur Folge haben und somit auch zu dieser Zeit keinen Wert zurückgeben.
Im Beispiel werden fak und quad jeweils zehnmal mit einer fortlaufenden ganzen Zahl aufgerufen.
Durch Aufruf der MultiCall-Instanz (mc()) werden alle gepufferten entfernten Funktionsaufrufe zusammen an den Server geschickt. Als Ergebnis wird ein Iterator zurückgegeben, der über alle Rückgabewerte in der Reihenfolge des jeweiligen Funktionsaufrufs iteriert. Im Beispielprogramm nutzen wir den Iterator dazu, die Ergebnisse mit print auszugeben.
Gerade bei wenigen Rückgabewerten ist es sinnvoll, diese direkt zu referenzieren.
wert1, wert2, wert3 = mc()
Hier wird davon ausgegangen, dass zuvor drei entfernte Funktionsaufrufe durchgeführt wurden und dementsprechend auch drei Rückgabewerte vorliegen.
34.6.4 Einschränkungen
Der XML-RPC-Standard ist nicht auf Python allein zugeschnitten, sondern es wurde bei der Ausarbeitung des Standards versucht, einen kleinsten gemeinsamen Nenner vieler Programmiersprachen zu finden, sodass beispielsweise Server und Client auch dann miteinander kommunizieren können, wenn sie in verschiedenen Sprachen geschrieben wurden.
Aus diesem Grund bringt die Verwendung von XML-RPC einige Einschränkungen mit sich, was die komplexeren bzw. exotischeren Datentypen von Python betrifft. So gibt es im XML-RPC-Standard beispielsweise keine Repräsentation der Datentypen complex, set und frozenset. Auch None darf nur verwendet werden, wenn dies bei der Instanziierung der Server- bzw. Clientklasse explizit angegeben wurde. Das bedeutet natürlich nur, dass Instanzen dieser Datentypen nicht über die XML-RPC-Schnittstelle versendet werden können. Programmintern können Sie sie weiterhin verwenden. Sollten Sie versuchen, beispielsweise eine Instanz des Datentyps complex als Rückgabewert einer Funktion über die XML-RPC-Schnittstelle zu versenden, wird eine xmlrpc.client.Fault-Exception geworfen. Es ist natürlich dennoch möglich, eine komplexe Zahl über eine XML-RPC-Schnittstelle zu schicken, indem Sie Real- und Imaginärteil getrennt jeweils als ganze Zahl übermitteln.
Tabelle 34.13 listet alle im XML-RPC-Standard vorgesehenen Datentypen auf und beschreibt, wie Sie diese in Python verwenden können.
XML-RPC | Python | Anmerkungen |
---|---|---|
boolesche Werte | bool | – |
ganze Zahlen | int | – |
Gleitkommazahlen | float | – |
Strings | str | – |
Arrays | list | In der Liste dürfen als Elemente nur XML-RPC-konforme Instanzen verwendet werden. |
Strukturen | dict | Alle Schlüssel müssen Strings sein. Als Werte dürfen nur XML-RPC-konforme Instanzen verwendet werden. |
Datum/Zeit | DateTime | Der spezielle Datentyp xmlrpc.client.DateTime wird verwendet.* |
Binärdaten | Binary | Der spezielle Datentyp xmlrpc.client.Binary wird verwendet. |
Nichts | None | Nur möglich, wenn der Client mit allow_none=True erzeugt wurde. |
Gleitkommazahlen mit beliebiger Genauigkeit | decimal.Decimal | – |
* Dabei handelt es sich nicht um den Datentyp datetime aus dem Modul datetime der Standardbibliothek. |
Es ist möglich, Instanzen von selbst erstellten Klassen zu verwenden. In einem solchen Fall wird die Instanz in ein Dictionary, also eine Struktur, umgewandelt, in der die Namen der enthaltenen Attribute als Schlüssel und die jeweils referenzierten Instanzen als Werte eingetragen werden. Dies geschieht automatisch. Beachten Sie jedoch, dass das auf der Gegenseite ankommende Dictionary nicht automatisch wieder in eine Instanz der ursprünglichen Klasse umgewandelt wird.
Die letzten beiden Datentypen, die in der Tabelle aufgelistet sind, sind uns noch nicht begegnet. Es handelt sich dabei um Datentypen, die im Modul xmlrpc.client enthalten und speziell auf die Verwendung im Zusammenhang mit XML-RPC zugeschnitten sind. Die beiden erwähnten Datentypen DateTime und Binary werden im Folgenden erläutert.
Der Datentyp DateTime
Der Datentyp DateTime des Moduls xmlrpc.client kann verwendet werden, um Datums- und Zeitangaben über eine XML-RPC-Schnittstelle zu versenden. Sofern der entsprechende Parameter bei der Instanziierung der ServerProxy-Instanz übergeben wurde, kann anstelle einer DateTime-Instanz auch direkt eine Instanz der bekannten Datentypen datetime.date, datetime.time oder datetime.datetime verwendet werden.
Bei der Erzeugung einer Instanz des Datentyps DateTime kann entweder einer der Datentypen des Moduls datetime übergeben werden oder ein UNIX-Timestamp als ganze Zahl:
>>> import xmlrpc.client
>>> import datetime
>>> xmlrpc.client.DateTime(987654321)
<DateTime '20010419T06:25:21' at 0x7f91671fb7f0>
>>> xmlrpc.client.DateTime(datetime.datetime(1970, 1, 1))
<DateTime '19700101T00:00:00' at 0x7f1d72595278>
Die erste DateTime-Instanz wurde aus einem UNIX-Timestamp erzeugt, während dem DateTime-Konstruktor bei der zweiten Instanziierung eine datetime.datetime-Instanz übergeben wurde.
Instanzen des Datentyps DateTime können Sie bedenkenlos in Form eines Rückgabewertes oder eines Parameters über eine XML-RPC-Schnittstelle senden.
Der Datentyp Binary
Der Datentyp Binary des Moduls xmlrpclib wird zum Versenden von Binärdaten über eine XML-RPC-Schnittstelle verwendet. Bei der Instanziierung des Datentyps Binary wird ein bytes-String übergeben, der die binären Daten enthält. Diese können auf der Gegenseite über das Attribut data wieder ausgelesen werden:
>>> import xmlrpc.client
>>> b = xmlrpc.client.Binary(b"\x00\x01\x02\x03")
>>> b.data
b'\x00\x01\x02\x03'
Instanzen des Datentyps Binary können Sie bedenkenlos in Form eines Rückgabewertes oder eines Parameters über eine XML-RPC-Schnittstelle senden.