]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/rpcMixin.py
44736e6bd8b59be715fa1d96e4dcc2c97e6a64e6
[wxWidgets.git] / wxPython / wxPython / lib / rpcMixin.py
1 #----------------------------------------------------------------------
2 # Name: rpcMixin
3 # Version: 0.1
4 # Purpose: provides xmlrpc server functionality for wxPython
5 # applications via a mixin class
6 #
7 # Requires: (1) Python with threading enabled.
8 # (2) xmlrpclib from PythonWare
9 # (http://www.pythonware.com/products/xmlrpc/)
10 # the code was developed and tested using version 0.9.8
11 #
12 # Author: greg Landrum (Landrum@RationalDiscovery.com)
13 #
14 # Copyright: (c) 2000 by Greg Landrum and Rational Discovery LLC
15 # Licence: wxWindows license
16 #----------------------------------------------------------------------
17
18 """
19 Some Notes:
20
21 1) The xmlrpc server runs in a separate thread from the main GUI
22 application, communication between the two threads using a custom
23 event (see the Threads demo in the wxPython docs for more info).
24
25 2) Neither the server nor the client are particularly smart about
26 checking method names. So it's easy to shoot yourself in the foot
27 by calling improper methods. It would be pretty easy to add
28 either a list of allowed methods or a list of forbidden methods.
29
30 3) Authentication of xmlrpc clients is *not* performed. I think it
31 would be pretty easy to do this in a hacky way, but I haven't done
32 it yet.
33
34 4) The default port number is 800, it's a windows thing... at least
35 it seems like a windows thing to me. Since I'm not being smart
36 about port numbers, you can probably hork yourself arbitrarily by
37 firing up more than one xmlrpc-active frame at the same time, but
38 I haven't tried that.
39
40 5) See the bottom of this file for an example of using the class.
41
42 Obligatory disclaimer:
43 This is my first crack at both using xmlrpc and multi-threaded
44 programming, so there could be huge horrible bugs or design
45 flaws. If you see one, I'd love to hear about them.
46
47 """
48
49 from wxPython.wx import *
50 import xmlrpcserver
51 import Threading
52 import SocketServer
53
54 rpcPENDING = 0
55 rpcDONE = 1
56 rpcEXCEPT = 2
57 class RPCRequest:
58 """A wrapper to use for handling requests and their responses"""
59 status = rpcPENDING
60 result = None
61
62 # here's the ID for external events
63 wxEVT_EXTERNAL_EVENT = 25015
64 class ExternalEvent(wxPyEvent):
65 """The custom event class used to pass xmlrpc calls from
66 the server thread into the GUI thread
67 """
68 def __init__(self,method,args):
69 wxPyEvent.__init__(self)
70 self.SetEventType(wxEVT_EXTERNAL_EVENT)
71 self.method = method
72 self.args = args
73 self.rpcStatus = RPCRequest()
74 self.rpcStatusLock = Threading.Lock()
75 self.rpcCondVar = Threading.Condition()
76
77 def EVT_EXTERNAL_EVENT(win,func):
78 win.Connect(-1,-1,wxEVT_EXTERNAL_EVENT,func)
79
80 class Handler(xmlrpcserver.RequestHandler):
81 """The handler class that the xmlrpcserver actually calls
82 when a request comes in.
83 """
84 def call(self,method,params):
85 """When an xmlrpc request comes in, this is the method that
86 gets called.
87 """
88 # construct the event
89 evt = ExternalEvent(method,params)
90
91 # update the status variable
92 evt.rpcStatusLock.acquire()
93 evt.rpcStatus.status = rpcPENDING
94 evt.rpcStatusLock.release()
95
96 # acquire the condition lock
97 evt.rpcCondVar.acquire()
98 # dispatch the event to the GUI
99 wxPostEvent(self._app,evt)
100 # wait for the GUI to finish
101 while evt.rpcStatus.status == rpcPENDING:
102 evt.rpcCondVar.wait()
103 evt.rpcCondVar.release()
104 evt.rpcStatusLock.acquire()
105 if evt.rpcStatus.status == rpcEXCEPT:
106 # The GUI threw an exception, release the status lock
107 # and re-raise the exception
108 evt.rpcStatusLock.release()
109 raise evt.rpcStatus.result[0],evt.rpcStatus.result[1]
110 else:
111 # everything went through without problems
112 s = evt.rpcStatus.result
113 evt.rpcStatusLock.release()
114 return s
115
116 class rpcMixin:
117 """A mixin class to provide xmlrpc server functionality to wxPython
118 frames/windows
119
120 If you want to customize this, probably the best idea is to
121 override the OnExternal method, which is what's invoked when an
122 RPC is handled.
123
124 """
125 def __init__(self,host='',port=800):
126 """
127 Arguments:
128 host: (optional) the hostname for the server
129 port: (optional) the port the server will use
130 """
131 EVT_EXTERNAL_EVENT(self,self.OnExternal)
132 if hasattr(self,'OnClose'):
133 self._origOnClose = self.OnClose
134 else:
135 self._origOnClose = None
136 EVT_CLOSE(self,self.OnClose)
137
138 exec('class Handler%d(Handler): pass'%(port))
139 exec('tClass= Handler%d'%(port))
140 tClass._app = self
141 self._xmlServ = SocketServer.TCPServer((host,port),tClass)
142 self.servThread = Threading.Thread(target=self._xmlServ.serve_forever)
143 self.servThread.setDaemon(1)
144 self.servThread.start()
145
146 def OnClose(self,event):
147 """ be sure to shutdown the server and the server thread before
148 leaving
149 """
150 self._xmlServ = None
151 self.servThread = None
152 if self._origOnClose is not None:
153 self._origOnClose(event)
154
155 def OnExternal(self,event):
156 """ this is the callback used to handle RPCs
157
158 Exceptions are caught and returned in the global _rpcStatus
159 structure. This allows the xmlrpc server to report the
160 exception to the client without mucking up any of the delicate
161 thread stuff.
162 """
163 event.rpcStatusLock.acquire()
164 try:
165 res = eval('apply(self.%s,event.args)'%event.method)
166 except:
167 import sys,traceback
168 traceback.print_exc()
169 event.rpcStatus.result = sys.exc_info()[:2]
170 event.rpcStatus.status = rpcEXCEPT
171 else:
172 if res is None:
173 event.rpcStatus.result = []
174 else:
175 event.rpcStatus.result = res
176 event.rpcStatus.status = rpcDONE
177 event.rpcStatusLock.release()
178 event.rpcCondVar.acquire()
179 event.rpcCondVar.notify()
180 event.rpcCondVar.release()
181
182 if __name__ == '__main__':
183 import sys
184 port = 800
185 if len(sys.argv)>1:
186 port = int(sys.argv[1])
187
188 class rpcFrame(wxFrame,rpcMixin):
189 """A simple wxFrame with the rpcMixin functionality added
190 """
191 def __init__(self,*args,**kwargs):
192 """ rpcHost or rpcPort keyword arguments will be passed along to
193 the xmlrpc server.
194 """
195 mixinArgs = {}
196 if kwargs.has_key('rpcHost'):
197 mixinArgs['host'] = kwargs['rpcHost']
198 del kwargs['rpcHost']
199 if kwargs.has_key('rpcPort'):
200 mixinArgs['port'] = kwargs['rpcPort']
201 del kwargs['rpcPort']
202
203 apply(wxFrame.__init__,(self,)+args,kwargs)
204 apply(rpcMixin.__init__,(self,),mixinArgs)
205
206 EVT_CHAR(self,self.OnChar)
207
208 def TestFunc(self,args):
209 """a demo method"""
210 return args
211
212 def OnChar(self,event):
213 key = event.GetKeyCode()
214 if key == ord('q'):
215 self.OnQuit(event)
216
217 def OnQuit(self,event):
218 self.OnClose(event)
219
220 def OnClose(self,event):
221 self.Destroy()
222
223 class MyApp(wxApp):
224 def OnInit(self):
225 frame = rpcFrame(NULL, -1, "wxPython RPCDemo", wxDefaultPosition, wxSize(300,300),rpcHost='localhost',rpcPort=port)
226 frame.Show(TRUE)
227 import time
228
229 #self.SetTopWindow(frame)
230 frame2 = rpcFrame(NULL, -1, "wxPython RPCDemo2", wxDefaultPosition, wxSize(300,300),rpcHost='localhost',rpcPort=port+1)
231 frame2.Show(TRUE)
232
233 return TRUE
234 app = MyApp(0)
235 app.MainLoop()
236