Removed /RPC2 default setting -- this isn't part of the JSON-RPC spec.
[invirt/packages/python-jsonrpclib.git] / jsonrpclib / SimpleJSONRPCServer.py
1 import jsonrpclib
2 from jsonrpclib import Fault
3 import SimpleXMLRPCServer
4 import SocketServer
5 import types
6 import traceback
7 import fcntl
8 import sys
9
10 def get_version(request):
11     # must be a dict
12     if 'jsonrpc' in request.keys():
13         return 2.0
14     if 'id' in request.keys():
15         return 1.0
16     return None
17     
18 def validate_request(request):
19     if type(request) is not types.DictType:
20         fault = Fault(
21             -32600, 'Request must be {}, not %s.' % type(request)
22         )
23         return fault
24     rpcid = request.get('id', None)
25     version = get_version(request)
26     if not version:
27         fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid)
28         return fault        
29     request.setdefault('params', [])
30     method = request.get('method', None)
31     params = request.get('params')
32     param_types = (types.ListType, types.DictType, types.TupleType)
33     if not method or type(method) not in types.StringTypes or \
34         type(params) not in param_types:
35         fault = Fault(
36             -32600, 'Invalid request parameters or method.', rpcid=rpcid
37         )
38         return fault
39     return True
40
41 class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
42
43     def __init__(self, encoding=None):
44         SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,
45                                         allow_none=True,
46                                         encoding=encoding)
47
48     def _marshaled_dispatch(self, data, dispatch_method = None):
49         response = None
50         try:
51             request = jsonrpclib.loads(data)
52         except Exception, e:
53             fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e))
54             response = fault.response()
55             return response
56         if not request:
57             fault = Fault(-32600, 'Request invalid -- no request data.')
58             return fault.response()
59         if type(request) is types.ListType:
60             # This SHOULD be a batch, by spec
61             responses = []
62             for req_entry in request:
63                 result = validate_request(req_entry)
64                 if type(result) is Fault:
65                     responses.append(result.response())
66                     continue
67                 resp_entry = self._marshaled_single_dispatch(req_entry)
68                 if resp_entry is not None:
69                     responses.append(resp_entry)
70             if len(responses) > 0:
71                 response = '[%s]' % ','.join(responses)
72             else:
73                 response = ''
74         else:    
75             result = validate_request(request)
76             if type(result) is Fault:
77                 return result.response()
78             response = self._marshaled_single_dispatch(request)
79         return response
80
81     def _marshaled_single_dispatch(self, request):
82         # TODO - Use the multiprocessing and skip the response if
83         # it is a notification
84         # Put in support for custom dispatcher here
85         # (See SimpleXMLRPCServer._marshaled_dispatch)
86         method = request.get('method')
87         params = request.get('params')
88         try:
89             response = self._dispatch(method, params)
90         except:
91             exc_type, exc_value, exc_tb = sys.exc_info()
92             fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
93             return fault.response()
94         if 'id' not in request.keys() or request['id'] == None:
95             # It's a notification
96             return None
97         try:
98             response = jsonrpclib.dumps(response,
99                                         methodresponse=True,
100                                         rpcid=request['id']
101                                         )
102             return response
103         except:
104             exc_type, exc_value, exc_tb = sys.exc_info()
105             fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
106             return fault.response()
107
108     def _dispatch(self, method, params):
109         func = None
110         try:
111             func = self.funcs[method]
112         except KeyError:
113             if self.instance is not None:
114                 if hasattr(self.instance, '_dispatch'):
115                     return self.instance._dispatch(method, params)
116                 else:
117                     try:
118                         func = SimpleXMLRPCServer.resolve_dotted_attribute(
119                             self.instance,
120                             method,
121                             True
122                             )
123                     except AttributeError:
124                         pass
125         if func is not None:
126             try:
127                 if type(params) is types.ListType:
128                     response = func(*params)
129                 else:
130                     response = func(**params)
131                 return response
132             except TypeError:
133                 return Fault(-32602, 'Invalid parameters.')
134             except:
135                 err_lines = traceback.format_exc().splitlines()
136                 trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
137                 fault = jsonrpclib.Fault(-32603, 'Server error: %s' % 
138                                          trace_string)
139                 return fault
140         else:
141             return Fault(-32601, 'Method %s not supported.' % method)
142
143 class SimpleJSONRPCRequestHandler(
144         SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
145     
146     def do_POST(self):
147         if not self.is_rpc_path_valid():
148             self.report_404()
149             return
150         try:
151             max_chunk_size = 10*1024*1024
152             size_remaining = int(self.headers["content-length"])
153             L = []
154             while size_remaining:
155                 chunk_size = min(size_remaining, max_chunk_size)
156                 L.append(self.rfile.read(chunk_size))
157                 size_remaining -= len(L[-1])
158             data = ''.join(L)
159             response = self.server._marshaled_dispatch(data)
160             self.send_response(200)
161         except Exception, e:
162             self.send_response(500)
163             err_lines = traceback.format_exc().splitlines()
164             trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
165             fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
166             response = fault.response()
167         if response == None:
168             response = ''
169         self.send_header("Content-type", "application/json-rpc")
170         self.send_header("Content-length", str(len(response)))
171         self.end_headers()
172         self.wfile.write(response)
173         self.wfile.flush()
174         self.connection.shutdown(1)
175
176 class SimpleJSONRPCServer(SocketServer.TCPServer,
177                          SimpleJSONRPCDispatcher):
178
179     allow_reuse_address = True
180
181     def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
182                  logRequests=True, encoding=None, bind_and_activate=True):
183         self.logRequests = logRequests
184         SimpleJSONRPCDispatcher.__init__(self, encoding)
185         # TCPServer.__init__ has an extra parameter on 2.6+, so
186         # check Python version and decide on how to call it
187         vi = sys.version_info
188         # if python 2.5 and lower
189         if vi[0] < 3 and vi[1] < 6:
190             SocketServer.TCPServer.__init__(self, addr, requestHandler)
191         else:
192             SocketServer.TCPServer.__init__(self, addr, requestHandler,
193                 bind_and_activate)
194         if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
195             flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
196             flags |= fcntl.FD_CLOEXEC
197             fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
198
199 class CGIJSONRPCRequestHandler(SimpleJSONRPCDispatcher):
200
201     def __init__(self, encoding=None):
202         SimpleJSONRPCDispatcher.__init__(self, encoding)
203
204     def handle_jsonrpc(self, request_text):
205         response = self._marshaled_dispatch(request_text)
206         print 'Content-Type: application/json-rpc'
207         print 'Content-Length: %d' % len(response)
208         print
209         sys.stdout.write(response)
210
211     handle_xmlrpc = handle_jsonrpc
212
213 if __name__ == '__main__':
214     print 'Running JSON-RPC server on port 8000'
215     server = SimpleJSONRPCServer(("localhost", 8000))
216     server.register_function(pow)
217     server.register_function(lambda x,y: x+y, 'add')
218     server.serve_forever()