Deep understanding XML-RPC Server – Client in Python


XMLRPC (XML Remote Procedure Call) which send XML through HTTP for transporting data is common way to communicate between Server and Client. Basically, Client can call methods with parameters on a remote server and get structured data for results. Python have XML-RPC standard library, which it’s mean native installed and ready to use.

First, I start with xmlrpclib (a class) for client access. There important objects inside xmlrpclib for communicate with remote server called ServerProxy(). Basically, you can start communicate with remote server by xmlrpclib.ServerProxy(URI).

There are 3 method inside ServerProxy() :

ServerProxy.system.listMethods()
This method returns a list of strings, one for each (non-system) method supported by the XML-RPC server.

ServerProxy.system.methodSignature(name)
This method takes one parameter, the name of a method implemented by the XML-RPC server. It returns an array of possible signatures for this method. A signature is an array of types. The first of these types is the return type of the method, the rest are parameters.

Because multiple signatures (ie. overloading) is permitted, this method returns a list of signatures rather than a singleton.

Signatures themselves are restricted to the top level parameters expected by a method. For instance if a method expects one array of structs as a parameter, and it returns a string, its signature is simply “string, array”. If it expects three integers and returns a string, its signature is “string, int, int, int”.

If no signature is defined for the method, a non-array value is returned. In Python this means that the type of the returned value will be something other than list.

ServerProxy.system.methodHelp(name)
This method takes one parameter, the name of a method implemented by the XML-RPC server. It returns a documentation string describing the use of that method. If no such string is available, an empty string is returned. The documentation string may contain HTML markup.

Results type?
Arrays are returned as lists. Also, dictionary keys must be strings, values may be any conformable type. Objects of user-defined classes can be passed in; only their __dict__ attribute is transmitted.

That are important thing in XMLRPC for client. Now, we moved into XMLRPC for server.

SimpleXMLRPCServer
FYI, The SimpleXMLRPCServer module has been merged into xmlrpc.server in Python 3.0. Also, SimpleXMLRPCServer class is based on SocketServer.TCPServer.

We need SimpleXMLRPCServer (class) to create server instance. This class provide methods for registration of function that can be called through XMLRPC protocols.

SimpleXMLRPCServer(addr[, requestHandler[, logRequests[, allow_none[, encoding[, bind_and_activate]]]])
So it class have several parameters like addr (URI), requestHandler, encoding, allow_non and bind that activated by default. Addr and RequestHandler are passed to SocketServer.TCPServer.

Also there two class that may you know. SimpleXMLRPCServer.CGIXMLRPCRequestHandler is XMLRPC server for CGI environment. SimpleXMLRPCServer.SimpleXMLRPCRequestHandler for handling request, suppport for POST request and modify logging.

Now we ready to break down objects inside SimpleXMLRPCServer which contains :

SimpleXMLRPCServer.register_function(function[, name])
Register a function that can respond to XML-RPC requests. If name is given, it will be the method name associated with function, otherwise function.__name__ will be used. Name can be either a normal or Unicode string, and may contain characters not legal in Python identifiers, including the period character.

SimpleXMLRPCServer.register_instance(instance[, allow_dotted_names])
Register an object which is used to expose method names which have not been registered using register_function(). If instance contains a _dispatch() method, it is called with the requested method name and the parameters from the request. Its API is def _dispatch(self, method, params) (note that params does not represent a variable argument list). If it calls an underlying function to perform its task, that function is called as func(*params), expanding the parameter list. The return value from _dispatch() is returned to the client as the result. If instance does not have a _dispatch() method, it is searched for an attribute matching the name of the requested method.

SimpleXMLRPCServer.register_introspection_functions()
Registers the XML-RPC introspection functions system.listMethods, system.methodHelp and system.methodSignature. This is helpfull to give client information about all methods name inside XMLRPC Server.

SimpleXMLRPCServer.register_multicall_functions()
Registers the XML-RPC multicall function system.multicall.

SimpleXMLRPCRequestHandler.rpc_paths
An attribute value that must be a tuple listing valid path portions of the URL for receiving XML-RPC requests. Requests posted to other paths will result in a 404 “no such page” HTTP error. If this tuple is empty, all paths will be considered valid. The default value is (‘/’, ‘/RPC2’).

SimpleXMLRPCRequestHandler.encode_threshold
If this attribute is not None, responses larger than this value will be encoded using the gzip transfer encoding, if permitted by the client. The default is 1400 which corresponds roughly to a single TCP packet.

That’s all of information about XMLRPC in Client and Server. Now we ready to create applications “xmlrpc-server.py” and “xmlrpc-client.py”.

xmlrpc-server.py :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

# We will restrict request only on http://localhost:8000/RPC2
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = (‘/RPC2’, )

# Example function
def server_status():
    return ‘connected!’

# Create an instance which all of the methods of the instance are published!
class SomeLibraries:
    def add(self, x, y):
        return x + y

    def minus(self, x, y):
        return x – y

if __name__ == ‘__main__’:
    # Create server instance on localhost:8000 with RequestHandler
    server = SimpleXMLRPCServer((‘localhost’, 8000),
                                requestHandler=RequestHandler)
    server.register_introspection_functions()

    # Register function server_status and rename it as "add" on client
    server.register_function(server_status, ‘check_status’)

    # Register Class instance
    server.register_instance(SomeLibraries())

    try:
        print("Press CTRL+C to exit. XMLRPC running on localhost:8000")
        server.serve_forever()
    except KeyboardInterrupt:
        print("XMLRPC exitting!")

xmlrpc-client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import xmlrpclib

# Connecting to XMLRPC Server and URL restricted on /RPC2
client = xmlrpclib.ServerProxy("http://localhost:8000/RPC2")

# Accessing XMLRPC register_function with rename on second arguments
print(client.check_status())

# Accessing XMLRPC Server Method on Class Instance
print(client.add(1, 2))
print(client.minus(12,3 ))

print client.system.listMethods()

Run the server then follw with clients and you will have this results :

1
2
3
4
connected!
3
9
[‘add’, ‘check_status’, ‘minus’, ‘system.listMethods’, ‘system.methodHelp’, ‘system.methodSignature’]

Now, we try with datetime in XMLRPC.

xmlrpc-server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import datetime
import xmlrpclib

# We will restrict request only on http://localhost:8000/RPC2
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = (‘/RPC2’, )

# Example function
def server_status():
    return ‘connected!’

# Datetime object
def server_time():
    timeserver = datetime.datetime.today()
    return xmlrpclib.DateTime(timeserver)

# Create an instance which all of the methods of the instance are published!
class SomeLibraries:
    def add(self, x, y):
        return x + y

    def minus(self, x, y):
        return x – y

if __name__ == ‘__main__’:
    # Create server instance on localhost:8000 with RequestHandler
    server = SimpleXMLRPCServer((‘localhost’, 8000),
                                requestHandler=RequestHandler)
    server.register_introspection_functions()

    # Register function server_status and rename it as "add" on client
    server.register_function(server_status, ‘check_status’)

    # Register Class instance
    server.register_instance(SomeLibraries())

    # Register function server_time_
    server.register_function(server_time, ‘server_time’)

    try:
        print("Press CTRL+C to exit. XMLRPC running on localhost:8000")
        server.serve_forever()
    except KeyboardInterrupt:
        print("XMLRPC exitting!")

xmlrpc-client.py :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import xmlrpclib
import datetime

# Connecting to XMLRPC Server
client = xmlrpclib.ServerProxy("http://localhost:8000/RPC2")

# Accessing XMLRPC register_function with rename on second arguments
print(client.check_status())

# Accessing XMLRPC Server Method on Class Instance
print(client.add(1, 2))
print(client.minus(12,3 ))

print(client.system.listMethods())

print(client.server_time())

# Convert into the ISO8601 string to a datetime object
# convert the ISO8601 string to a datetime object
today = client.server_time()
converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S")
print(converted)
print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M"))

It will produce datetime :

1
2
3
20111206T11:39:51
2011-12-06 11:39:51
Today: 06.12.2011, 11:39

Now we try wit hsending files using XMLRPC. First, create a.txt on same folder with xmlrpc-server.py :

1
touch a.txt && echo ‘hello world!’ > a.txt

Then we create XMLRPC Binary on xmlrpc-server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import datetime
import xmlrpclib

# We will restrict request only on http://localhost:8000/RPC2
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = (‘/RPC2’, )

# Example function
def server_status():
    return ‘connected!’

# Datetime object
def server_time():
    timeserver = datetime.datetime.today()
    return xmlrpclib.DateTime(timeserver)

# Sending a.txt to client
def download_files():
    with open("a.txt", "rb") as handle:
        return xmlrpclib.Binary(handle.read())

# Create an instance which all of the methods of the instance are published!
class SomeLibraries:
    def add(self, x, y):
        return x + y

    def minus(self, x, y):
        return x – y

if __name__ == ‘__main__’:
    # Create server instance on localhost:8000 with RequestHandler
    server = SimpleXMLRPCServer((‘localhost’, 8000),
                                requestHandler=RequestHandler)
    server.register_introspection_functions()

    # Register function server_status and rename it as "add" on client
    server.register_function(server_status, ‘check_status’)

    # Register Class instance
    server.register_instance(SomeLibraries())

    # Register function server_time
    server.register_function(server_time, ‘server_time’)

    # Register function download files
    server.register_function(download_files, ‘download_files’)

    try:
        print("Press CTRL+C to exit. XMLRPC running on localhost:8000")
        server.serve_forever()
    except KeyboardInterrupt:
        print("XMLRPC exitting!")

Then, we download it on xmlrpc-client.py :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import xmlrpclib
import datetime

# Connecting to XMLRPC Server
client = xmlrpclib.ServerProxy("http://localhost:8000/RPC2")

# Accessing XMLRPC register_function with rename on second arguments
print(client.check_status())

# Accessing XMLRPC Server Method on Class Instance
print(client.add(1, 2))
print(client.minus(12,3 ))

print(client.system.listMethods())

print(client.server_time())

# Convert into the ISO8601 string to a datetime object
# convert the ISO8601 string to a datetime object
today = client.server_time()
converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S")
print(converted)
print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M"))

# downloading a.txt into download-a.txt
with open("download-a.txt", "wb") as handle:
    handle.write(client.download_files().data)

Now you should have download-a.txt with same content a.txt on same folder.

Manage Error
When collaborating with XMLRPC Server, some cases we need to know the error and information about it. Then we should try with Fault Objects (xmlrpclib.Fault)

On xmlrpc-client.py, we will analyze add() methods on server.

1
2
3
4
5
6
7
# Try xmlrpclib.Fault()
try:
    client.add(2, )
except xmlrpclib.Fault, err:
    print "A Fault occured!"
    print "Fault code : %d" % err.faultCode
    print "Fault string: %s" % err.faultString

And it should give you result :

1
2
3
A Fault occured!
Fault code : 1
Fault string: <type ‘exceptions.TypeError’>:add() takes exactly 3 arguments (2 given)

ProtocolError Objects
Using ProtocolError will make debugging server more easy! Now, create a new client called “xmlrpc-test-protocol.py”.

1
2
3
4
5
6
7
8
9
10
11
12
13
import xmlrpclib

client = xmlrpclib.ServerProxy("http://basicpython.com")

try:
    client.someo_method()

except xmlrpclib.ProtocolError, err:
    print("A protocol error occured.")
    print("URL: %s" % err.url)
    print("HTTP/HTTPS headers : %s" % err.headers)
    print("Error code: %d" % err.errcode)
    print("Error message: %s" % err.errmsg)

It will give you results :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A protocol error occured.
URL: basicpython.com/RPC2
HTTP/HTTPS headers : Server: nginx/1.0.5
Date: Tue, 06 Dec 2011 05:47:21 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.3.6-13ubuntu3.2
X-Pingback: http://basicpython.com/xmlrpc.php
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Last-Modified: Tue, 06 Dec 2011 05:47:21 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Content-Encoding: gzip

Error code: 404
Error message: Not Found

Make multicall in single request
Xmlrpclib come with multicall feature which need it to be enable on XMLRPC Server and called on client.

xmlrpc-server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import datetime
import xmlrpclib

# We will restrict request only on http://localhost:8000/RPC2
class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = (‘/RPC2’, )

# Example function
def server_status():
    return ‘connected!’

# Datetime object
def server_time():
    timeserver = datetime.datetime.today()
    return xmlrpclib.DateTime(timeserver)

# Sending a.txt to client
def download_files():
    with open("a.txt", "rb") as handle:
        return xmlrpclib.Binary(handle.read())

# Create an instance which all of the methods of the instance are published!
class SomeLibraries:
    def add(self, x, y):
        return x + y

    def minus(self, x, y):
        return x – y

if __name__ == ‘__main__’:
    # Create server instance on localhost:8000 with RequestHandler
    server = SimpleXMLRPCServer((‘localhost’, 8000),
                                requestHandler=RequestHandler)
    server.register_introspection_functions()

    # Enable multicall
    server.register_multicall_functions()

    # Register function server_status and rename it as "add" on client
    server.register_function(server_status, ‘check_status’)

    # Register Class instance
    server.register_instance(SomeLibraries())

    # Register function server_time
    server.register_function(server_time, ‘server_time’)

    # Register function download files
    server.register_function(download_files, ‘download_files’)

    try:
        print("Press CTRL+C to exit. XMLRPC running on localhost:8000")
        server.serve_forever()
    except KeyboardInterrupt:
        print("XMLRPC exitting!")

xmlrpc-client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import xmlrpclib
import datetime

# Connecting to XMLRPC Server
client = xmlrpclib.ServerProxy("http://localhost:8000/RPC2")

# Accessing XMLRPC register_function with rename on second arguments
print(client.check_status())

# Accessing XMLRPC Server Method on Class Instance
print(client.add(1, 2))
print(client.minus(12,3 ))

print(client.system.listMethods())

print(client.server_time())

# Convert into the ISO8601 string to a datetime object
# convert the ISO8601 string to a datetime object
today = client.server_time()
converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S")
print(converted)
print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M"))

# downloading a.txt into download-a.txt
with open("download-a.txt", "wb") as handle:
    handle.write(client.download_files().data)

# multicall
multicall = xmlrpclib.MultiCall(client)
multicall.add(3, 2)
multicall.minus(4, 1)
multicall.check_status()
result = multicall()

print "3+2 = %d, 4-1 = %d, Check status with server %s" % tuple(result)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.