Update of /cvsroot/webware/Webware/WebKit
In directory usw-pr-cvs1:/tmp/cvs-serv24717/WebKit
Modified Files:
Application.py HTTPRequest.py XMLRPCServlet.py
Added Files:
PickleRPCServlet.py RPCServlet.py
Log Message:
created PickleRPCServlet (and base class RPCServlet)
--- NEW FILE: PickleRPCServlet.py ---
from RPCServlet import RPCServlet
import sys, traceback, types
from pickle import load, dumps
from time import time
from MiscUtils.PickleRPC import RequestError
class PickleRPCServlet(RPCServlet):
"""
PickleRPCServlet is a base class for Dict-RPC servlets.
The "Pickle" refers to Python's pickle module. This class is similar to
XMLRPCServlet. By using Python pickles you get their
convenience (assuming the client is Pythonic), but lose
language independence. Some of us don't mind that last one. ;-)
Conveniences over XML-RPC include the use of all of the following:
* Any pickle-able Python type (mx.DateTime for example)
* Python instances (aka objects)
* None
* Longs that are outside the 32-bit int boundaries
* Keyword arguments
Pickles should also be faster than XML.
To make your own PickleRPCServlet, create a subclass and implement a
method which is then named in exposedMethods():
from WebKit.PickleRPCServlet import PickleRPCServlet
class Math(PickleRPCServlet):
def multiply(self, x, y):
return x * y
def exposedMethods(self):
return ['multiply']
To make a PickleRPC call from another Python program, do this:
from MiscUtils.PickleRPC import Server
server = Server('http://localhost/WebKit.cgi/Context/Math')
print server.multiply(3, 4) # 12
print server.multiply('-', 10) # ----------
If a request error is raised by the server, then
MiscUtils.PickleRPC.RequestError is raised. If an unhandled
exception is raised by the server, or the server response is
malformed, then MiscUtils.PickleRPC.ResponseError (or one of
it's subclasses) is raised.
Tip: If you want callers of the RPC servlets to be able to
introspect what methods are available, then include
'exposedMethods' in exposedMethods().
If you wanted the actual response dictionary for some reason:
print server._request('multiply', 3, 4)
# { 'value': 12, 'timeReceived': ... }
In which case, an exception is not purposefully raised if the
dictionary contains one. Instead, examine the dictionary.
For the dictionary formats and more information see the docs
for MiscUtils.PickleRPC.
TO DO
* Geoff T mentioned that security concerns were mentioned when
pickling was discussed before. What are they?
"""
def respondToPost(self, trans):
transReponse = trans.response()
try:
data = trans.request().dictInput()
response = {
'timeReceived': trans.request().time(),
}
try:
try:
req = load(data)
except:
raise RequestError, 'Cannot unpickle dict-rpc request.'
if not isinstance(req, types.DictType):
raise RequestError, 'Expecting a dictionary for dict-rpc requests, but got %s instead.' % type(dict)
if req.get('version', 1)!=1:
raise RequestError, 'Cannot handle version %s requests.' % req['version']
if req.get('action', 'call')!='call':
raise RequestError, 'Cannot handle the request action, %r.' % req['action']
try:
methodName = req['methodName']
except KeyError:
raise RequestError, 'Missing method in request'
args = req.get('args', ())
if methodName=='__methods__.__getitem__':
# support PythonWin autoname completion
response = self.exposedMethods()[args[0]]
else:
response['value'] = self.call(methodName, *args, **req.get('keywords', {}))
except RequestError, e:
response['requestError'] = str(e)
except Exception, e:
response['exception'] = self.resultForException(e, trans)
response['timeResponded'] = time()
response = dumps(response)
except:
# internal error, report as HTTP server error
print 'PickleRPCServlet internal error'
print ''.join(traceback.format_exception(*sys.exc_info()))
trans.response().setStatus(500, 'Server Error')
else:
self.sendOK('text/python/pickle/dict', response, trans)
--- NEW FILE: RPCServlet.py ---
from HTTPServlet import HTTPServlet
import traceback, sys
class RPCServlet(HTTPServlet):
def call(self, methodName, *args, **keywords):
"""
Subclasses may override this class for custom handling of
methods.
"""
if methodName in self.exposedMethods():
return getattr(self, methodName)( *args, **keywords)
else:
raise NotImplementedError, methodName
def exposedMethods(self):
"""
Subclasses should return a list of methods that will be
exposed through XML-RPC.
"""
return ['exposedMethods']
def resultForException(self, e, trans):
"""
Given an unhandled exception, returns the string that should be
sent back in the RPC response as controlled by the
RPCExceptionReturn setting.
"""
# report exception back to server
setting = trans.application().setting('RPCExceptionReturn')
assert setting in ('occurred', 'exception', 'traceback'), 'setting=%r' % setting
if setting=='occurred':
result = 'unhandled exception'
elif setting=='exception':
result = str(e)
elif setting=='traceback':
result = ''.join(traceback.format_exception(*sys.exc_info()))
return result
def sendOK(self, contentType, contents, trans):
"""
Sends a 200 OK response with the given contents.
"""
response = trans.response()
response.setStatus(200, 'OK')
response.setHeader('Content-type', contentType)
response.setHeader('Content-length', str(len(contents)))
response.write(contents)
Index: Application.py
===================================================================
RCS file: /cvsroot/webware/Webware/WebKit/Application.py,v
retrieving revision 1.128
retrieving revision 1.129
diff -C2 -d -r1.128 -r1.129
*** Application.py 2002/01/26 17:35:58 1.128
--- Application.py 2002/02/01 08:54:06 1.129
***************
*** 296,299 ****
--- 296,300 ----
'Subject': 'Error'
},
+ 'RPCExceptionReturn': 'traceback',
'Contexts': { 'default': 'Examples',
'Admin': 'Admin',
Index: HTTPRequest.py
===================================================================
RCS file: /cvsroot/webware/Webware/WebKit/HTTPRequest.py,v
retrieving revision 1.35
retrieving revision 1.36
diff -C2 -d -r1.35 -r1.36
*** HTTPRequest.py 2001/12/18 21:33:58 1.35
--- HTTPRequest.py 2002/02/01 08:54:06 1.36
***************
*** 30,41 ****
self._environ = dict['environ']
self._input = dict['input']
# If the content type is text/xml, don't run self._input through cgi.FieldStorage; instead, save it in
# self._xmlInput to be processed by a servlet. This is needed for XML-RPC.
! if self._environ.get('CONTENT_TYPE', None) == 'text/xml':
self._xmlInput = self._input
self._input = StringIO('')
! else:
! self._xmlInput = None
!
self._fields = FieldStorage.FieldStorage(self._input, environ=self._environ, keep_blank_values=1, strict_parsing=0)
self._fields.parse_qs()
--- 30,43 ----
self._environ = dict['environ']
self._input = dict['input']
+ self._xmlInput = None
+ self._pickleInput = None
# If the content type is text/xml, don't run self._input through cgi.FieldStorage; instead, save it in
# self._xmlInput to be processed by a servlet. This is needed for XML-RPC.
! if self._environ.get('CONTENT_TYPE', None)=='text/xml':
self._xmlInput = self._input
self._input = StringIO('')
! if self._environ.get('CONTENT_TYPE', None)=='text/python/pickled/dict':
! self._dictInput = self._input
! self._input = StringIO('')
self._fields = FieldStorage.FieldStorage(self._input, environ=self._environ, keep_blank_values=1, strict_parsing=0)
self._fields.parse_qs()
***************
*** 142,145 ****
--- 144,148 ----
if debug: print "Done setting up request, found keys %s" % repr(self._fields.keys())
+
## Transactions ##
***************
*** 417,420 ****
--- 420,438 ----
"""
return self._xmlInput
+
+ def dictInput(self):
+ """
+ If content-type "text/python/pickled/dict" was POST'ed, this
+ will return a file-like object ready to read the XML.
+ Otherwise, it returns None.
+ """
+ return self._dictInput
+
+ def time(self):
+ """
+ Returns the time that the request was received.
+ """
+ return self._time
+
## Information ##
Index: XMLRPCServlet.py
===================================================================
RCS file: /cvsroot/webware/Webware/WebKit/XMLRPCServlet.py,v
retrieving revision 1.6
retrieving revision 1.7
diff -C2 -d -r1.6 -r1.7
*** XMLRPCServlet.py 2001/06/18 18:47:40 1.6
--- XMLRPCServlet.py 2002/02/01 08:54:06 1.7
***************
*** 12,28 ****
import xmlrpclib
import sys, string, traceback
! from HTTPServlet import HTTPServlet
! class XMLRPCServlet(HTTPServlet):
'''
XMLRPCServlet is a base class for XML-RPC servlets.
See Examples/XMLRPCExample.py for sample usage.
'''
def respondToPost(self, transaction):
! '''
This is similar to the xmlrpcserver.py example from the xmlrpc
library distribution, only it's been adapted to work within a
WebKit servlet.
! '''
try:
# get arguments
--- 12,31 ----
import xmlrpclib
import sys, string, traceback
! from RPCServlet import RPCServlet
! class XMLRPCServlet(RPCServlet):
'''
XMLRPCServlet is a base class for XML-RPC servlets.
See Examples/XMLRPCExample.py for sample usage.
+
+ For more Pythonic convenience at the cost of language independence,
+ see PickleRPCServlet.
'''
def respondToPost(self, transaction):
! """
This is similar to the xmlrpcserver.py example from the xmlrpc
library distribution, only it's been adapted to work within a
WebKit servlet.
! """
try:
# get arguments
***************
*** 35,47 ****
response = self.exposedMethods()[params[0]]
else:
! response = self.call(method, params)
if type(response) != type(()):
response = (response,)
! except:
! # report exception back to server
! if transaction.application().setting('IncludeTracebackInXMLRPCFault', 0):
! fault = string.join(traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2]))
! else:
! fault = 'unhandled exception'
response = xmlrpclib.dumps(xmlrpclib.Fault(1, fault))
else:
--- 38,46 ----
response = self.exposedMethods()[params[0]]
else:
! response = self.call(method, *params)
if type(response) != type(()):
response = (response,)
! except Exception, e:
! fault = self.resultForException(e, transaction)
response = xmlrpclib.dumps(xmlrpclib.Fault(1, fault))
else:
***************
*** 53,74 ****
transaction.response().setStatus(500, 'Server Error')
else:
! # got a valid XML RPC response
! transaction.response().setStatus(200, 'OK')
! transaction.response().setHeader("Content-type", "text/xml")
! transaction.response().setHeader("Content-length", str(len(response)))
! transaction.response().write(response)
!
! def call(self, method, params):
! '''
! Subclasses may override this class for custom handling of methods.
! '''
! if method in self.exposedMethods():
! return apply(getattr(self, method), params)
! else:
! raise 'method not implemented', method
!
! def exposedMethods(self):
! '''
! Subclasses should return a list of methods that will be exposed through XML-RPC.
! '''
! return []
--- 52,54 ----
transaction.response().setStatus(500, 'Server Error')
else:
! self.sendOK('text/html', response, transaction)
|