]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/rpcMixin.py
26a640a3d089eb1d45119475472ed6bf9f738bf3
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
84 """A wrapper to use for handling requests and their responses"""
88 # here's the ID for external events
89 wxEVT_EXTERNAL_EVENT
= 25015
90 class ExternalEvent(wxPyEvent
):
91 """The custom event class used to pass xmlrpc calls from
92 the server thread into the GUI thread
95 def __init__(self
,method
,args
):
96 wxPyEvent
.__init
__(self
)
97 self
.SetEventType(wxEVT_EXTERNAL_EVENT
)
100 self
.rpcStatus
= RPCRequest()
101 self
.rpcStatusLock
= threading
.Lock()
102 self
.rpcCondVar
= threading
.Condition()
107 self
.rpcStatus
= None
108 self
.rpcStatusLock
= None
109 self
.rpcondVar
= None
111 def EVT_EXTERNAL_EVENT(win
,func
):
112 win
.Connect(-1,-1,wxEVT_EXTERNAL_EVENT
,func
)
114 class Handler(xmlrpcserver
.RequestHandler
):
115 """The handler class that the xmlrpcserver actually calls
116 when a request comes in.
119 def log_message(self
,*args
):
120 """ causes the server to stop spewing messages every time a request comes in
124 def call(self
,method
,params
):
125 """When an xmlrpc request comes in, this is the method that
130 - method: name of the method to be called
132 - params: arguments to that method
135 if method
== '_rpcPing':
136 # we just acknowledge these without processing them
139 # construct the event
140 evt
= ExternalEvent(method
,params
)
142 # update the status variable
143 evt
.rpcStatusLock
.acquire()
144 evt
.rpcStatus
.status
= rpcPENDING
145 evt
.rpcStatusLock
.release()
147 evt
.rpcCondVar
.acquire()
148 # dispatch the event to the GUI
149 wxPostEvent(self
._app
,evt
)
151 # wait for the GUI to finish
152 while evt
.rpcStatus
.status
== rpcPENDING
:
153 evt
.rpcCondVar
.wait()
154 evt
.rpcCondVar
.release()
155 evt
.rpcStatusLock
.acquire()
156 if evt
.rpcStatus
.status
== rpcEXCEPT
:
157 # The GUI threw an exception, release the status lock
158 # and re-raise the exception
159 evt
.rpcStatusLock
.release()
160 raise evt
.rpcStatus
.result
[0],evt
.rpcStatus
.result
[1]
162 # everything went through without problems
163 s
= evt
.rpcStatus
.result
165 evt
.rpcStatusLock
.release()
170 # this global Event is used to let the server thread
171 # know when it should quit
172 stopEvent
= threading
.Event()
175 class _ServerThread(threading
.Thread
):
176 """ this is the Thread class which actually runs the server
179 def __init__(self
,server
,verbose
=0):
180 self
._xmlServ
= server
181 threading
.Thread
.__init
__(self
,verbose
=verbose
)
186 def shouldStop(self
):
187 return stopEvent
.isSet()
190 while not self
.shouldStop():
191 self
._xmlServ
.handle_request()
195 """A mixin class to provide xmlrpc server functionality to wxPython
198 If you want to customize this, probably the best idea is to
199 override the OnExternal method, which is what's invoked when an
204 # we'll try a range of ports for the server, this is the size of the
205 # range to be scanned
207 if sys
.platform
== 'win32':
212 def __init__(self
,host
='',port
=-1,verbose
=0,portScan
=1):
217 - host: (optional) the hostname for the server
219 - port: (optional) the port the server will use
221 - verbose: (optional) if set, the server thread will be launched
224 - portScan: (optional) if set, we'll scan across a number of ports
225 to find one which is avaiable
231 EVT_EXTERNAL_EVENT(self
,self
.OnExternal
)
232 if hasattr(self
,'OnClose'):
233 self
._origOnClose
= self
.OnClose
234 self
.Disconnect(-1,-1,wxEVT_CLOSE_WINDOW
)
236 self
._origOnClose
= None
237 self
.OnClose
= self
.RPCOnClose
238 EVT_CLOSE(self
,self
.RPCOnClose
)
240 tClass
= new
.classobj('Handler%d'%(port),(Handler
,),{})
244 for i
in xrange(self
.nPortsToTry
):
246 xmlServ
= SocketServer
.TCPServer((host
,port
+i
),tClass
)
250 self
.rpcPort
= port
+i
254 xmlServ
= SocketServer
.TCPServer((host
,port
),tClass
)
258 if self
.rpcPort
== -1:
259 raise 'RPCMixinError','Cannot initialize server'
260 self
.servThread
= _ServerThread(xmlServ
,verbose
=self
.verbose
)
261 self
.servThread
.setName('XML-RPC Server')
262 self
.servThread
.start()
264 def RPCOnClose(self
,event
):
265 """ callback for when the application is closed
267 be sure to shutdown the server and the server thread before
271 # by setting the global stopEvent we inform the server thread
272 # that it's time to shut down.
274 if event
is not None:
275 # if we came in here from a user event (as opposed to an RPC event),
276 # then we'll need to kick the server one last time in order
277 # to get that thread to terminate. do so now
278 s1
= xmlrpclib
.Server('http://localhost:%d'%(self
.rpcPort
))
284 if self
._origOnClose
is not None:
285 self
._origOnClose
(event
)
288 """ shuts down everything, including the rpc server
291 self
.RPCOnClose(None)
292 def OnExternal(self
,event
):
293 """ this is the callback used to handle RPCs
297 - event: an _ExternalEvent_ sent by the rpc server
299 Exceptions are caught and returned in the global _rpcStatus
300 structure. This allows the xmlrpc server to report the
301 exception to the client without mucking up any of the delicate
305 event
.rpcStatusLock
.acquire()
308 methsplit
= string
.split(event
.method
,'.')
310 for piece
in methsplit
:
311 meth
= getattr(meth
,piece
)
312 except AttributeError,msg
:
313 event
.rpcStatus
.result
= 'No Such Method',msg
314 event
.rpcStatus
.status
= rpcEXCEPT
317 res
= apply(meth
,event
.args
)
320 if self
.verbose
: traceback
.print_exc()
321 event
.rpcStatus
.result
= sys
.exc_info()[:2]
322 event
.rpcStatus
.status
= rpcEXCEPT
325 # returning None across the xmlrpc interface is problematic
326 event
.rpcStatus
.result
= []
328 event
.rpcStatus
.result
= res
329 event
.rpcStatus
.status
= rpcDONE
331 event
.rpcStatusLock
.release()
333 # broadcast (using the condition var) that we're done with the event
334 event
.rpcCondVar
.acquire()
335 event
.rpcCondVar
.notify()
336 event
.rpcCondVar
.release()
339 if __name__
== '__main__':
341 if sys
.platform
== 'win32':
346 class rpcFrame(wxFrame
,rpcMixin
):
347 """A simple wxFrame with the rpcMixin functionality added
349 def __init__(self
,*args
,**kwargs
):
350 """ rpcHost or rpcPort keyword arguments will be passed along to
354 if kwargs
.has_key('rpcHost'):
355 mixinArgs
['host'] = kwargs
['rpcHost']
356 del kwargs
['rpcHost']
357 if kwargs
.has_key('rpcPort'):
358 mixinArgs
['port'] = kwargs
['rpcPort']
359 del kwargs
['rpcPort']
360 if kwargs
.has_key('rpcPortScan'):
361 mixinArgs
['portScan'] = kwargs
['rpcPortScan']
362 del kwargs
['rpcPortScan']
364 apply(wxFrame
.__init
__,(self
,)+args
,kwargs
)
365 apply(rpcMixin
.__init
__,(self
,),mixinArgs
)
367 EVT_CHAR(self
,self
.OnChar
)
369 def TestFunc(self
,args
):
373 def OnChar(self
,event
):
374 key
= event
.GetKeyCode()
378 def OnQuit(self
,event
):
381 def OnClose(self
,event
):
388 self
.frame
= rpcFrame(NULL
, -1, "wxPython RPCDemo", wxDefaultPosition
,
390 rpcHost
='localhost',rpcPort
=port
)
391 self
.frame
.Show(TRUE
)
396 s1
= xmlrpclib
.Server('http://localhost:%d'%(port))
397 s1
.SetTitle('Munged')
403 if len(sys
.argv
)>1 and sys
.argv
[1] == '-q':
405 nT
= threading
.activeCount()
407 activePort
= app
.frame
.rpcPort
408 t
= threading
.Thread(target
=lambda x
=activePort
:testcon(x
),verbose
=0)
412 # give the threads time to shut down
413 if threading
.activeCount() > nT
:
414 print 'waiting for all threads to terminate'
415 while threading
.activeCount() > nT
: