Jeff Grimmett with some tweaks and changes from Robin git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24889 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			423 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| #  This was modified from rpcMixin.py distributed with wxPython
 | |
| #
 | |
| #----------------------------------------------------------------------
 | |
| # Name:        rpcMixin
 | |
| # Version:     0.2.0
 | |
| # Purpose:     provides xmlrpc server functionality for wxPython
 | |
| #              applications via a mixin class
 | |
| #
 | |
| # Requires:    (1) Python with threading enabled.
 | |
| #              (2) xmlrpclib from PythonWare
 | |
| #                  (http://www.pythonware.com/products/xmlrpc/)
 | |
| #                  the code was developed and tested using version 0.9.8
 | |
| #
 | |
| # Author:      greg Landrum (Landrum@RationalDiscovery.com)
 | |
| #
 | |
| # Copyright:   (c) 2000, 2001 by Greg Landrum and Rational Discovery LLC
 | |
| # Licence:     wxWindows license
 | |
| #----------------------------------------------------------------------
 | |
| # 12/11/2003 - Jeff Grimmett (grimmtooth@softhome.net)
 | |
| #
 | |
| # o 2.5 compatability update.
 | |
| # o xmlrpcserver not available.
 | |
| #
 | |
| 
 | |
| """provides xmlrpc server functionality for wxPython applications via a mixin class
 | |
| 
 | |
| **Some Notes:**
 | |
| 
 | |
|   1)  The xmlrpc server runs in a separate thread from the main GUI
 | |
|       application, communication between the two threads using a custom
 | |
|       event (see the Threads demo in the wxPython docs for more info).
 | |
| 
 | |
|   2)  Neither the server nor the client are particularly smart about
 | |
|       checking method names.  So it's easy to shoot yourself in the foot
 | |
|       by calling improper methods.  It would be pretty easy to add
 | |
|       either a list of allowed methods or a list of forbidden methods.
 | |
| 
 | |
|   3)  Authentication of xmlrpc clients is *not* performed.  I think it
 | |
|       would be pretty easy to do this in a hacky way, but I haven't done
 | |
|       it yet.
 | |
| 
 | |
|   4)  See the bottom of this file for an example of using the class.
 | |
| 
 | |
| **Obligatory disclaimer:**
 | |
|   This is my first crack at both using xmlrpc and multi-threaded
 | |
|   programming, so there could be huge horrible bugs or design
 | |
|   flaws. If you see one, I'd love to hear about them.
 | |
| 
 | |
| """
 | |
| 
 | |
| 
 | |
| """ ChangeLog
 | |
| 23 May 2001:  Version bumped to 0.2.0
 | |
|   Numerous code and design changes
 | |
| 
 | |
| 21 Mar. 2001:  Version bumped to 0.1.4
 | |
|   Updated rpcMixin.OnExternal to support methods with further references
 | |
|    (i.e. now you can do rpcClient.foo.bar() and have it work)
 | |
|   This probably ain't super legal in xmlrpc land, but it works just fine here
 | |
|    and we need it.
 | |
| 
 | |
| 6  Mar. 2001:  Version bumped to 0.1.3
 | |
|   Documentation changes to make this compatible with happydoc
 | |
| 
 | |
| 21 Jan. 2001:  Version bumped to 0.1.2
 | |
|   OnExternal() method in the mixin class now uses getattr() to check if
 | |
|     a desired method is present.  It should have been done this way in
 | |
|     the first place.
 | |
| 14 Dec. 2000:  Version bumped to 0.1.1
 | |
|   rearranged locking code and made other changes so that multiple
 | |
|     servers in one application are possible.
 | |
| 
 | |
| """
 | |
| 
 | |
| import  new
 | |
| import  SocketServer
 | |
| import  sys
 | |
| import  threading
 | |
| import  xmlrpclib
 | |
| import  xmlrpcserver
 | |
| 
 | |
| import  wx
 | |
| 
 | |
| rpcPENDING = 0
 | |
| rpcDONE = 1
 | |
| rpcEXCEPT = 2
 | |
| 
 | |
| class RPCRequest:
 | |
|   """A wrapper to use for handling requests and their responses"""
 | |
|   status = rpcPENDING
 | |
|   result = None
 | |
| 
 | |
| # here's the ID for external events
 | |
| wxEVT_EXTERNAL_EVENT = wx.NewEventType()
 | |
| EVT_EXTERNAL_EVENT = wx.PyEventBinder(wxEVT_EXTERNAL_EVENT, 0)
 | |
| 
 | |
| class ExternalEvent(wx.PyEvent):
 | |
|   """The custom event class used to pass xmlrpc calls from
 | |
|      the server thread into the GUI thread
 | |
| 
 | |
|   """
 | |
|   def __init__(self,method,args):
 | |
|     wx.PyEvent.__init__(self)
 | |
|     self.SetEventType(wxEVT_EXTERNAL_EVENT)
 | |
|     self.method = method
 | |
|     self.args = args
 | |
|     self.rpcStatus = RPCRequest()
 | |
|     self.rpcStatusLock = threading.Lock()
 | |
|     self.rpcCondVar = threading.Condition()
 | |
| 
 | |
|   def Destroy(self):
 | |
|     self.method=None
 | |
|     self.args=None
 | |
|     self.rpcStatus = None
 | |
|     self.rpcStatusLock = None
 | |
|     self.rpcondVar = None
 | |
| 
 | |
| class Handler(xmlrpcserver.RequestHandler):
 | |
|   """The handler class that the xmlrpcserver actually calls
 | |
|      when a request comes in.
 | |
| 
 | |
|   """
 | |
|   def log_message(self,*args):
 | |
|     """ causes the server to stop spewing messages every time a request comes in
 | |
| 
 | |
|     """
 | |
|     pass
 | |
|   def call(self,method,params):
 | |
|     """When an xmlrpc request comes in, this is the method that
 | |
|        gets called.
 | |
| 
 | |
|        **Arguments**
 | |
| 
 | |
|          - method: name of the method to be called
 | |
| 
 | |
|          - params: arguments to that method
 | |
| 
 | |
|     """
 | |
|     if method == '_rpcPing':
 | |
|       # we just acknowledge these without processing them
 | |
|       return 'ack'
 | |
| 
 | |
|     # construct the event
 | |
|     evt = ExternalEvent(method,params)
 | |
| 
 | |
|     # update the status variable
 | |
|     evt.rpcStatusLock.acquire()
 | |
|     evt.rpcStatus.status = rpcPENDING
 | |
|     evt.rpcStatusLock.release()
 | |
| 
 | |
|     evt.rpcCondVar.acquire()
 | |
|     # dispatch the event to the GUI
 | |
|     wx.PostEvent(self._app,evt)
 | |
| 
 | |
|     # wait for the GUI to finish
 | |
|     while evt.rpcStatus.status == rpcPENDING:
 | |
|       evt.rpcCondVar.wait()
 | |
|     evt.rpcCondVar.release()
 | |
|     evt.rpcStatusLock.acquire()
 | |
|     if evt.rpcStatus.status == rpcEXCEPT:
 | |
|       # The GUI threw an exception, release the status lock
 | |
|       #  and re-raise the exception
 | |
|       evt.rpcStatusLock.release()
 | |
|       raise evt.rpcStatus.result[0],evt.rpcStatus.result[1]
 | |
|     else:
 | |
|       # everything went through without problems
 | |
|       s = evt.rpcStatus.result
 | |
| 
 | |
|       evt.rpcStatusLock.release()
 | |
|       evt.Destroy()
 | |
|       self._app = None
 | |
|       return s
 | |
| 
 | |
| # this global Event is used to let the server thread
 | |
| #  know when it should quit
 | |
| stopEvent = threading.Event()
 | |
| stopEvent.clear()
 | |
| 
 | |
| class _ServerThread(threading.Thread):
 | |
|   """ this is the Thread class which actually runs the server
 | |
| 
 | |
|   """
 | |
|   def __init__(self,server,verbose=0):
 | |
|     self._xmlServ = server
 | |
|     threading.Thread.__init__(self,verbose=verbose)
 | |
| 
 | |
|   def stop(self):
 | |
|     stopEvent.set()
 | |
| 
 | |
|   def shouldStop(self):
 | |
|     return stopEvent.isSet()
 | |
| 
 | |
|   def run(self):
 | |
|     while not self.shouldStop():
 | |
|       self._xmlServ.handle_request()
 | |
|     self._xmlServ = None
 | |
| 
 | |
| class rpcMixin:
 | |
|   """A mixin class to provide xmlrpc server functionality to wxPython
 | |
|      frames/windows
 | |
| 
 | |
|      If you want to customize this, probably the best idea is to
 | |
|      override the OnExternal method, which is what's invoked when an
 | |
|      RPC is handled.
 | |
| 
 | |
|   """
 | |
| 
 | |
|   # we'll try a range of ports for the server, this is the size of the
 | |
|   #  range to be scanned
 | |
|   nPortsToTry=20
 | |
|   if sys.platform == 'win32':
 | |
|     defPort = 800
 | |
|   else:
 | |
|     defPort = 8023
 | |
| 
 | |
|   def __init__(self,host='',port=-1,verbose=0,portScan=1):
 | |
|     """Constructor
 | |
| 
 | |
|       **Arguments**
 | |
| 
 | |
|         - host: (optional) the hostname for the server
 | |
| 
 | |
|         - port: (optional) the port the server will use
 | |
| 
 | |
|         - verbose: (optional) if set, the server thread will be launched
 | |
|           in verbose mode
 | |
| 
 | |
|         - portScan: (optional) if set, we'll scan across a number of ports
 | |
|           to find one which is avaiable
 | |
| 
 | |
|     """
 | |
|     if port == -1:
 | |
|       port = self.defPort
 | |
|     self.verbose=verbose
 | |
|     self.Bind(EVT_EXTERNAL_EVENT,self.OnExternal)
 | |
|     if hasattr(self,'OnClose'):
 | |
|       self._origOnClose = self.OnClose
 | |
|       self.Disconnect(-1,-1,wx.EVT_CLOSE_WINDOW)
 | |
|     else:
 | |
|       self._origOnClose = None
 | |
|     self.OnClose = self.RPCOnClose
 | |
|     self.Bind(wx.EVT_CLOSE,self.RPCOnClose)
 | |
| 
 | |
|     tClass = new.classobj('Handler%d'%(port),(Handler,),{})
 | |
|     tClass._app = self
 | |
|     if portScan:
 | |
|       self.rpcPort = -1
 | |
|       for i in xrange(self.nPortsToTry):
 | |
|         try:
 | |
|           xmlServ = SocketServer.TCPServer((host,port+i),tClass)
 | |
|         except:
 | |
|           pass
 | |
|         else:
 | |
|           self.rpcPort = port+i
 | |
|     else:
 | |
|       self.rpcPort = port
 | |
|       try:
 | |
|         xmlServ = SocketServer.TCPServer((host,port),tClass)
 | |
|       except:
 | |
|         self.rpcPort = -1
 | |
| 
 | |
|     if self.rpcPort == -1:
 | |
|       raise 'RPCMixinError','Cannot initialize server'
 | |
|     self.servThread = _ServerThread(xmlServ,verbose=self.verbose)
 | |
|     self.servThread.setName('XML-RPC Server')
 | |
|     self.servThread.start()
 | |
| 
 | |
|   def RPCOnClose(self,event):
 | |
|     """ callback for when the application is closed
 | |
| 
 | |
|        be sure to shutdown the server and the server thread before
 | |
|        leaving
 | |
| 
 | |
|     """
 | |
|     # by setting the global stopEvent we inform the server thread
 | |
|     # that it's time to shut down.
 | |
|     stopEvent.set()
 | |
|     if event is not None:
 | |
|       # if we came in here from a user event (as opposed to an RPC event),
 | |
|       #  then we'll need to kick the server one last time in order
 | |
|       #  to get that thread to terminate.  do so now
 | |
|       s1 = xmlrpclib.Server('http://localhost:%d'%(self.rpcPort))
 | |
|       try:
 | |
|         s1._rpcPing()
 | |
|       except:
 | |
|         pass
 | |
| 
 | |
|     if self._origOnClose is not None:
 | |
|       self._origOnClose(event)
 | |
| 
 | |
|   def RPCQuit(self):
 | |
|     """ shuts down everything, including the rpc server
 | |
| 
 | |
|     """
 | |
|     self.RPCOnClose(None)
 | |
|   def OnExternal(self,event):
 | |
|     """ this is the callback used to handle RPCs
 | |
| 
 | |
|       **Arguments**
 | |
| 
 | |
|         - event: an _ExternalEvent_ sent by the rpc server
 | |
| 
 | |
|       Exceptions are caught and returned in the global _rpcStatus
 | |
|       structure.  This allows the xmlrpc server to report the
 | |
|       exception to the client without mucking up any of the delicate
 | |
|       thread stuff.
 | |
| 
 | |
|     """
 | |
|     event.rpcStatusLock.acquire()
 | |
|     doQuit = 0
 | |
|     try:
 | |
|       methsplit = event.method.split('.')
 | |
|       meth = self
 | |
|       for piece in methsplit:
 | |
|         meth = getattr(meth,piece)
 | |
|     except AttributeError,msg:
 | |
|       event.rpcStatus.result = 'No Such Method',msg
 | |
|       event.rpcStatus.status = rpcEXCEPT
 | |
|     else:
 | |
|       try:
 | |
|         res = apply(meth,event.args)
 | |
|       except:
 | |
|         import traceback
 | |
|         if self.verbose: traceback.print_exc()
 | |
|         event.rpcStatus.result = sys.exc_info()[:2]
 | |
|         event.rpcStatus.status = rpcEXCEPT
 | |
|       else:
 | |
|         if res is None:
 | |
|           # returning None across the xmlrpc interface is problematic
 | |
|           event.rpcStatus.result = []
 | |
|         else:
 | |
|           event.rpcStatus.result = res
 | |
|         event.rpcStatus.status = rpcDONE
 | |
| 
 | |
|     event.rpcStatusLock.release()
 | |
| 
 | |
|     # broadcast (using the condition var) that we're done with the event
 | |
|     event.rpcCondVar.acquire()
 | |
|     event.rpcCondVar.notify()
 | |
|     event.rpcCondVar.release()
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|   import time
 | |
|   if sys.platform == 'win32':
 | |
|     port = 800
 | |
|   else:
 | |
|     port = 8023
 | |
| 
 | |
|   class rpcFrame(wx.Frame,rpcMixin):
 | |
|     """A simple wxFrame with the rpcMixin functionality added
 | |
|     """
 | |
|     def __init__(self,*args,**kwargs):
 | |
|       """ rpcHost or rpcPort keyword arguments will be passed along to
 | |
|           the xmlrpc server.
 | |
|       """
 | |
|       mixinArgs = {}
 | |
|       if kwargs.has_key('rpcHost'):
 | |
|         mixinArgs['host'] = kwargs['rpcHost']
 | |
|         del kwargs['rpcHost']
 | |
|       if kwargs.has_key('rpcPort'):
 | |
|         mixinArgs['port'] = kwargs['rpcPort']
 | |
|         del kwargs['rpcPort']
 | |
|       if kwargs.has_key('rpcPortScan'):
 | |
|         mixinArgs['portScan'] = kwargs['rpcPortScan']
 | |
|         del kwargs['rpcPortScan']
 | |
| 
 | |
|       apply(wx.Frame.__init__,(self,)+args,kwargs)
 | |
|       apply(rpcMixin.__init__,(self,),mixinArgs)
 | |
| 
 | |
|       self.Bind(wx.EVT_CHAR,self.OnChar)
 | |
| 
 | |
|     def TestFunc(self,args):
 | |
|       """a demo method"""
 | |
|       return args
 | |
| 
 | |
|     def OnChar(self,event):
 | |
|       key = event.GetKeyCode()
 | |
|       if key == ord('q'):
 | |
|         self.OnQuit(event)
 | |
| 
 | |
|     def OnQuit(self,event):
 | |
|       self.OnClose(event)
 | |
| 
 | |
|     def OnClose(self,event):
 | |
|       self.Destroy()
 | |
| 
 | |
| 
 | |
| 
 | |
|   class MyApp(wx.App):
 | |
|     def OnInit(self):
 | |
|       self.frame = rpcFrame(None, -1, "wxPython RPCDemo", wx.DefaultPosition,
 | |
|                             (300,300), rpcHost='localhost',rpcPort=port)
 | |
|       self.frame.Show(True)
 | |
|       return True
 | |
| 
 | |
| 
 | |
|   def testcon(port):
 | |
|     s1 = xmlrpclib.Server('http://localhost:%d'%(port))
 | |
|     s1.SetTitle('Munged')
 | |
|     s1._rpcPing()
 | |
|     if doQuit:
 | |
|       s1.RPCQuit()
 | |
| 
 | |
|   doQuit = 1
 | |
|   if len(sys.argv)>1 and sys.argv[1] == '-q':
 | |
|     doQuit = 0
 | |
|   nT = threading.activeCount()
 | |
|   app = MyApp(0)
 | |
|   activePort = app.frame.rpcPort
 | |
|   t = threading.Thread(target=lambda x=activePort:testcon(x),verbose=0)
 | |
|   t.start()
 | |
| 
 | |
|   app.MainLoop()
 | |
|   # give the threads time to shut down
 | |
|   if threading.activeCount() > nT:
 | |
|     print 'waiting for all threads to terminate'
 | |
|     while threading.activeCount() > nT:
 | |
|       time.sleep(0.5)
 | |
| 
 | |
| 
 |