]>
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 #----------------------------------------------------------------------
21 """provides xmlrpc server functionality for wxPython applications via a mixin class
25 1) The xmlrpc server runs in a separate thread from the main GUI
26 application, communication between the two threads using a custom
27 event (see the Threads demo in the wxPython docs for more info).
29 2) Neither the server nor the client are particularly smart about
30 checking method names. So it's easy to shoot yourself in the foot
31 by calling improper methods. It would be pretty easy to add
32 either a list of allowed methods or a list of forbidden methods.
34 3) Authentication of xmlrpc clients is *not* performed. I think it
35 would be pretty easy to do this in a hacky way, but I haven't done
38 4) See the bottom of this file for an example of using the class.
40 **Obligatory disclaimer:**
41 This is my first crack at both using xmlrpc and multi-threaded
42 programming, so there could be huge horrible bugs or design
43 flaws. If you see one, I'd love to hear about them.
49 23 May 2001: Version bumped to 0.2.0
50 Numerous code and design changes
52 21 Mar. 2001: Version bumped to 0.1.4
53 Updated rpcMixin.OnExternal to support methods with further references
54 (i.e. now you can do rpcClient.foo.bar() and have it work)
55 This probably ain't super legal in xmlrpc land, but it works just fine here
58 6 Mar. 2001: Version bumped to 0.1.3
59 Documentation changes to make this compatible with happydoc
61 21 Jan. 2001: Version bumped to 0.1.2
62 OnExternal() method in the mixin class now uses getattr() to check if
63 a desired method is present. It should have been done this way in
65 14 Dec. 2000: Version bumped to 0.1.1
66 rearranged locking code and made other changes so that multiple
67 servers in one application are possible.
71 from wxPython
.wx
import *
72 import xmlrpcserver
,xmlrpclib
83 """A wrapper to use for handling requests and their responses"""
87 # here's the ID for external events
88 wxEVT_EXTERNAL_EVENT
= 25015
89 class ExternalEvent(wxPyEvent
):
90 """The custom event class used to pass xmlrpc calls from
91 the server thread into the GUI thread
94 def __init__(self
,method
,args
):
95 wxPyEvent
.__init
__(self
)
96 self
.SetEventType(wxEVT_EXTERNAL_EVENT
)
99 self
.rpcStatus
= RPCRequest()
100 self
.rpcStatusLock
= threading
.Lock()
101 self
.rpcCondVar
= threading
.Condition()
106 self
.rpcStatus
= None
107 self
.rpcStatusLock
= None
108 self
.rpcondVar
= None
110 def EVT_EXTERNAL_EVENT(win
,func
):
111 win
.Connect(-1,-1,wxEVT_EXTERNAL_EVENT
,func
)
113 class Handler(xmlrpcserver
.RequestHandler
):
114 """The handler class that the xmlrpcserver actually calls
115 when a request comes in.
118 def log_message(self
,*args
):
119 """ causes the server to stop spewing messages every time a request comes in
123 def call(self
,method
,params
):
124 """When an xmlrpc request comes in, this is the method that
129 - method: name of the method to be called
131 - params: arguments to that method
134 if method
== '_rpcPing':
135 # we just acknowledge these without processing them
138 # construct the event
139 evt
= ExternalEvent(method
,params
)
141 # update the status variable
142 evt
.rpcStatusLock
.acquire()
143 evt
.rpcStatus
.status
= rpcPENDING
144 evt
.rpcStatusLock
.release()
146 evt
.rpcCondVar
.acquire()
147 # dispatch the event to the GUI
148 wxPostEvent(self
._app
,evt
)
150 # wait for the GUI to finish
151 while evt
.rpcStatus
.status
== rpcPENDING
:
152 evt
.rpcCondVar
.wait()
153 evt
.rpcCondVar
.release()
154 evt
.rpcStatusLock
.acquire()
155 if evt
.rpcStatus
.status
== rpcEXCEPT
:
156 # The GUI threw an exception, release the status lock
157 # and re-raise the exception
158 evt
.rpcStatusLock
.release()
159 raise evt
.rpcStatus
.result
[0],evt
.rpcStatus
.result
[1]
161 # everything went through without problems
162 s
= evt
.rpcStatus
.result
164 evt
.rpcStatusLock
.release()
169 # this global Event is used to let the server thread
170 # know when it should quit
171 stopEvent
= threading
.Event()
174 class _ServerThread(threading
.Thread
):
175 """ this is the Thread class which actually runs the server
178 def __init__(self
,server
,verbose
=0):
179 self
._xmlServ
= server
180 threading
.Thread
.__init
__(self
,verbose
=verbose
)
185 def shouldStop(self
):
186 return stopEvent
.isSet()
189 while not self
.shouldStop():
190 self
._xmlServ
.handle_request()
194 """A mixin class to provide xmlrpc server functionality to wxPython
197 If you want to customize this, probably the best idea is to
198 override the OnExternal method, which is what's invoked when an
203 # we'll try a range of ports for the server, this is the size of the
204 # range to be scanned
206 if sys
.platform
== 'win32':
211 def __init__(self
,host
='',port
=-1,verbose
=0,portScan
=1):
216 - host: (optional) the hostname for the server
218 - port: (optional) the port the server will use
220 - verbose: (optional) if set, the server thread will be launched
223 - portScan: (optional) if set, we'll scan across a number of ports
224 to find one which is avaiable
230 EVT_EXTERNAL_EVENT(self
,self
.OnExternal
)
231 if hasattr(self
,'OnClose'):
232 self
._origOnClose
= self
.OnClose
233 self
.Disconnect(-1,-1,wxEVT_CLOSE_WINDOW
)
235 self
._origOnClose
= None
236 self
.OnClose
= self
.RPCOnClose
237 EVT_CLOSE(self
,self
.RPCOnClose
)
239 tClass
= new
.classobj('Handler%d'%(port),(Handler
,),{})
243 for i
in xrange(self
.nPortsToTry
):
245 xmlServ
= SocketServer
.TCPServer((host
,port
+i
),tClass
)
249 self
.rpcPort
= port
+i
253 xmlServ
= SocketServer
.TCPServer((host
,port
),tClass
)
257 if self
.rpcPort
== -1:
258 raise 'RPCMixinError','Cannot initialize server'
259 self
.servThread
= _ServerThread(xmlServ
,verbose
=self
.verbose
)
260 self
.servThread
.setName('XML-RPC Server')
261 self
.servThread
.start()
263 def RPCOnClose(self
,event
):
264 """ callback for when the application is closed
266 be sure to shutdown the server and the server thread before
270 # by setting the global stopEvent we inform the server thread
271 # that it's time to shut down.
273 if event
is not None:
274 # if we came in here from a user event (as opposed to an RPC event),
275 # then we'll need to kick the server one last time in order
276 # to get that thread to terminate. do so now
277 s1
= xmlrpclib
.Server('http://localhost:%d'%(self
.rpcPort
))
283 if self
._origOnClose
is not None:
284 self
._origOnClose
(event
)
287 """ shuts down everything, including the rpc server
290 self
.RPCOnClose(None)
291 def OnExternal(self
,event
):
292 """ this is the callback used to handle RPCs
296 - event: an _ExternalEvent_ sent by the rpc server
298 Exceptions are caught and returned in the global _rpcStatus
299 structure. This allows the xmlrpc server to report the
300 exception to the client without mucking up any of the delicate
304 event
.rpcStatusLock
.acquire()
307 methsplit
= event
.method
.split('.')
309 for piece
in methsplit
:
310 meth
= getattr(meth
,piece
)
311 except AttributeError,msg
:
312 event
.rpcStatus
.result
= 'No Such Method',msg
313 event
.rpcStatus
.status
= rpcEXCEPT
316 res
= apply(meth
,event
.args
)
319 if self
.verbose
: traceback
.print_exc()
320 event
.rpcStatus
.result
= sys
.exc_info()[:2]
321 event
.rpcStatus
.status
= rpcEXCEPT
324 # returning None across the xmlrpc interface is problematic
325 event
.rpcStatus
.result
= []
327 event
.rpcStatus
.result
= res
328 event
.rpcStatus
.status
= rpcDONE
330 event
.rpcStatusLock
.release()
332 # broadcast (using the condition var) that we're done with the event
333 event
.rpcCondVar
.acquire()
334 event
.rpcCondVar
.notify()
335 event
.rpcCondVar
.release()
338 if __name__
== '__main__':
340 if sys
.platform
== 'win32':
345 class rpcFrame(wxFrame
,rpcMixin
):
346 """A simple wxFrame with the rpcMixin functionality added
348 def __init__(self
,*args
,**kwargs
):
349 """ rpcHost or rpcPort keyword arguments will be passed along to
353 if kwargs
.has_key('rpcHost'):
354 mixinArgs
['host'] = kwargs
['rpcHost']
355 del kwargs
['rpcHost']
356 if kwargs
.has_key('rpcPort'):
357 mixinArgs
['port'] = kwargs
['rpcPort']
358 del kwargs
['rpcPort']
359 if kwargs
.has_key('rpcPortScan'):
360 mixinArgs
['portScan'] = kwargs
['rpcPortScan']
361 del kwargs
['rpcPortScan']
363 apply(wxFrame
.__init
__,(self
,)+args
,kwargs
)
364 apply(rpcMixin
.__init
__,(self
,),mixinArgs
)
366 EVT_CHAR(self
,self
.OnChar
)
368 def TestFunc(self
,args
):
372 def OnChar(self
,event
):
373 key
= event
.GetKeyCode()
377 def OnQuit(self
,event
):
380 def OnClose(self
,event
):
387 self
.frame
= rpcFrame(NULL
, -1, "wxPython RPCDemo", wxDefaultPosition
,
389 rpcHost
='localhost',rpcPort
=port
)
390 self
.frame
.Show(True)
395 s1
= xmlrpclib
.Server('http://localhost:%d'%(port))
396 s1
.SetTitle('Munged')
402 if len(sys
.argv
)>1 and sys
.argv
[1] == '-q':
404 nT
= threading
.activeCount()
406 activePort
= app
.frame
.rpcPort
407 t
= threading
.Thread(target
=lambda x
=activePort
:testcon(x
),verbose
=0)
411 # give the threads time to shut down
412 if threading
.activeCount() > nT
:
413 print 'waiting for all threads to terminate'
414 while threading
.activeCount() > nT
: