2 # Copyright(C) 2012 thomasv@gitorious
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public
15 # License along with this program. If not, see
16 # <http://www.gnu.org/licenses/agpl.html>.
19 from jsonrpclib import Fault
20 from jsonrpclib.jsonrpc import USE_UNIX_SOCKETS
21 import SimpleXMLRPCServer
37 def get_version(request):
39 if 'jsonrpc' in request.keys():
41 if 'id' in request.keys():
45 def validate_request(request):
46 if type(request) is not types.DictType:
48 -32600, 'Request must be {}, not %s.' % type(request)
51 rpcid = request.get('id', None)
52 version = get_version(request)
54 fault = Fault(-32600, 'Request %s invalid.' % request, rpcid=rpcid)
56 request.setdefault('params', [])
57 method = request.get('method', None)
58 params = request.get('params')
59 param_types = (types.ListType, types.DictType, types.TupleType)
60 if not method or type(method) not in types.StringTypes or \
61 type(params) not in param_types:
63 -32600, 'Invalid request parameters or method.', rpcid=rpcid
68 class StratumJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
70 def __init__(self, encoding=None):
71 SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,
75 def _marshaled_dispatch(self, data, dispatch_method = None):
78 request = jsonrpclib.loads(data)
80 fault = Fault(-32700, 'Request %s invalid. (%s)' % (data, e))
81 response = fault.response()
85 if type(request) is not types.ListType:
88 for req_entry in request:
89 result = validate_request(req_entry)
90 if type(result) is Fault:
91 responses.append(result.response())
93 resp_entry = self._marshaled_single_dispatch(req_entry)
94 if resp_entry is not None:
95 responses.append(resp_entry)
98 r = self._marshaled_single_dispatch({'method':'session.poll', 'params':[], 'id':'z' })
99 r = jsonrpclib.loads(r)
102 responses.append(json.dumps(item))
104 if len(responses) > 1:
105 response = '[%s]' % ','.join(responses)
106 elif len(responses) == 1:
107 response = responses[0]
113 def _marshaled_single_dispatch(self, request):
114 # TODO - Use the multiprocessing and skip the response if
115 # it is a notification
116 # Put in support for custom dispatcher here
117 # (See SimpleXMLRPCServer._marshaled_dispatch)
118 method = request.get('method')
119 params = request.get('params')
120 if params is None: params=[]
121 params = [ self.session_id, request['id'] ] + params
122 #print method, params
124 response = self._dispatch(method, params)
126 exc_type, exc_value, exc_tb = sys.exc_info()
127 fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
128 return fault.response()
129 if 'id' not in request.keys() or request['id'] == None:
130 # It's a notification
134 response = jsonrpclib.dumps(response,
140 exc_type, exc_value, exc_tb = sys.exc_info()
141 fault = Fault(-32603, '%s:%s' % (exc_type, exc_value))
142 return fault.response()
144 def _dispatch(self, method, params):
147 func = self.funcs[method]
149 if self.instance is not None:
150 if hasattr(self.instance, '_dispatch'):
151 return self.instance._dispatch(method, params)
154 func = SimpleXMLRPCServer.resolve_dotted_attribute(
159 except AttributeError:
163 if type(params) is types.ListType:
164 response = func(*params)
166 response = func(**params)
169 return Fault(-32602, 'Invalid parameters.')
171 err_lines = traceback.format_exc().splitlines()
172 trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
173 fault = jsonrpclib.Fault(-32603, 'Server error: %s' %
177 return Fault(-32601, 'Method %s not supported.' % method)
179 class StratumJSONRPCRequestHandler(
180 SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
183 if not self.is_rpc_path_valid():
187 self.server.session_id = None
188 c = self.headers.get('cookie')
190 if c[0:8]=='SESSION=':
191 #print "found cookie", c[8:]
192 self.server.session_id = c[8:]
194 if self.server.session_id is None:
195 r = self.server._marshaled_single_dispatch({'method':'session.create', 'params':[], 'id':'z' })
196 r = jsonrpclib.loads(r)
197 self.server.session_id = r.get('result')
198 #print "setting cookie", self.server.session_id
200 data = json.dumps([])
201 response = self.server._marshaled_dispatch(data)
202 self.send_response(200)
204 self.send_response(500)
205 err_lines = traceback.format_exc().splitlines()
206 trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
207 fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
208 response = fault.response()
209 print "500", trace_string
213 if hasattr(self.server, 'session_id'):
214 if self.server.session_id:
215 self.send_header("Set-Cookie", "SESSION=%s"%self.server.session_id)
216 self.session_id = None
218 self.send_header("Content-type", "application/json-rpc")
219 self.send_header("Content-length", str(len(response)))
221 self.wfile.write(response)
223 self.connection.shutdown(1)
227 if not self.is_rpc_path_valid():
231 max_chunk_size = 10*1024*1024
232 size_remaining = int(self.headers["content-length"])
234 while size_remaining:
235 chunk_size = min(size_remaining, max_chunk_size)
236 L.append(self.rfile.read(chunk_size))
237 size_remaining -= len(L[-1])
240 self.server.session_id = None
241 c = self.headers.get('cookie')
243 if c[0:8]=='SESSION=':
244 #print "found cookie", c[8:]
245 self.server.session_id = c[8:]
247 if self.server.session_id is None:
248 r = self.server._marshaled_single_dispatch({'method':'session.create', 'params':[], 'id':'z' })
249 r = jsonrpclib.loads(r)
250 self.server.session_id = r.get('result')
251 #print "setting cookie", self.server.session_id
253 response = self.server._marshaled_dispatch(data)
254 self.send_response(200)
256 self.send_response(500)
257 err_lines = traceback.format_exc().splitlines()
258 trace_string = '%s | %s' % (err_lines[-3], err_lines[-1])
259 fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)
260 response = fault.response()
261 print "500", trace_string
265 if hasattr(self.server, 'session_id'):
266 if self.server.session_id:
267 self.send_header("Set-Cookie", "SESSION=%s"%self.server.session_id)
268 self.session_id = None
270 self.send_header("Content-type", "application/json-rpc")
271 self.send_header("Content-length", str(len(response)))
273 self.wfile.write(response)
275 self.connection.shutdown(1)
278 class StratumJSONRPCServer(SocketServer.TCPServer, StratumJSONRPCDispatcher):
280 allow_reuse_address = True
282 def __init__(self, addr, requestHandler=StratumJSONRPCRequestHandler,
283 logRequests=False, encoding=None, bind_and_activate=True,
284 address_family=socket.AF_INET):
285 self.logRequests = logRequests
286 StratumJSONRPCDispatcher.__init__(self, encoding)
287 # TCPServer.__init__ has an extra parameter on 2.6+, so
288 # check Python version and decide on how to call it
289 vi = sys.version_info
290 self.address_family = address_family
291 if USE_UNIX_SOCKETS and address_family == socket.AF_UNIX:
292 # Unix sockets can't be bound if they already exist in the
293 # filesystem. The convention of e.g. X11 is to unlink
294 # before binding again.
295 if os.path.exists(addr):
299 logging.warning("Could not unlink socket %s", addr)
300 # if python 2.5 and lower
301 if vi[0] < 3 and vi[1] < 6:
302 SocketServer.TCPServer.__init__(self, addr, requestHandler)
304 SocketServer.TCPServer.__init__(self, addr, requestHandler,
306 if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
307 flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
308 flags |= fcntl.FD_CLOEXEC
309 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)