3636 <section id =" remoting-pyro" >
3737 <title >Remoting with PYRO (Python Remote Objects)</title >
3838
39- <section id =" remoting-pyro-conversion " >
40- <title >Converting a local service to an remote one </title >
39+ <section id =" remoting-pyro-decoupling " >
40+ <title >Decoupling a simple service, to setup for remoting </title >
4141
4242 <para >For starters, let's define a simple service.</para >
4343
@@ -133,10 +133,54 @@ print service.get_data("Hello")
133133 </listitem >
134134 </orderedlist >
135135
136+ <section id =" remoting-pyro-hostname-port" >
137+ <title >Hostname/Port overrides</title >
138+
139+ <para >Pyro defaults to advertising the service at <emphasis >localhost:7766</emphasis >. However, you
140+ can easily override that by setting the <methodname >service_host</methodname > and
141+ <methodname >service_port</methodname > properties of the <classname >PyroServiceExporter</classname >
142+ object, either through setter or
143+ <link linkend =" objects-config-xmlconfig-constructors" >constructor injection</link >.</para >
144+
145+ <programlisting ><![CDATA[
146+ <?xml version="1.0" encoding="UTF-8"?>^M
147+ <objects xmlns="http://www.springframework.org/springpython/schema/objects"^M
148+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"^M
149+ xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects^M
150+ http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">^M
151+
152+ <object id="remoteService" class="Service"/>
153+
154+ <object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter">
155+ <property name="service_name" value="ServiceName"/>
156+ <property name="service" ref="remoteService"/>
157+ <property name="service_host" value="127.0.0.1"/>
158+ <property name="service_port" value="7000"/>
159+ </object>
160+
161+ <object id="service" class="springpython.remoting.pyro.PyroProxyFactory">
162+ <property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/>
163+ </object>
164+
165+ </objects>
166+ ]]> </programlisting >
167+
168+ <para >In this variation, your service is being hosted on port 7000 instead of the default 7766. This is
169+ also key, if you need to advertise to another IP address, to make it visible to another host.</para >
170+
171+ </section >
172+
136173 <para >Now when the client runs, it will fetch the <classname >PyroProxyFactory</classname >, which will use Pyro
137174 to look up the exported module, and end up calling our remote Spring service. And notice how neither our service
138175 nor the client have changed!</para >
139176
177+ <note >
178+ <title >Python doesn't need an interface declaration for the client proxy</title >
179+
180+ <para >If you have used Spring Java's remoting client proxy beans, then you may be used to the idiom
181+ of specifying the interface of the client proxy. Due to Python's dynamic nature, you don't have to do this.</para >
182+ </note >
183+
140184 <para >We can now split up this application into two objects. Running the remote service on another server only
141185 requires us to edit the client's application context, changing the URL to get to the service. All without telling
142186 the client and server code.</para >
@@ -161,6 +205,7 @@ remoteService = Service()
161205service_exporter = PyroServiceExporter()
162206service_exporter.service_name = "ServiceName"
163207service_exporter.service = remoteService
208+ service_exporter.after_properties_set()
164209
165210# Get a handle on a client-side proxy that will remotely call the service.
166211service = PyroProxyFactory()
@@ -170,11 +215,173 @@ service.service_url = "PYROLOC://localhost:7766/ServiceName"
170215print service.get_data("Hello")
171216]]> </programlisting >
172217
173- <para >That is effectively the same steps that the IoC container executes. And it is just as easy to split this
174- up between two machines. Chop the script and run the <classname >PyroServiceExporter</classname > on one machine,
175- and run the <classname >PyroProxyFactory</classname > on the other. Then all you have to do is edit the
176- <emphasis >service_url</emphasis >.</para >
177-
218+ <para >Against, you can override the hostname/port values as well</para >
219+
220+ <programlisting ><![CDATA[
221+ ...
222+ # Export it via Pyro using Spring Python's utility classes
223+ service_exporter = PyroServiceExporter()
224+ service_exporter.service_name = "ServiceName"
225+ service_exporter.service = remoteService
226+ service_exporter.service_host = "127.0.0.1" # or perhaps the machines actual hostname
227+ service_exporter.service_port = 7000
228+ service_exporter.after_properties_set()
229+ ...
230+ ]]> </programlisting >
231+
232+ <para >That is effectively the same steps that the IoC container executes.</para >
233+
234+ <note >
235+ <title >Don't forget after_properties_set!</title >
236+
237+ <para >Since <classname >PyroServiceExporter</classname > is an <classname >InitializingObject</classname >,
238+ you must call <methodname >after_properties_set</methodname > in order for it to start the Pyro thread.
239+ Normally the IoC container will do this step for you, but if you choose to create the proxy yourself,
240+ you are responsible for this step.</para >
241+ </note >
242+
243+ </section >
244+
245+ <section id =" remoting-pyro-client-server" >
246+ <title >Splitting up the client and the server</title >
247+
248+ <para >This configuration sets us up to run the server and the client in two different Python VMs. All we have
249+ to do is split things into two parts.</para >
250+
251+ <para >Copy the following into <filename >server.xml</filename >:</para >
252+
253+ <programlisting ><![CDATA[
254+ <?xml version="1.0" encoding="UTF-8"?>
255+ <objects xmlns="http://www.springframework.org/springpython/schema/objects"
256+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
257+ xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
258+ http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
259+
260+ <object id="remoteService" class="server.Service"/>
261+
262+ <object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter">
263+ <property name="service_name" value="ServiceName"/>
264+ <property name="service" ref="remoteService"/>
265+ <property name="service_host" value="127.0.0.1"/>
266+ <property name="service_port" value="7000"/>
267+ </object>
268+
269+ </objects>
270+ ]]> </programlisting >
271+
272+ <para >Copy the following into <filename >server.py</filename >:</para >
273+
274+ <programlisting ><![CDATA[
275+ import logging
276+ from springpython.config import XMLConfig
277+ from springpython.context import ApplicationContext
278+
279+ class Service(object):
280+ def get_data(self, param):
281+ return "You got remote data => %s" % param
282+
283+ if __name__ == "__main__":
284+ # Turn on some logging in order to see what is happening behind the scenes...
285+ logger = logging.getLogger("springpython")
286+ loggingLevel = logging.DEBUG
287+ logger.setLevel(loggingLevel)
288+ ch = logging.StreamHandler()
289+ ch.setLevel(loggingLevel)
290+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
291+ ch.setFormatter(formatter)
292+ logger.addHandler(ch)
293+
294+ appContext = ApplicationContext(XMLConfig("server.xml"))
295+ ]]> </programlisting >
296+
297+ <para >Copy the following into <filename >client.xml</filename >:</para >
298+
299+ <programlisting ><![CDATA[
300+ <?xml version="1.0" encoding="UTF-8"?>
301+ <objects xmlns="http://www.springframework.org/springpython/schema/objects"
302+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
303+ xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
304+ http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
305+
306+ <object id="service" class="springpython.remoting.pyro.PyroProxyFactory">
307+ <property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/>
308+ </object>
309+
310+ </objects>
311+ ]]> </programlisting >
312+
313+ <para >Copy the following into <filename >client.py</filename >:</para >
314+ <programlisting ><![CDATA[
315+ import logging
316+ from springpython.config import XMLConfig
317+ from springpython.context import ApplicationContext
318+
319+ if __name__ == "__main__":
320+ # Turn on some logging in order to see what is happening behind the scenes...
321+ logger = logging.getLogger("springpython")
322+ loggingLevel = logging.DEBUG
323+ logger.setLevel(loggingLevel)
324+ ch = logging.StreamHandler()
325+ ch.setLevel(loggingLevel)
326+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
327+ ch.setFormatter(formatter)
328+ logger.addHandler(ch)
329+
330+ appContext = ApplicationContext(XMLConfig("client.xml"))
331+ service = appContext.get_object("service")
332+ print "CLIENT: %s" % service.get_data("Hello")
333+ ]]> </programlisting >
334+
335+ <para >First, launch the server script, and then launch the client script, both on the same machine. They should
336+ be able to talk to each other with no problem at all, producing some log chatter like this:</para >
337+
338+ <programlisting ><![CDATA[
339+ $ python server.py &
340+ [1] 20854
341+
342+ 2009-01-08 12:06:20,021 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7fa276c> for object definitions ===
343+ 2009-01-08 12:06:20,021 - springpython.config.XMLConfig - DEBUG - ==============================================================
344+ 2009-01-08 12:06:20,022 - springpython.config.XMLConfig - DEBUG - * Parsing server.xml
345+ 2009-01-08 12:06:20,025 - springpython.config.XMLConfig - DEBUG - ==============================================================
346+ 2009-01-08 12:06:20,025 - springpython.container.ObjectContainer - DEBUG - remoteService object definition does not exist. Adding to list of definitions.
347+ 2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - service_exporter object definition does not exist. Adding to list of definitions.
348+ 2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
349+ 2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching remoteService
350+ 2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'remoteService' in the singleton storage.
351+ 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)
352+ 2009-01-08 12:06:20,026 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of server.Service
353+ 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Stored object 'remoteService' in container's singleton storage
354+ 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service_exporter
355+ 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service_exporter' in the singleton storage.
356+ 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service_exporter props=[<springpython.config.ValueDef object at 0xb7a4664c>, <springpython.config.ReferenceDef object at 0xb7a468ac>, <springpython.config.ValueDef object at 0xb7a4692c>, <springpython.config.ValueDef object at 0xb7a46d2c>] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroServiceExporter)
357+ 2009-01-08 12:06:20,028 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroServiceExporter
358+ 2009-01-08 12:06:20,028 - springpython.context.ApplicationContext - DEBUG - Stored object 'service_exporter' in container's singleton storage
359+ 2009-01-08 12:06:20,028 - springpython.remoting.pyro.PyroServiceExporter - DEBUG - Exporting ServiceName as a Pyro service at 127.0.0.1:7000
360+ 2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Registering ServiceName at 127.0.0.1:7000 with the Pyro server
361+ 2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Pyro thread needs to be started at 127.0.0.1:7000
362+ 2009-01-08 12:06:20,030 - springpython.remoting.pyro.PyroDaemonHolder._PyroThread - DEBUG - Starting up Pyro server thread for 127.0.0.1:7000
363+
364+ $ python client.py
365+
366+ 2009-01-08 12:06:26,291 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7ed45ac> for object definitions ===
367+ 2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - ==============================================================
368+ 2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - * Parsing client.xml
369+ 2009-01-08 12:06:26,294 - springpython.config.XMLConfig - DEBUG - ==============================================================
370+ 2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - service object definition does not exist. Adding to list of definitions.
371+ 2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
372+ 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service
373+ 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service' in the singleton storage.
374+ 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service props=[<springpython.config.ValueDef object at 0xb797948c>] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroProxyFactory)
375+ 2009-01-08 12:06:26,295 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroProxyFactory
376+ 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Stored object 'service' in container's singleton storage
377+
378+ CLIENT: You got remote data => Hello
379+ ]]> </programlisting >
380+
381+ <para >This shows one instance of Python running the client, connecting to the instance of Python hosting the
382+ server module. After that, moving these scripts to other machines only requires changing the hostname
383+ in the XML files.</para >
384+
178385 </section >
179386
180387
@@ -239,5 +446,5 @@ service.get_data("World")
239446 be a way to grow the services. But this gets us off to a good start.</para >
240447
241448 </section >
242-
449+
243450</chapter >
0 commit comments