root/branches/cherrypy-2.x/cherrypy/filters/xmlrpcfilter.py
| Revision 1503 (checked in by fumanchu, 2 years ago) | |
|---|---|
| |
| 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.

