]>
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
: