Remoting
========
Coupling Aspect Oriented Programming with different types of Python remoting
services makes it easy to convert your local application into a distributed
one. Technically, the remoting segment of Spring Python doesn't use AOP.
However, it is very similar in the concept that you won't have to modify either
your servers or your clients.
Distributed applications have multiple objects. These can be spread across
different instances of the Python interpreter on the same machine, as well
on different machines on the network. The key factor is that they need to talk
to each other. The developer shouldn't have to spend a large effort coding a
custom solution. Another common practice in the realm of distributed programming
is that fact that programmers often develop standalone. When it comes time to
distribute the application to production, the configuration may be very
different. Spring Python solves this by making the link between client and
server objects a step of configuration not coding.
In the context of this section of documentation, the term client refers to
a client-application that is trying to access some remote service. The service
is referred to as the server object. The term remote is subjective. It can
either mean a different thread, a different interpretor, or the other side
of the world over an Internet connection. As long as both parties agree on
the configuration, they all share the same solution.
External dependencies
---------------------
Spring Python currently supports and requires the installation of at least one of the libraries:
* `Pyro `_ (Python Remote Objects) - a pure Python transport mechanism
* `Pyro4 ` - (Python Remote Object v.4) - an updated version of the Pyro API.
* `Hessian `_ - support for Hessian has just started. So far, you can call
Python-to-Java based on libraries released from Caucho.
Remoting with PYRO (Python Remote Objects)
------------------------------------------
Decoupling a simple service, to setup for remoting
++++++++++++++++++++++++++++++++++++++++++++++++++
For starters, let's define a simple service::
class Service(object):
def get_data(self, param):
return "You got remote data => %s" % param
Now, we will create it locally and then call it::
service = Service()
print service.get_data("Hello")
"You got remote data => Hello"
.. highlight:: xml
Okay, imagine that you want to relocate this service to another instance of
Python, or perhaps another machine on your network. To make this easy, let's
utilize Inversion Of Control, and transform this service into a Spring service.
First, we need to define an application context. We will create a file called
*applicationContext.xml*::
.. highlight:: python
The client code is changed to this::
appContext = ApplicationContext(XMLConfig("applicationContext.xml"))
service = appContext.get_object("service")
print service.get_data("Hello")
"You got remote data => Hello"
Not too tough, ehh? Well, guess what. That little step just decoupled the
client from directly creating the service. Now we can step in and configure
things for remote procedure calls without the client knowing it.
Exporting a Spring Service Using :doc:`Inversion Of Control `
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.. highlight:: xml
In order to reach our service remotely, we have to export it. Spring Python
provides *PyroServiceExporter* to export your service through Pyro. Add this
to your application context::
Three things have happened:
* Our original service's object name has been changed to *remoteService*.
* Another object was introduced called *service_exporter*. It references object
*remoteService*, and provides a proxied interface through a Pyro URL.
* We created a client called *service*. That is the same name our client code it
looking for. It won't know the difference!
Hostname/Port overrides
>>>>>>>>>>>>>>>>>>>>>>>
Pyro defaults to advertising the service at *localhost:7766*. However, you can
easily override that by setting the *service_host* and *service_port* properties
of the *PyroServiceExporter* object, either through setter
or :ref:`constructor injection `::
In this variation, your service is being hosted on port 7000 instead of the
default 7766. This is also key, if you need to advertise to another IP address,
to make it visible to another host.
Now when the client runs, it will fetch the *PyroProxyFactory*, which will use
Pyro to look up the exported module, and end up calling our remote Spring
service. And notice how neither our service nor the client have changed!
.. note::
Python doesn't need an interface declaration for the client proxy
If you have used Spring Java's remoting client proxy beans, then you may be
used to the idiom of specifying the interface of the client proxy. Due to
Python's dynamic nature, you don't have to do this.
We can now split up this application into two objects. Running the remote
service on another server only requires us to edit the client's application
context, changing the URL to get to the service. All without telling the
client and server code.
Do I have to use XML?
+++++++++++++++++++++
No. Again, Spring Python provides you the freedom to do things using the
IoC container, or programmatically.
.. highlight:: python
To do the same configuration as shown above looks like this::
from springpython.remoting.pyro import PyroServiceExporter
from springpython.remoting.pyro import PyroProxyFactory
# Create the service
remoteService = Service()
# Export it via Pyro using Spring Python's utility classes
service_exporter = PyroServiceExporter()
service_exporter.service_name = "ServiceName"
service_exporter.service = remoteService
service_exporter.after_properties_set()
# Get a handle on a client-side proxy that will remotely call the service.
service = PyroProxyFactory()
service.service_url = "PYROLOC://127.0.0.1:7000/ServiceName"
# Call the service just you did in the original, simplified version.
print service.get_data("Hello")
Again, you can override the hostname/port values as well::
# ...
# Export it via Pyro using Spring Python's utility classes
service_exporter = PyroServiceExporter()
service_exporter.service_name = "ServiceName"
service_exporter.service = remoteService
service_exporter.service_host = "127.0.0.1" # or perhaps the machine's actual hostname
service_exporter.service_port = 7000
service_exporter.after_properties_set()
# ...
That is effectively the same steps that the IoC container executes.
.. note::
Don't forget after_properties_set!
Since *PyroServiceExporter* is an *InitializingObject*, you must call
*after_properties_set* in order for it to start the Pyro thread. Normally
the IoC container will do this step for you, but if you choose to create
the proxy yourself, you are responsible for this step.
Splitting up the client and the server
++++++++++++++++++++++++++++++++++++++
This configuration sets us up to run the server and the client in two different
Python VMs. All we have to do is split things into two parts.
.. highlight:: xml
Copy the following into *server.xml*::
.. highlight:: python
Copy the following into *server.py*::
import logging
from springpython.config import XMLConfig
from springpython.context import ApplicationContext
class Service(object):
def get_data(self, param):
return "You got remote data => %s" % param
if __name__ == "__main__":
# Turn on some logging in order to see what is happening behind the scenes...
logger = logging.getLogger("springpython")
loggingLevel = logging.DEBUG
logger.setLevel(loggingLevel)
ch = logging.StreamHandler()
ch.setLevel(loggingLevel)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
appContext = ApplicationContext(XMLConfig("server.xml"))
.. highlight:: xml
Copy the following into *client.xml*::
.. highlight:: python
Copy the following into *client.py*::
import logging
from springpython.config import XMLConfig
from springpython.context import ApplicationContext
if __name__ == "__main__":
# Turn on some logging in order to see what is happening behind the scenes...
logger = logging.getLogger("springpython")
loggingLevel = logging.DEBUG
logger.setLevel(loggingLevel)
ch = logging.StreamHandler()
ch.setLevel(loggingLevel)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
appContext = ApplicationContext(XMLConfig("client.xml"))
service = appContext.get_object("service")
print "CLIENT: %s" % service.get_data("Hello")
First, launch the server script, and then launch the client script, both on
the same machine. They should be able to talk to each other with no problem at
all, producing some log chatter like this:
::
$ python server.py &
[1] 20854
2009-01-08 12:06:20,021 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration for object definitions ===
2009-01-08 12:06:20,021 - springpython.config.XMLConfig - DEBUG - ==============================================================
2009-01-08 12:06:20,022 - springpython.config.XMLConfig - DEBUG - * Parsing server.xml
2009-01-08 12:06:20,025 - springpython.config.XMLConfig - DEBUG - ==============================================================
2009-01-08 12:06:20,025 - springpython.container.ObjectContainer - DEBUG - remoteService object definition does not exist. Adding to list of definitions.
2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - service_exporter object definition does not exist. Adding to list of definitions.
2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching remoteService
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'remoteService' in the singleton storage.
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=remoteService props=[] scope=scope.SINGLETON factory=ReflectiveObjectFactory(server.Service)
2009-01-08 12:06:20,026 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of server.Service
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Stored object 'remoteService' in container's singleton storage
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service_exporter
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service_exporter' in the singleton storage.
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service_exporter props=[, , , ] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroServiceExporter)
2009-01-08 12:06:20,028 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroServiceExporter
2009-01-08 12:06:20,028 - springpython.context.ApplicationContext - DEBUG - Stored object 'service_exporter' in container's singleton storage
2009-01-08 12:06:20,028 - springpython.remoting.pyro.PyroServiceExporter - DEBUG - Exporting ServiceName as a Pyro service at 127.0.0.1:7000
2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Registering ServiceName at 127.0.0.1:7000 with the Pyro server
2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Pyro thread needs to be started at 127.0.0.1:7000
2009-01-08 12:06:20,030 - springpython.remoting.pyro.PyroDaemonHolder._PyroThread - DEBUG - Starting up Pyro server thread for 127.0.0.1:7000
$ python client.py
2009-01-08 12:06:26,291 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration for object definitions ===
2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - ==============================================================
2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - * Parsing client.xml
2009-01-08 12:06:26,294 - springpython.config.XMLConfig - DEBUG - ==============================================================
2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - service object definition does not exist. Adding to list of definitions.
2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service' in the singleton storage.
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service props=[] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroProxyFactory)
2009-01-08 12:06:26,295 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroProxyFactory
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Stored object 'service' in container's singleton storage
CLIENT: You got remote data => Hello
This shows one instance of Python running the client, connecting to the instance
of Python hosting the server module. After that, moving these scripts to other
machines only requires changing the hostname in the XML files.
.. _pyro4:
New support for Pyro 4
++++++++++++++++++++++
Pyro has recently released a beta version of its overhauled API labeled *Pyro 4*. This release of Spring Python includes support for it. The only changes you will need to make are:
- replace **springpython.remoting.pyro.PyroProxyFactory** with **springpython.remoting.pyro.Pyro4ProxyFactory**
- replace **springpython.remoting.pyro.PyroServiceExporter** with **springpython.remoting.pyro.Pyro4ServiceExporter**
- replace any URI entries of **PYROLOC::/** with **PYRO:@:**
.. note::
Pyro 4 is unstable and still in development. For proper usage, you must install at least version 4.2+.
Their API is also subject to change, and we will try to keep up until it stabilizes.
Remoting with Hessian
---------------------
.. note::
Caucho's Python library for Hessian is incomplete
Due to minimal functionality provided by Caucho's Hessian library for Python,
there is minimal documentation to show its functionality.
.. highlight:: xml
The following shows how to connect a client to a Hessian-exported service.
This can theoretically be any technology. Currently, Java objects are converted
intoPpython dictionaries, meaning that the data and transferred, but there are
not method calls available::
The Caucho library appears to only support Python being a client, and not yet
as a service, so there is no *HessianServiceExporter* available yet.
High-Availability/Clustering Solutions
--------------------------------------
.. highlight:: python
This props you up for many options to increase availability. It is possible to
run a copy of the server on multiple machines. You could then institute some
type of round-robin router to go to different URLs. You could easily run ten
copies of the remote service::
pool = []
for i in range(10):
service_exporter = PyroServiceExporter(service_name = "ServiceName%s" % i, service = Service())
pool.append(service_exporter)
(Yeah, I know, you can probably do this in one line with a list comprehension).
Now you have ten copies of the server running, each under a distinct name.
For any client, your configuration is a slight tweak::
services = []
for i in range(10):
services.append(PyroProxyFactory(service_url = "PYROLOC://localhost:7766/ServiceName%s" % i))
Now you have an array of possible services to reach, easily spread between
different machines. With a little client-side utility class, we can implement
a round-robin solution::
class HighAvailabilityService(object):
def __init__(self, service_pool):
self.service_pool = service_pool
self.index = 0
def get_data(self, param):
self.index = (self.index+1) % len(self.service_pool)
try:
return self.service_pool[self.index].get_data(param)
except:
del(self.service_pool[i])
return self.get_data(param)
service = HighAvailabilityService(service_pool = services)
service.get_data("Hello")
service.get_data("World")
Notice how each call to the *HighAvailabilityService* class causes the internal
index to increment and roll over. If a service doesn't appear to be reachable,
it is deleted from the list and attempted again. A little more sophisticated
error handling should be added in case there are no services available. And
there needs to be a way to grow the services. But this gets us off to a good
start.
.. _remoting-secure-xml-rpc:
Secure XML-RPC
--------------
.. highlight:: python
Spring Python extends Python’s built-in XML-RPC mechanims by adding the
support for securing the communications path. You can choose whether to:
* simply encrypt the link,
* have server require a client certificate signed off by a given CA or a chain of CAs,
* validate the client certificate’s fields, for instance you can configure the server
to only allow requests if a commonName is equal to an upon agreed value
Note that you can use both the client and the server with other XML-RPC
implementations, there’s nothing preventing you from exposing secure XML-RPC to
Java or .NET clients or from connecting with the secure client to XML-RPC servers
implemented in other languages and technologies.
To aid with better understanding of how the components work out of the box,
you can download :ref:`sample keys and certificates `
prepared by the Spring Python team.
Be sure not to ever use the sample keys & certificates for anything serious outside your
testing environment, they are working and functional but because of private keys being available for
download they should only be used for learning of how Spring Python's
secure XML-RPC works.
Encrypted connection only
+++++++++++++++++++++++++
.. image:: gfx/sslxmlrpc-01.png
:align: center
The most basic setup which requires the server to have a private key and
a certificate and the client to have a list (possibly consisting of one
element only) of Certificate Authorities it is allowed to trust. Client will
connect to server only if the server’s certificate has been signed off by given
CAs. This is the most common way of performing SSL akin to what browsers do when
connecting to secure online sites that don’t require a client certificate such
as the majority of online banking sites.
In the code below the server exposes a Python’s built-in pow function over
encrypted XML-RPC link and the client invokes it to get the result. Server
uses its private key and a certificate which must have been signed off by
one of CAs the client is aware of::
# -*- coding: utf-8 -*-
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
class MySSLServer(SSLServer):
def __init__(self, *args, **kwargs):
super(MySSLServer, self).__init__(*args, **kwargs)
def register_functions(self):
self.register_function(pow)
host = "localhost"
port = 8000
keyfile = "./server-key.pem"
certfile = "./server-cert.pem"
server = MySSLServer(host, port, keyfile, certfile)
server.serve_forever()
::
# -*- coding: utf-8 -*-
# stdlib
import ssl
# Spring Python
from springpython.remoting.xmlrpc import SSLClient
server_location = "https://localhost:8000/RPC2"
ca_certs = "./ca-chain.pem"
client = SSLClient(server_location, ca_certs)
print client.pow(41, 3)
Server requires the client to have a certificate
++++++++++++++++++++++++++++++++++++++++++++++++
.. image:: gfx/sslxmlrpc-02.png
:align: center
Same as above but this time the client must authenticate itself using its
own certificate which must have been signed off by one of CAs known to the server.
Server is still required to have a certificate whose signing CAs need to be
known to the client::
# -*- coding: utf-8 -*-
# stdlib
import ssl
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
class MySSLServer(SSLServer):
def __init__(self, *args, **kwargs):
super(MySSLServer, self).__init__(*args, **kwargs)
def register_functions(self):
self.register_function(pow)
host = "localhost"
port = 8000
keyfile = "./server-key.pem"
certfile = "./server-cert.pem"
ca_certs = "./ca-chain.pem"
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED)
server.serve_forever()
::
# -*- coding: utf-8 -*-
# Spring Python
from springpython.remoting.xmlrpc import SSLClient
server_location = "https://localhost:8000/RPC2"
keyfile = "./client-key.pem"
certfile = "./client-cert.pem"
ca_certs = "./ca-chain.pem"
client = SSLClient(server_location, ca_certs, keyfile, certfile)
print client.pow(41, 3)
Server requires the client to have a certificate and checks its fields
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.. image:: gfx/sslxmlrpc-03.png
:align: center
Same as above (both sides need to have certificates signed off by trusted CAs)
but this time the server inspects the client certificate’s fields and lets it
in only if they match the configuration it was fed with. In the example below
*commonName* must be *My Client*, *organizationName* must be *My Company* and the
*stateOrProvinceName* must be *My State*. Server checks for both their existance and value and
if there’s any mismatch the connection will be dropped (client will receive a socket
error) and the error reason will be logged on the server side but no details of the error
will be leaked to the client::
# -*- coding: utf-8 -*-
# stdlib
import logging
import ssl
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
class MySSLServer(SSLServer):
def __init__(self, *args, **kwargs):
super(MySSLServer, self).__init__(*args, **kwargs)
def register_functions(self):
self.register_function(pow)
host = "localhost"
port = 8000
keyfile = "./server-key.pem"
certfile = "./server-cert.pem"
ca_certs = "./ca-chain.pem"
verify_fields = {"commonName": "My Client", "organizationName":"My Company",
"stateOrProvinceName":"My State"}
logging.basicConfig(level=logging.ERROR)
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED,
verify_fields=verify_fields)
server.serve_forever()
::
# -*- coding: utf-8 -*-
# Spring Python
from springpython.remoting.xmlrpc import SSLClient
server_location = "https://localhost:8000/RPC2"
keyfile = "./client-key.pem"
certfile = "./client-cert.pem"
ca_certs = "./ca-chain.pem"
client = SSLClient(server_location, ca_certs, keyfile, certfile)
print client.pow(41, 3)
.. _remoting-secure-xml-rpc-sample-keys-and-certificates:
Sample keys and certificates
++++++++++++++++++++++++++++
`The downloadable package <./_static/pki.zip>`_ contains the keys and certificates of CAs, client and
the server shown in the examples. It's crucial to remember that these are only
samples with known private keys and they should **only** be used for playing around
with SSL XML-RPC's API.
.. image:: gfx/pki.png
:align: center
*client-key.pem* and *client-cert.pem* are the client's private key and its
certificate while *server-key.pem* and *server-cert.pem* are their counterparts
as used by the server. Both certificates have been signed off by the *SAMPLE Signing CA*
whose certificate has been in turn signed off by the *SAMPLE Root CA*. SAMPLE Root
CA's certificate is self-signed. Private keys of CAs are in files *ca-root-key.pem* and
*ca-signing-key.pem*. Certificates of both CAs - *ca-root-cert.pem* & *ca-signing-cert.pem*
have been concatenated into a *ca-chain.pem* file so that they form a chain of the
Certificate Authorities both sides may trust. All certificates are valid until
2020 so there's a lot of time for experimenting. Type **1234** if asked for any
password, it's the same one for each private key.
.. _remoting-secure-xml-rpc-configuration:
Configuration
+++++++++++++
The two main classes to use in secure XML-RPC communications are
:ref:`springpython.remoting.xmlrpc.SSLServer `
and
:ref:`springpython.remoting.xmlrpc.SSLClient `
both of which support a number of options discussed below.
Keep in mind that those classes are thin wrappers around the base objects found
in Python's standard library and as such they always accept all the default arguments
of their super-classes along with those specific to Spring Python's secure XML-RPC
implementation.
.. _remoting-secure-xml-config-sslserver:
SSLServer
>>>>>>>>>
SSLServer is a subclass of Python's
`SimpleXMLRPCServer.SimpleXMLRPCServer `_
which accepts arguments related to SSL in addition to those inherited from
the base class. You expose XML-RPC services by extending SSLServer in your
own subclass which is required to override one method, *register_functions*.
*register_functions* may in turn use *self.register_function* for exposing those
methods that should be accessible via XML-RPC, see
`Python's documentation `_
for details of using *self.register_function*.
SSLServer.__init__'s default arguments::
class SSLServer(object, SimpleXMLRPCServer):
def __init__(self, host=None, port=None, keyfile=None, certfile=None,
ca_certs=None, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_TLSv1,
do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None,
log_requests=True, **kwargs):
* *host* - interface to listen on, e.g. "localhost",
* *port* - port to listen on, e.g. 8000,
* *keyfile* - path to a PEM-encoded private key of the server, e.g. "./server-key.pem",
* *certfile* - path to a PEM-encoded certificate of the server, e.g. "./server-cert.pem",
* *ca_certs* - path to a PEM-encoded list (possibly one element long) of certificates
of Certificate Authorities signing the certificates of clients you deal with,
e.g. "./ca-chain.pem",
* *cert_reqs* - whether the client is required to authenticate itself with a certificate,
see `Python's documentation `_
for supported values,
* *ssl_version* - the SSL/TLS version to use, see
`Python's documentation `_
for supported values, note that the same value **must** be used by the client
application,
* *do_handshake_on_connect* - `same as in Python `_,
* *suppress_ragged_eofs* - `same as in Python `_,
* *ciphers* - `same as in Python `_,
the value will be silently ignored if not running Python 2.7 or newer,
* *log_requests* - whether requests should be logged on stdout, the value is actually
passed directly to the request handler and that's why in current version
it doesn't allow for any customization such as using different logging formats. To keep
it compatible with Python, the value is accessible under a camelCase *.logRequests*
attribute of an SSLServer object,
* *\**kwargs* - an open-ended list of keyword arguments, currently the only
argument being recognized is *verify_fields* which must be a dictionary
containing fields and values of the client certificate that must exist when the client's
connecting. Fields names should be in the format given in `Appendix A of RFC 3280 `_,
which means using long names instead of short ones (commonName not CN, organizationName not O, etc.),
for instance, setting verify_fields to:
::
{"commonName":"My Client", "localityName":"My Town"}
will make sure the client certificate's subject has both commonName and localityName
set and will also validate their respective values. The connection will not
be accepted unless the fields and values match.
.. _remoting-secure-xml-config-sslserver-sample:
Sample SSL XML-RPC server which expects the client to use a certificate whose
fields must match the configuration. The server exposes one method, *listdir*::
# -*- coding: utf-8 -*-
# stdlib
import logging
import os
import ssl
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
class MySSLServer(SSLServer):
def __init__(self, *args, **kwargs):
super(MySSLServer, self).__init__(*args, **kwargs)
def _listdir(self, path):
return os.listdir(path)
def register_functions(self):
self.register_function(self._listdir, "listdir")
host = "localhost"
port = 8000
keyfile = "./server-key.pem"
certfile = "./server-cert.pem"
ca_certs = "./ca-chain.pem"
verify_fields = {"commonName": "My Client", "organizationName":"My Company",
"stateOrProvinceName":"My State"}
logging.basicConfig(level=logging.ERROR)
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED,
verify_fields=verify_fields)
server.serve_forever()
.. _remoting-secure-xml-config-sslclient:
SSLClient
>>>>>>>>>
SSLClient extends Python's built-in
`xmlrpclib.ServerProxy `_
class and, unlike :ref:`SSLServer `,
can be used directly without the need for subclassing. You can simply create
an instance and start invoking server's methods.
SSLClient.__init__’s default arguments::
class SSLClient(ServerProxy):
def __init__(self, uri=None, ca_certs=None, keyfile=None, certfile=None,
cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1,
transport=None, encoding=None, verbose=0, allow_none=0, use_datetime=0,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, strict=None):
* *uri* - address of the XML-RPC server, e.g. "https://localhost:8000/RPC2",
* *ca_certs* - path to a PEM-encoded list (possibly one element long) containing certificates
of Certificate Authorities the client is to trust; client will be establishing
authenticity of the server's certificate against certificates from that file;
e.g. "./ca-chain.pem",
* *keyfile* - path to a PAM-encoded private key of the client, e.g. "./client-key.pem",
* *certfile* - path to a PAM-encoded certificate of the client, e.g. "./client-key.pem",
* *cert_reqs* - whether a server is required to have a certificate,
see `Python's documentation `_
for supported values,
* *ssl_version* - the SSL/TLS version to use, see
`Python's documentation `_
for supported values, note that the same value **must** be used by the server,
* *transport* - `same as in Python `_,
* *encoding* - `same as in Python `_,
* *verbose* - `same as in Python `_,
* *allow_none* - `same as in Python `_,
* *use_datetime* - `same as in Python `_,
* *timeout* - `same as in Python `_,
* *strict* - `same as in Python `_
Sample SSL XML-RPC client which uses a private key and a certificate, can be
used for invoking the :ref:`server `
shown in previous chapter::
# -*- coding: utf-8 -*-
# Spring Python
from springpython.remoting.xmlrpc import SSLClient
server_location = "https://localhost:8000/RPC2"
keyfile = "./client-key.pem"
certfile = "./client-cert.pem"
ca_certs = "./ca-chain.pem"
client = SSLClient(server_location, ca_certs, keyfile, certfile)
print client.listdir("/home")
.. _remoting-secure-xml-rpc-logging:
Logging
+++++++
.. _remoting-secure-xml-logging-sslserver:
SSLServer
>>>>>>>>>
Your subclass of SSLServer can be configured to use Python's
standard `logging `_ module.
Currently, logging events are emitted at *logging.DEBUG* and *logging.ERROR* levels.
At ERROR level all failed attempts at validating of client certificates will
be logged giving the exact reason for the failure. Interal errors (should they ever happen)
are also logged at the ERROR level.
When told to run at DEBUG level, in addition to information logged at the ERROR level,
the server will also log details of each client's certificate received along with
the IP address of a client application connecting.
A server also accepts a *log_requests* boolean argument, defaulting to True,
which is passed directly to the underlying stdlib's mechanisms. The flag tells whether
client requests should be printed on stdout.
A sample SSL XML-RPC server running with full verbosity turned on::
# -*- coding: utf-8 -*-
# stdlib
import logging
import os
import ssl
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
class MySSLServer(SSLServer):
def __init__(self, *args, **kwargs):
super(MySSLServer, self).__init__(*args, **kwargs)
def _listdir(self, path):
return os.listdir(path)
def register_functions(self):
self.register_function(self._listdir, "listdir")
host = "localhost"
port = 8000
keyfile = "./server-key.pem"
certfile = "./server-cert.pem"
ca_certs = "./ca-chain.pem"
verify_fields = {"commonName": "My Client", "organizationName":"My Company",
"stateOrProvinceName":"My State"}
log_format = "%(asctime)s - %(levelname)s - %(process)d - %(threadName)s - %(name)s - %(message)s"
formatter = logging.Formatter(log_format)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger("MySSLServer")
logger.setLevel(level=logging.DEBUG)
logger.addHandler(handler)
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED,
verify_fields=verify_fields)
server.serve_forever()
.. _remoting-secure-xml-logging-sslclient:
SSLClient
>>>>>>>>>
Although SSLClient does define a self.logger object it isn't currently used
internally in any situation (subject to change without notice so you shouldn't
rely on the current status). On the other hand, as a subclass of
`xmlrpclib.ServerProxy `_,
the client may be configured to run in a *verbose* mode which means all HTTP traffic
will be printed onto *standard output*.
A sample SSL XML-RPC client configured to use the verbose mode::
# -*- coding: utf-8 -*-
# Spring Python
from springpython.remoting.xmlrpc import SSLClient
server_location = "https://localhost:8000/RPC2"
keyfile = "./client-key.pem"
certfile = "./client-cert.pem"
ca_certs = "./ca-chain.pem"
client = SSLClient(server_location, ca_certs, keyfile, certfile, verbose=1)
print client.listdir("/home")