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