Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-2.x/cherrypy/filters/xmlrpcfilter.py

Revision 1503 (checked in by fumanchu, 2 years ago)

2.x fix for #533. CP3 will behave correctly and return a Fault when an XML-RPC method is not found.

  • Property svn:eol-style set to native
Line 
1 ##########################################################################
2 ## Remco Boerma
3 ## Sylvain Hellegouarch
4 ##
5 ## History:
6 ## 1.0.6   : 2005-12-04 Fixed error handling problems
7 ## 1.0.5   : 2005-11-04 Fixed Content-Length bug (http://www.cherrypy.org/ticket/384)
8 ## 1.0.4   : 2005-08-28 Fixed issues on input types which are not strings
9 ## 1.0.3   : 2005-01-28 Bugfix on content-length in 1.0.2 code fixed by
10 ##           Gian Paolo Ciceri
11 ## 1.0.2   : 2005-01-26 changed infile dox based on ticket #97
12 ## 1.0.1   : 2005-01-26 Speedup due to generator usage in CP2.
13 ##           The result is now converted to a list with length 1. So the complete
14 ##           xmlrpc result is written at once, and not per character. Thanks to
15 ##           Gian Paolo Ciceri for reporting the slowdown.
16 ## 1.0.0   : 2004-12-29 Released with CP2
17 ## 0.0.9   : 2004-12-23 made it CP2 #59 compatible (returns an iterable)
18 ##           Please note: as the xmlrpc doesn't know what you would want to return
19 ##           (and for the logic of marshalling) it will return Generator objects, as
20 ##           it is.. So it'll brake on that one!!
21 ##           NOTE: __don't try to return a Generator object to the caller__
22 ##           You could of course handle the generator usage internally, before sending
23 ##           the result. This breaks from the general cherrypy way of handling generators...
24 ## 0.0.8   : 2004-12-23 cherrypy.request.paramList should now be a filter.
25 ## 0.0.7   : 2004-12-07 inserted in the experimental branch (all remco boerma till here)
26 ## 0.0.6   : 2004-12-02 Converted basefilter to baseinputfileter,baseoutputfilter
27 ## 0.0.5   : 2004-11-22 "RPC2/" now changed to "/RPC2/" with the new mapping function
28 ##           Gian paolo ciceri notified me with the lack of passing parameters.
29 ##           Thanks Gian, it's now implemented against the latest trunk.
30 ##           Gian also came up with the idea of lazy content-type checking: if it's sent
31 ##           as a header, it should be 'text/xml', if not sent at all, it should be
32 ##           accepted. (While this it not the xml/rpc standard, it's handy for those
33 ##           xml-rpc client implementations wich don't send this header)
34 ## 0.0.4   : 2004-11-20 in setting the path, the dot is replaces by a slash
35 ##           therefore the regular CP2 routines knows how to handle things, as
36 ##           dots are not allowed in object names, it's varely easily adopted.
37 ##           Path + method handling. The default path is 'RPC2', this one is
38 ##           stripped. In case of path 'someurl' it is used for 'someurl' + method
39 ##           and 'someurl/someotherurl' is mapped to someurl.someotherurl + method.
40 ##           this way python serverproxies initialised with an url other than
41 ##           just the host are handled well. I don't hope any other service would map
42 ##           it to 'RPC2/someurl/someotherurl', cause then it would break i think. .
43 ## 0.0.3   : 2004-11-19 changed some examples (includes error checking
44 ##           wich returns marshalled Fault objects if the request is an RPC call.
45 ##           took testing code form afterRequestHeader and put it in
46 ##           testValidityOfRequest to make things a little simpler.
47 ##           simply log the requested function with parameters to stdout
48 ## 0.0.2   : 2004-11-19 the required cgi.py patch is no longer needed
49 ##           (thanks remi for noticing). Webbased calls to regular objects
50 ##           are now possible again ;) so it's no longer a dedicated xmlrpc
51 ##           server. The test script is also in a ready to run file named
52 ##           testRPC.py along with the test server: filterExample.py
53 ## 0.0.1   : 2004-11-19 informing the public, dropping loads of useless
54 ##           tests and debugging
55 ## 0.0.0   : 2004-11-19 initial alpha
56 ##
57 ##---------------------------------------------------------------------
58 ##
59 ## EXAMPLE CODE FOR THE SERVER:
60 ##    import cherrypy
61 ##
62 ##    class Root:
63 ##        def longString(self, s, times):
64 ##            return s * times
65 ##        longString.exposed = True
66 ##
67 ##    cherrypy.root = Root()
68 ##    cherrypy.config.update({'xmlrpc_filter.on': True,
69 ##                            'socket_port': 9001,
70 ##                            'thread_pool':10,
71 ##                            'socket_queue_size':10 })
72 ##    if __name__=='__main__':
73 ##        cherrypy.server.start()
74 ##
75 ## EXAMPLE CODE FOR THE CLIENT:
76 ## >>> import xmlrpclib
77 ## >>> server = xmlrpclib.ServerProxy('http://localhost:9001')
78 ## >>> assert server.longString('abc', 3) == 'abcabcabc'
79 ## >>>
80 ######################################################################
81
82
83 import sys
84 import xmlrpclib
85
86 import cherrypy
87 from basefilter import BaseFilter
88
89
90 class XmlRpcFilter(BaseFilter):
91     """Converts XMLRPC to CherryPy2 object system and vice-versa.
92     
93     PLEASE NOTE:
94     
95     before_request_body:
96         Unmarshalls the posted data to a methodname and parameters.
97         - These are stored in cherrypy.request.rpcMethod and .rpcParams
98         - The method is also stored in cherrypy.request.object_path,
99           so CP2 will find the right method to call for you,
100           based on the root's position.
101     before_main:
102         Marshalls cherrypy.response.body to xmlrpc.
103         - Until resolved: cherrypy.response.body must be a python source string;
104           this string is 'eval'ed to return the results. This will be
105           resolved in the future.
106         - Content-Type and Content-Length are set according to the new
107           (marshalled) data.
108     """
109    
110     def testValidityOfRequest(self):
111         # test if the content-length was sent
112         length = cherrypy.request.headers.get('Content-Length') or 0
113         ct = cherrypy.request.headers.get('Content-Type') or 'text/xml'
114         ct = ct.split(';')[0]
115         return int(length) > 0 and ct.lower() in ['text/xml']
116    
117     def before_request_body(self):
118         """ Called after the request header has been read/parsed"""
119         request = cherrypy.request
120        
121         request.xmlrpc_filter_on = cherrypy.config.get('xmlrpc_filter.on', False)
122         if not request.xmlrpc_filter_on:
123             return
124        
125         request.is_rpc = self.testValidityOfRequest()
126         if not request.is_rpc:
127             return
128        
129         request.processRequestBody = False
130         dataLength = int(request.headers.get('Content-Length') or 0)
131         data = request.rfile.read(dataLength)
132         try:
133             params, method = xmlrpclib.loads(data)
134         except Exception:
135             params, method = ('ERROR PARAMS', ), 'ERRORMETHOD'
136         request.rpcMethod, request.rpcParams = method, params
137        
138         # patch the path. there are only a few options:
139         # - 'RPC2' + method >> method
140         # - 'someurl' + method >> someurl.method
141         # - 'someurl/someother' + method >> someurl.someother.method
142         if not request.object_path.endswith('/'):
143             request.object_path += '/'
144         if request.object_path.startswith('/RPC2/'):
145             # strip the first /rpc2
146             request.object_path = request.object_path[5:]
147         request.object_path += str(method).replace('.', '/')
148         request.paramList = list(params)
149    
150     def before_main(self):
151         """This is a variation of main() from _cphttptools.
152         
153         It is redone here because:
154             1. we want to handle responses of any type
155             2. we need to pass our own paramList
156         """
157        
158         if (not cherrypy.config.get('xmlrpc_filter.on', False)
159             or not getattr(cherrypy.request, 'is_rpc', False)):
160             return
161        
162         path = cherrypy.request.object_path
163         while True:
164             try:
165                 page_handler, object_path, virtual_path = cherrypy.request.mapPathToObject(path)
166                
167                 # Decode any leftover %2F in the virtual_path atoms.
168                 virtual_path = [x.replace("%2F", "/") for x in virtual_path]
169                
170                 # Remove "root" from object_path and join it to get object_path
171                 self.object_path = '/' + '/'.join(object_path[1:])
172                 args = virtual_path + cherrypy.request.paramList
173                 body = page_handler(*args, **cherrypy.request.params)
174                 break
175             except cherrypy.InternalRedirect, x:
176                 # Try again with the new path
177                 path = x.path
178             except cherrypy.NotFound:
179                 # http://www.cherrypy.org/ticket/533
180                 # if a method is not found, an xmlrpclib.Fault should be returned
181                 # raising an exception here will do that; see
182                 # cherrypy.lib.xmlrpc.on_error
183                 raise Exception('method "%s" is not supported'
184                                 % cherrypy.request.rpcMethod)
185        
186         # See xmlrpclib documentation
187         # Python's None value cannot be used in standard XML-RPC;
188         # to allow using it via an extension, provide a true value for allow_none.
189         encoding = cherrypy.config.get('xmlrpc_filter.encoding', 'utf-8')
190         body = xmlrpclib.dumps((body,), methodresponse=1,
191                                encoding=encoding, allow_none=0)
192         self.respond(body)
193         cherrypy.request.execute_main = False
194    
195     def after_error_response(self):
196         if (not cherrypy.config.get('xmlrpc_filter.on', False)
197             or not getattr(cherrypy.request, 'is_rpc', False)):
198             return
199        
200         # Since we got here because of an exception,
201         # let's get its error message if any
202         body = str(sys.exc_info()[1])
203         body = xmlrpclib.dumps(xmlrpclib.Fault(1, body))
204         self.respond(body)
205    
206     def respond(self, body):
207         # The XML-RPC spec (http://www.xmlrpc.com/spec) says:
208         # "Unless there's a lower-level error, always return 200 OK."
209         # Since Python's xmlrpclib interprets a non-200 response
210         # as a "Protocol Error", we'll just return 200 every time.
211         response = cherrypy.response
212         response.status = '200 OK'
213         response.body = body
214         response.headers['Content-Type'] = 'text/xml'
215         response.headers['Content-Length'] = len(body)
216
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets