]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/rpcMixin.py
2 # This was modified from rpcMixin.py distributed with wxPython
4 #----------------------------------------------------------------------
7 # Purpose: provides xmlrpc server functionality for wxPython
8 # applications via a mixin class
10 # Requires: (1) Python with threading enabled.
11 # (2) xmlrpclib from PythonWare
12 # (http://www.pythonware.com/products/xmlrpc/)
13 # the code was developed and tested using version 0.9.8
15 # Author: greg Landrum (Landrum@RationalDiscovery.com)
17 # Copyright: (c) 2000, 2001 by Greg Landrum and Rational Discovery LLC
18 # Licence: wxWindows license
19 #----------------------------------------------------------------------
20 # 12/11/2003 - Jeff Grimmett (grimmtooth@softhome.net)
22 # o 2.5 compatability update.
23 # o xmlrpcserver not available.
26 """provides xmlrpc server functionality for wxPython applications via a mixin class
30 1) The xmlrpc server runs in a separate thread from the main GUI
31 application, communication between the two threads using a custom
32 event (see the Threads demo in the wxPython docs for more info).
34 2) Neither the server nor the client are particularly smart about
35 checking method names. So it's easy to shoot yourself in the foot
36 by calling improper methods. It would be pretty easy to add
37 either a list of allowed methods or a list of forbidden methods.
39 3) Authentication of xmlrpc clients is *not* performed. I think it
40 would be pretty easy to do this in a hacky way, but I haven't done
43 4) See the bottom of this file for an example of using the class.
45 **Obligatory disclaimer:**
46 This is my first crack at both using xmlrpc and multi-threaded
47 programming, so there could be huge horrible bugs or design
48 flaws. If you see one, I'd love to hear about them.
54 23 May 2001: Version bumped to 0.2.0
55 Numerous code and design changes
57 21 Mar. 2001: Version bumped to 0.1.4
58 Updated rpcMixin.OnExternal to support methods with further references
59 (i.e. now you can do rpcClient.foo.bar() and have it work)
60 This probably ain't super legal in xmlrpc land, but it works just fine here
63 6 Mar. 2001: Version bumped to 0.1.3
64 Documentation changes to make this compatible with happydoc
66 21 Jan. 2001: Version bumped to 0.1.2
67 OnExternal() method in the mixin class now uses getattr() to check if
68 a desired method is present. It should have been done this way in
70 14 Dec. 2000: Version bumped to 0.1.1
71 rearranged locking code and made other changes so that multiple
72 servers in one application are possible.
90 """A wrapper to use for handling requests and their responses"""
94 # here's the ID for external events
95 wxEVT_EXTERNAL_EVENT
= wx
.NewEventType()
96 EVT_EXTERNAL_EVENT
= wx
.PyEventBinder(wxEVT_EXTERNAL_EVENT
, 0)
98 class ExternalEvent(wx
.PyEvent
):
99 """The custom event class used to pass xmlrpc calls from
100 the server thread into the GUI thread
103 def __init__(self
,method
,args
):
104 wx
.PyEvent
.__init
__(self
)
105 self
.SetEventType(wxEVT_EXTERNAL_EVENT
)
108 self
.rpcStatus
= RPCRequest()
109 self
.rpcStatusLock
= threading
.Lock()
110 self
.rpcCondVar
= threading
.Condition()
115 self
.rpcStatus
= None
116 self
.rpcStatusLock
= None
117 self
.rpcondVar
= None
119 class Handler(xmlrpcserver
.RequestHandler
):
120 """The handler class that the xmlrpcserver actually calls
121 when a request comes in.
124 def log_message(self
,*args
):
125 """ causes the server to stop spewing messages every time a request comes in
129 def call(self
,method
,params
):
130 """When an xmlrpc request comes in, this is the method that
135 - method: name of the method to be called
137 - params: arguments to that method
140 if method
== '_rpcPing':
141 # we just acknowledge these without processing them
144 # construct the event
145 evt
= ExternalEvent(method
,params
)
147 # update the status variable
148 evt
.rpcStatusLock
.acquire()
149 evt
.rpcStatus
.status
= rpcPENDING
150 evt
.rpcStatusLock
.release()
152 evt
.rpcCondVar
.acquire()
153 # dispatch the event to the GUI
154 wx
.PostEvent(self
._app
,evt
)
156 # wait for the GUI to finish
157 while evt
.rpcStatus
.status
== rpcPENDING
:
158 evt
.rpcCondVar
.wait()
159 evt
.rpcCondVar
.release()
160 evt
.rpcStatusLock
.acquire()
161 if evt
.rpcStatus
.status
== rpcEXCEPT
:
162 # The GUI threw an exception, release the status lock
163 # and re-raise the exception
164 evt
.rpcStatusLock
.release()
165 raise evt
.rpcStatus
.result
[0],evt
.rpcStatus
.result
[1]
167 # everything went through without problems
168 s
= evt
.rpcStatus
.result
170 evt
.rpcStatusLock
.release()
175 # this global Event is used to let the server thread
176 # know when it should quit
177 stopEvent
= threading
.Event()
180 class _ServerThread(threading
.Thread
):
181 """ this is the Thread class which actually runs the server
184 def __init__(self
,server
,verbose
=0):
185 self
._xmlServ
= server
186 threading
.Thread
.__init
__(self
,verbose
=verbose
)
191 def shouldStop(self
):
192 return stopEvent
.isSet()
195 while not self
.shouldStop():
196 self
._xmlServ
.handle_request()
200 """A mixin class to provide xmlrpc server functionality to wxPython
203 If you want to customize this, probably the best idea is to
204 override the OnExternal method, which is what's invoked when an
209 # we'll try a range of ports for the server, this is the size of the
210 # range to be scanned
212 if sys
.platform
== 'win32':
217 def __init__(self
,host
='',port
=-1,verbose
=0,portScan
=1):
222 - host: (optional) the hostname for the server
224 - port: (optional) the port the server will use
226 - verbose: (optional) if set, the server thread will be launched
229 - portScan: (optional) if set, we'll scan across a number of ports
230 to find one which is avaiable
236 self
.Bind(EVT_EXTERNAL_EVENT
,self
.OnExternal
)
237 if hasattr(self
,'OnClose'):
238 self
._origOnClose
= self
.OnClose
239 self
.Disconnect(-1,-1,wx
.EVT_CLOSE_WINDOW
)
241 self
._origOnClose
= None
242 self
.OnClose
= self
.RPCOnClose
243 self
.Bind(wx
.EVT_CLOSE
,self
.RPCOnClose
)
245 tClass
= new
.classobj('Handler%d'%(port),(Handler
,),{})
249 for i
in xrange(self
.nPortsToTry
):
251 xmlServ
= SocketServer
.TCPServer((host
,port
+i
),tClass
)
255 self
.rpcPort
= port
+i
259 xmlServ
= SocketServer
.TCPServer((host
,port
),tClass
)
263 if self
.rpcPort
== -1:
264 raise 'RPCMixinError','Cannot initialize server'
265 self
.servThread
= _ServerThread(xmlServ
,verbose
=self
.verbose
)
266 self
.servThread
.setName('XML-RPC Server')
267 self
.servThread
.start()
269 def RPCOnClose(self
,event
):
270 """ callback for when the application is closed
272 be sure to shutdown the server and the server thread before
276 # by setting the global stopEvent we inform the server thread
277 # that it's time to shut down.
279 if event
is not None:
280 # if we came in here from a user event (as opposed to an RPC event),
281 # then we'll need to kick the server one last time in order
282 # to get that thread to terminate. do so now
283 s1
= xmlrpclib
.Server('http://localhost:%d'%(self
.rpcPort
))
289 if self
._origOnClose
is not None:
290 self
._origOnClose
(event
)
293 """ shuts down everything, including the rpc server
296 self
.RPCOnClose(None)
297 def OnExternal(self
,event
):
298 """ this is the callback used to handle RPCs
302 - event: an _ExternalEvent_ sent by the rpc server
304 Exceptions are caught and returned in the global _rpcStatus
305 structure. This allows the xmlrpc server to report the
306 exception to the client without mucking up any of the delicate
310 event
.rpcStatusLock
.acquire()
313 methsplit
= event
.method
.split('.')
315 for piece
in methsplit
:
316 meth
= getattr(meth
,piece
)
317 except AttributeError,msg
:
318 event
.rpcStatus
.result
= 'No Such Method',msg
319 event
.rpcStatus
.status
= rpcEXCEPT
322 res
= apply(meth
,event
.args
)
325 if self
.verbose
: traceback
.print_exc()
326 event
.rpcStatus
.result
= sys
.exc_info()[:2]
327 event
.rpcStatus
.status
= rpcEXCEPT
330 # returning None across the xmlrpc interface is problematic
331 event
.rpcStatus
.result
= []
333 event
.rpcStatus
.result
= res
334 event
.rpcStatus
.status
= rpcDONE
336 event
.rpcStatusLock
.release()
338 # broadcast (using the condition var) that we're done with the event
339 event
.rpcCondVar
.acquire()
340 event
.rpcCondVar
.notify()
341 event
.rpcCondVar
.release()
344 if __name__
== '__main__':
346 if sys
.platform
== 'win32':
351 class rpcFrame(wx
.Frame
,rpcMixin
):
352 """A simple wxFrame with the rpcMixin functionality added
354 def __init__(self
,*args
,**kwargs
):
355 """ rpcHost or rpcPort keyword arguments will be passed along to
359 if kwargs
.has_key('rpcHost'):
360 mixinArgs
['host'] = kwargs
['rpcHost']
361 del kwargs
['rpcHost']
362 if kwargs
.has_key('rpcPort'):
363 mixinArgs
['port'] = kwargs
['rpcPort']
364 del kwargs
['rpcPort']
365 if kwargs
.has_key('rpcPortScan'):
366 mixinArgs
['portScan'] = kwargs
['rpcPortScan']
367 del kwargs
['rpcPortScan']
369 apply(wx
.Frame
.__init
__,(self
,)+args
,kwargs
)
370 apply(rpcMixin
.__init
__,(self
,),mixinArgs
)
372 self
.Bind(wx
.EVT_CHAR
,self
.OnChar
)
374 def TestFunc(self
,args
):
378 def OnChar(self
,event
):
379 key
= event
.GetKeyCode()
383 def OnQuit(self
,event
):
386 def OnClose(self
,event
):
393 self
.frame
= rpcFrame(None, -1, "wxPython RPCDemo", wx
.DefaultPosition
,
394 (300,300), rpcHost
='localhost',rpcPort
=port
)
395 self
.frame
.Show(True)
400 s1
= xmlrpclib
.Server('http://localhost:%d'%(port))
401 s1
.SetTitle('Munged')
407 if len(sys
.argv
)>1 and sys
.argv
[1] == '-q':
409 nT
= threading
.activeCount()
411 activePort
= app
.frame
.rpcPort
412 t
= threading
.Thread(target
=lambda x
=activePort
:testcon(x
),verbose
=0)
416 # give the threads time to shut down
417 if threading
.activeCount() > nT
:
418 print 'waiting for all threads to terminate'
419 while threading
.activeCount() > nT
: