Merged the wxPy_newswig branch into the HEAD branch (main trunk)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24541 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -1,8 +1,417 @@
|
||||
#
|
||||
# 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
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
"""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.
|
||||
|
||||
"""
|
||||
|
||||
from wxPython.wx import *
|
||||
import xmlrpcserver,xmlrpclib
|
||||
import threading
|
||||
import SocketServer
|
||||
import new
|
||||
import sys
|
||||
|
||||
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 = 25015
|
||||
class ExternalEvent(wxPyEvent):
|
||||
"""The custom event class used to pass xmlrpc calls from
|
||||
the server thread into the GUI thread
|
||||
|
||||
"""
|
||||
def __init__(self,method,args):
|
||||
wxPyEvent.__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
|
||||
|
||||
def EVT_EXTERNAL_EVENT(win,func):
|
||||
win.Connect(-1,-1,wxEVT_EXTERNAL_EVENT,func)
|
||||
|
||||
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
|
||||
wxPostEvent(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
|
||||
EVT_EXTERNAL_EVENT(self,self.OnExternal)
|
||||
if hasattr(self,'OnClose'):
|
||||
self._origOnClose = self.OnClose
|
||||
self.Disconnect(-1,-1,wxEVT_CLOSE_WINDOW)
|
||||
else:
|
||||
self._origOnClose = None
|
||||
self.OnClose = self.RPCOnClose
|
||||
EVT_CLOSE(self,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(wxFrame,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(wxFrame.__init__,(self,)+args,kwargs)
|
||||
apply(rpcMixin.__init__,(self,),mixinArgs)
|
||||
|
||||
EVT_CHAR(self,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(wxApp):
|
||||
def OnInit(self):
|
||||
self.frame = rpcFrame(NULL, -1, "wxPython RPCDemo", wxDefaultPosition,
|
||||
wxSize(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)
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.lib import rpcMixin
|
||||
_rename(globals(), rpcMixin.__dict__, modulename='lib.rpcMixin')
|
||||
del rpcMixin
|
||||
del _rename
|
||||
|
Reference in New Issue
Block a user