PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/pyrope/jsonrpc.py

https://bitbucket.org/tomusdrw/mgr.p2p.proxy
Python | 1095 lines | 1056 code | 15 blank | 24 comment | 18 complexity | f6d74dc17c4ec9323f6dd623d712688f MD5 | raw file
Possible License(s): LGPL-3.0
  1. # -*- coding:utf-8 -*-
  2. """
  3. JSON-RPC (remote procedure call).
  4. It consists of 3 (independent) parts:
  5. - proxy/dispatcher
  6. - data structure / serializer
  7. - transport
  8. It's intended for JSON-RPC, but since the above 3 parts are independent,
  9. it could be used for other RPCs as well.
  10. Currently, JSON-RPC 2.0(pre) and JSON-RPC 1.0 are implemented
  11. :Version: 2008-08-31-beta
  12. :Status: experimental
  13. :Example:
  14. simple Client with JsonRPC2.0 and TCP/IP::
  15. >>> proxy = ServerProxy( JsonRpc20(), TransportTcpIp(addr=("127.0.0.1",31415)) )
  16. >>> proxy.echo( "hello world" )
  17. u'hello world'
  18. >>> proxy.echo( "bye." )
  19. u'bye.'
  20. simple Server with JsonRPC2.0 and TCP/IP with logging to STDOUT::
  21. >>> server = Server( JsonRpc20(), TransportTcpIp(addr=("127.0.0.1",31415), logfunc=log_stdout) )
  22. >>> def echo( s ):
  23. ... return s
  24. >>> server.register_function( echo )
  25. >>> server.serve( 2 ) # serve 2 requests # doctest: +ELLIPSIS
  26. listen ('127.0.0.1', 31415)
  27. ('127.0.0.1', ...) connected
  28. ('127.0.0.1', ...) <-- {"jsonrpc": "2.0", "method": "echo", "params": ["hello world"], "id": 0}
  29. ('127.0.0.1', ...) --> {"jsonrpc": "2.0", "result": "hello world", "id": 0}
  30. ('127.0.0.1', ...) close
  31. ('127.0.0.1', ...) connected
  32. ('127.0.0.1', ...) <-- {"jsonrpc": "2.0", "method": "echo", "params": ["bye."], "id": 0}
  33. ('127.0.0.1', ...) --> {"jsonrpc": "2.0", "result": "bye.", "id": 0}
  34. ('127.0.0.1', ...) close
  35. close ('127.0.0.1', 31415)
  36. Client with JsonRPC2.0 and an abstract Unix Domain Socket::
  37. >>> proxy = ServerProxy( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket") )
  38. >>> proxy.hi( message="hello" ) #named parameters
  39. u'hi there'
  40. >>> proxy.test() #fault
  41. Traceback (most recent call last):
  42. ...
  43. jsonrpc.RPCMethodNotFound: <RPCFault -32601: u'Method not found.' (None)>
  44. >>> proxy.debug.echo( "hello world" ) #hierarchical procedures
  45. u'hello world'
  46. Server with JsonRPC2.0 and abstract Unix Domain Socket with a logfile::
  47. >>> server = Server( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket", logfunc=log_file("mylog.txt")) )
  48. >>> def echo( s ):
  49. ... return s
  50. >>> def hi( message ):
  51. ... return "hi there"
  52. >>> server.register_function( hi )
  53. >>> server.register_function( echo, name="debug.echo" )
  54. >>> server.serve( 3 ) # serve 3 requests
  55. "mylog.txt" then contains:
  56. listen '\\x00.rpcsocket'
  57. '' connected
  58. '' --> '{"jsonrpc": "2.0", "method": "hi", "params": {"message": "hello"}, "id": 0}'
  59. '' <-- '{"jsonrpc": "2.0", "result": "hi there", "id": 0}'
  60. '' close
  61. '' connected
  62. '' --> '{"jsonrpc": "2.0", "method": "test", "id": 0}'
  63. '' <-- '{"jsonrpc": "2.0", "error": {"code":-32601, "message": "Method not found."}, "id": 0}'
  64. '' close
  65. '' connected
  66. '' --> '{"jsonrpc": "2.0", "method": "debug.echo", "params": ["hello world"], "id": 0}'
  67. '' <-- '{"jsonrpc": "2.0", "result": "hello world", "id": 0}'
  68. '' close
  69. close '\\x00.rpcsocket'
  70. :Note: all exceptions derived from RPCFault are propagated to the client.
  71. other exceptions are logged and result in a sent-back "empty" INTERNAL_ERROR.
  72. :Uses: simplejson, socket, sys,time,codecs
  73. :SeeAlso: JSON-RPC 2.0 proposal, 1.0 specification
  74. :Warning:
  75. .. Warning::
  76. This is **experimental** code!
  77. :Bug:
  78. :Author: Roland Koebler (rk(at)simple-is-better.org)
  79. :Copyright: 2007-2008 by Roland Koebler (rk(at)simple-is-better.org)
  80. :License: see __license__
  81. :Changelog:
  82. - 2008-08-31: 1st release
  83. TODO:
  84. - server: multithreading rpc-server
  85. - client: multicall (send several requests)
  86. - transport: SSL sockets, maybe HTTP, HTTPS
  87. - types: support for date/time (ISO 8601)
  88. - errors: maybe customizable error-codes/exceptions
  89. - mixed 1.0/2.0 server ?
  90. - system description etc. ?
  91. - maybe test other json-serializers, like cjson?
  92. """
  93. __version__ = "2008-08-31-beta"
  94. __author__ = "Roland Koebler <rk(at)simple-is-better.org>"
  95. __license__ = """Copyright (c) 2007-2008 by Roland Koebler (rk(at)simple-is-better.org)
  96. Permission is hereby granted, free of charge, to any person obtaining
  97. a copy of this software and associated documentation files (the
  98. "Software"), to deal in the Software without restriction, including
  99. without limitation the rights to use, copy, modify, merge, publish,
  100. distribute, sublicense, and/or sell copies of the Software, and to
  101. permit persons to whom the Software is furnished to do so, subject to
  102. the following conditions:
  103. The above copyright notice and this permission notice shall be included
  104. in all copies or substantial portions of the Software.
  105. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  106. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  107. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  108. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  109. CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  110. TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  111. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""
  112. #=========================================
  113. #import
  114. import sys
  115. #=========================================
  116. # errors
  117. #----------------------
  118. # error-codes + exceptions
  119. #JSON-RPC 2.0 error-codes
  120. PARSE_ERROR = -32700
  121. INVALID_REQUEST = -32600
  122. METHOD_NOT_FOUND = -32601
  123. INVALID_METHOD_PARAMS = -32602 #invalid number/type of parameters
  124. INTERNAL_ERROR = -32603 #"all other errors"
  125. #additional error-codes
  126. PROCEDURE_EXCEPTION = -32000
  127. AUTHENTIFICATION_ERROR = -32001
  128. PERMISSION_DENIED = -32002
  129. INVALID_PARAM_VALUES = -32003
  130. #human-readable messages
  131. ERROR_MESSAGE = {
  132. PARSE_ERROR : "Parse error.",
  133. INVALID_REQUEST : "Invalid Request.",
  134. METHOD_NOT_FOUND : "Method not found.",
  135. INVALID_METHOD_PARAMS : "Invalid parameters.",
  136. INTERNAL_ERROR : "Internal error.",
  137. PROCEDURE_EXCEPTION : "Procedure exception.",
  138. AUTHENTIFICATION_ERROR : "Authentification error.",
  139. PERMISSION_DENIED : "Permission denied.",
  140. INVALID_PARAM_VALUES: "Invalid parameter values."
  141. }
  142. #----------------------
  143. # exceptions
  144. class RPCError(Exception):
  145. """Base class for rpc-errors."""
  146. class RPCTransportError(RPCError):
  147. """Transport error."""
  148. class RPCTimeoutError(RPCTransportError):
  149. """Transport/reply timeout."""
  150. class RPCFault(RPCError):
  151. """RPC error/fault package received.
  152. This exception can also be used as a class, to generate a
  153. RPC-error/fault message.
  154. :Variables:
  155. - error_code: the RPC error-code
  156. - error_string: description of the error
  157. - error_data: optional additional information
  158. (must be json-serializable)
  159. :TODO: improve __str__
  160. """
  161. def __init__(self, error_code, error_message, error_data=None):
  162. RPCError.__init__(self)
  163. self.error_code = error_code
  164. self.error_message = error_message
  165. self.error_data = error_data
  166. def __str__(self):
  167. return repr(self)
  168. def __repr__(self):
  169. return( "<RPCFault %s: %s (%s)>" % (self.error_code, repr(self.error_message), repr(self.error_data)) )
  170. class RPCParseError(RPCFault):
  171. """Broken rpc-package. (PARSE_ERROR)"""
  172. def __init__(self, error_data=None):
  173. RPCFault.__init__(self, PARSE_ERROR, ERROR_MESSAGE[PARSE_ERROR], error_data)
  174. class RPCInvalidRPC(RPCFault):
  175. """Invalid rpc-package. (INVALID_REQUEST)"""
  176. def __init__(self, error_data=None):
  177. RPCFault.__init__(self, INVALID_REQUEST, ERROR_MESSAGE[INVALID_REQUEST], error_data)
  178. class RPCMethodNotFound(RPCFault):
  179. """Method not found. (METHOD_NOT_FOUND)"""
  180. def __init__(self, error_data=None):
  181. RPCFault.__init__(self, METHOD_NOT_FOUND, ERROR_MESSAGE[METHOD_NOT_FOUND], error_data)
  182. class RPCInvalidMethodParams(RPCFault):
  183. """Invalid method-parameters. (INVALID_METHOD_PARAMS)"""
  184. def __init__(self, error_data=None):
  185. RPCFault.__init__(self, INVALID_METHOD_PARAMS, ERROR_MESSAGE[INVALID_METHOD_PARAMS], error_data)
  186. class RPCInternalError(RPCFault):
  187. """Internal error. (INTERNAL_ERROR)"""
  188. def __init__(self, error_data=None):
  189. RPCFault.__init__(self, INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], error_data)
  190. class RPCProcedureException(RPCFault):
  191. """Procedure exception. (PROCEDURE_EXCEPTION)"""
  192. def __init__(self, error_data=None):
  193. RPCFault.__init__(self, PROCEDURE_EXCEPTION, ERROR_MESSAGE[PROCEDURE_EXCEPTION], error_data)
  194. class RPCAuthentificationError(RPCFault):
  195. """AUTHENTIFICATION_ERROR"""
  196. def __init__(self, error_data=None):
  197. RPCFault.__init__(self, AUTHENTIFICATION_ERROR, ERROR_MESSAGE[AUTHENTIFICATION_ERROR], error_data)
  198. class RPCPermissionDenied(RPCFault):
  199. """PERMISSION_DENIED"""
  200. def __init__(self, error_data=None):
  201. RPCFault.__init__(self, PERMISSION_DENIED, ERROR_MESSAGE[PERMISSION_DENIED], error_data)
  202. class RPCInvalidParamValues(RPCFault):
  203. """INVALID_PARAM_VALUES"""
  204. def __init__(self, error_data=None):
  205. RPCFault.__init__(self, INVALID_PARAM_VALUES, ERROR_MESSAGE[INVALID_PARAM_VALUES], error_data)
  206. #=========================================
  207. # data structure / serializer
  208. try:
  209. import simplejson
  210. except ImportError, err:
  211. print "FATAL: json-module 'simplejson' is missing (%s)" % (err)
  212. sys.exit(1)
  213. #----------------------
  214. #
  215. def dictkeyclean(d):
  216. """Convert all keys of the dict 'd' to (ascii-)strings.
  217. :Raises: UnicodeEncodeError
  218. """
  219. new_d = {}
  220. for (k, v) in d.iteritems():
  221. new_d[str(k)] = v
  222. return new_d
  223. #----------------------
  224. # JSON-RPC 1.0
  225. class JsonRpc10:
  226. """JSON-RPC V1.0 data-structure / serializer
  227. This implementation is quite liberal in what it accepts: It treats
  228. missing "params" and "id" in Requests and missing "result"/"error" in
  229. Responses as empty/null.
  230. :SeeAlso: JSON-RPC 1.0 specification
  231. :TODO: catch simplejson.dumps not-serializable-exceptions
  232. """
  233. def __init__(self, dumps=simplejson.dumps, loads=simplejson.loads):
  234. """init: set serializer to use
  235. :Parameters:
  236. - dumps: json-encoder-function
  237. - loads: json-decoder-function
  238. :Note: The dumps_* functions of this class already directly create
  239. the invariant parts of the resulting json-object themselves,
  240. without using the given json-encoder-function.
  241. """
  242. self.dumps = dumps
  243. self.loads = loads
  244. def dumps_request( self, method, params=(), id=0 ):
  245. """serialize JSON-RPC-Request
  246. :Parameters:
  247. - method: the method-name (str/unicode)
  248. - params: the parameters (list/tuple)
  249. - id: if id=None, this results in a Notification
  250. :Returns: | {"method": "...", "params": ..., "id": ...}
  251. | "method", "params" and "id" are always in this order.
  252. :Raises: TypeError if method/params is of wrong type or
  253. not JSON-serializable
  254. """
  255. if not isinstance(method, (str, unicode)):
  256. raise TypeError('"method" must be a string (or unicode string).')
  257. if not isinstance(params, (tuple, list)):
  258. raise TypeError("params must be a tuple/list.")
  259. return '{"method": %s, "params": %s, "id": %s}' % \
  260. (self.dumps(method), self.dumps(params), self.dumps(id))
  261. def dumps_notification( self, method, params=() ):
  262. """serialize a JSON-RPC-Notification
  263. :Parameters: see dumps_request
  264. :Returns: | {"method": "...", "params": ..., "id": null}
  265. | "method", "params" and "id" are always in this order.
  266. :Raises: see dumps_request
  267. """
  268. if not isinstance(method, (str, unicode)):
  269. raise TypeError('"method" must be a string (or unicode string).')
  270. if not isinstance(params, (tuple, list)):
  271. raise TypeError("params must be a tuple/list.")
  272. return '{"method": %s, "params": %s, "id": null}' % \
  273. (self.dumps(method), self.dumps(params))
  274. def dumps_response( self, result, id=None ):
  275. """serialize a JSON-RPC-Response (without error)
  276. :Returns: | {"result": ..., "error": null, "id": ...}
  277. | "result", "error" and "id" are always in this order.
  278. :Raises: TypeError if not JSON-serializable
  279. """
  280. return '{"result": %s, "error": null, "id": %s}' % \
  281. (self.dumps(result), self.dumps(id))
  282. def dumps_error( self, error, id=None ):
  283. """serialize a JSON-RPC-Response-error
  284. Since JSON-RPC 1.0 does not define an error-object, this uses the
  285. JSON-RPC 2.0 error-object.
  286. :Parameters:
  287. - error: a RPCFault instance
  288. :Returns: | {"result": null, "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...}
  289. | "result", "error" and "id" are always in this order, data is omitted if None.
  290. :Raises: ValueError if error is not a RPCFault instance,
  291. TypeError if not JSON-serializable
  292. """
  293. if not isinstance(error, RPCFault):
  294. raise ValueError("""error must be a RPCFault-instance.""")
  295. if error.error_data is None:
  296. return '{"result": null, "error": {"code":%s, "message": %s}, "id": %s}' % \
  297. (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(id))
  298. else:
  299. return '{"result": null, "error": {"code":%s, "message": %s, "data": %s}, "id": %s}' % \
  300. (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(error.error_data), self.dumps(id))
  301. def loads_request( self, string ):
  302. """de-serialize a JSON-RPC Request/Notification
  303. :Returns: | [method_name, params, id] or [method_name, params]
  304. | params is a tuple/list
  305. | if id is missing, this is a Notification
  306. :Raises: RPCParseError, RPCInvalidRPC, RPCInvalidMethodParams
  307. """
  308. try:
  309. data = self.loads(string)
  310. except ValueError, err:
  311. raise RPCParseError("No valid JSON. (%s)" % str(err))
  312. if not isinstance(data, dict): raise RPCInvalidRPC("No valid RPC-package.")
  313. if "method" not in data: raise RPCInvalidRPC("""Invalid Request, "method" is missing.""")
  314. if not isinstance(data["method"], (str, unicode)):
  315. raise RPCInvalidRPC("""Invalid Request, "method" must be a string.""")
  316. if "id" not in data: data["id"] = None #be liberal
  317. if "params" not in data: data["params"] = () #be liberal
  318. if not isinstance(data["params"], (list, tuple)):
  319. raise RPCInvalidRPC("""Invalid Request, "params" must be an array.""")
  320. if len(data) != 3: raise RPCInvalidRPC("""Invalid Request, additional fields found.""")
  321. # notification / request
  322. if data["id"] is None:
  323. return data["method"], data["params"] #notification
  324. else:
  325. return data["method"], data["params"], data["id"] #request
  326. def loads_response( self, string ):
  327. """de-serialize a JSON-RPC Response/error
  328. :Returns: | [result, id] for Responses
  329. :Raises: | RPCFault+derivates for error-packages/faults, RPCParseError, RPCInvalidRPC
  330. | Note that for error-packages which do not match the
  331. V2.0-definition, RPCFault(-1, "Error", RECEIVED_ERROR_OBJ)
  332. is raised.
  333. """
  334. try:
  335. data = self.loads(string)
  336. except ValueError, err:
  337. raise RPCParseError("No valid JSON. (%s)" % str(err))
  338. if not isinstance(data, dict): raise RPCInvalidRPC("No valid RPC-package.")
  339. if "id" not in data: raise RPCInvalidRPC("""Invalid Response, "id" missing.""")
  340. if "result" not in data: data["result"] = None #be liberal
  341. if "error" not in data: data["error"] = None #be liberal
  342. if len(data) != 3: raise RPCInvalidRPC("""Invalid Response, additional or missing fields.""")
  343. #error
  344. if data["error"] is not None:
  345. if data["result"] is not None:
  346. raise RPCInvalidRPC("""Invalid Response, one of "result" or "error" must be null.""")
  347. #v2.0 error-format
  348. if( isinstance(data["error"], dict) and "code" in data["error"] and "message" in data["error"] and
  349. (len(data["error"])==2 or ("data" in data["error"] and len(data["error"])==3)) ):
  350. if "data" not in data["error"]:
  351. error_data = None
  352. else:
  353. error_data = data["error"]["data"]
  354. if data["error"]["code"] == PARSE_ERROR:
  355. raise RPCParseError(error_data)
  356. elif data["error"]["code"] == INVALID_REQUEST:
  357. raise RPCInvalidRPC(error_data)
  358. elif data["error"]["code"] == METHOD_NOT_FOUND:
  359. raise RPCMethodNotFound(error_data)
  360. elif data["error"]["code"] == INVALID_METHOD_PARAMS:
  361. raise RPCInvalidMethodParams(error_data)
  362. elif data["error"]["code"] == INTERNAL_ERROR:
  363. raise RPCInternalError(error_data)
  364. elif data["error"]["code"] == PROCEDURE_EXCEPTION:
  365. raise RPCProcedureException(error_data)
  366. elif data["error"]["code"] == AUTHENTIFICATION_ERROR:
  367. raise RPCAuthentificationError(error_data)
  368. elif data["error"]["code"] == PERMISSION_DENIED:
  369. raise RPCPermissionDenied(error_data)
  370. elif data["error"]["code"] == INVALID_PARAM_VALUES:
  371. raise RPCInvalidParamValues(error_data)
  372. else:
  373. raise RPCFault(data["error"]["code"], data["error"]["message"], error_data)
  374. #other error-format
  375. else:
  376. raise RPCFault(-1, "Error", data["error"])
  377. #result
  378. else:
  379. return data["result"], data["id"]
  380. #----------------------
  381. # JSON-RPC 2.0
  382. class JsonRpc20:
  383. """JSON-RPC V2.0 data-structure / serializer
  384. :SeeAlso: JSON-RPC 2.0 specification
  385. :TODO: catch simplejson.dumps not-serializable-exceptions
  386. """
  387. def __init__(self, dumps=simplejson.dumps, loads=simplejson.loads):
  388. """init: set serializer to use
  389. :Parameters:
  390. - dumps: json-encoder-function
  391. - loads: json-decoder-function
  392. :Note: The dumps_* functions of this class already directly create
  393. the invariant parts of the resulting json-object themselves,
  394. without using the given json-encoder-function.
  395. """
  396. self.dumps = dumps
  397. self.loads = loads
  398. def dumps_request( self, method, params=(), id=0 ):
  399. """serialize JSON-RPC-Request
  400. :Parameters:
  401. - method: the method-name (str/unicode)
  402. - params: the parameters (list/tuple/dict)
  403. - id: the id (should not be None)
  404. :Returns: | {"jsonrpc": "2.0", "method": "...", "params": ..., "id": ...}
  405. | "jsonrpc", "method", "params" and "id" are always in this order.
  406. | "params" is omitted if empty
  407. :Raises: TypeError if method/params is of wrong type or
  408. not JSON-serializable
  409. """
  410. if not isinstance(method, (str, unicode)):
  411. raise TypeError('"method" must be a string (or unicode string).')
  412. if not isinstance(params, (tuple, list, dict)):
  413. raise TypeError("params must be a tuple/list/dict or None.")
  414. if params:
  415. return '{"jsonrpc": "2.0", "method": %s, "params": %s, "id": %s}' % \
  416. (self.dumps(method), self.dumps(params), self.dumps(id))
  417. else:
  418. return '{"jsonrpc": "2.0", "method": %s, "id": %s}' % \
  419. (self.dumps(method), self.dumps(id))
  420. def dumps_notification( self, method, params=() ):
  421. """serialize a JSON-RPC-Notification
  422. :Parameters: see dumps_request
  423. :Returns: | {"jsonrpc": "2.0", "method": "...", "params": ...}
  424. | "jsonrpc", "method" and "params" are always in this order.
  425. :Raises: see dumps_request
  426. """
  427. if not isinstance(method, (str, unicode)):
  428. raise TypeError('"method" must be a string (or unicode string).')
  429. if not isinstance(params, (tuple, list, dict)):
  430. raise TypeError("params must be a tuple/list/dict or None.")
  431. if params:
  432. return '{"jsonrpc": "2.0", "method": %s, "params": %s}' % \
  433. (self.dumps(method), self.dumps(params))
  434. else:
  435. return '{"jsonrpc": "2.0", "method": %s}' % \
  436. (self.dumps(method))
  437. def dumps_response( self, result, id=None ):
  438. """serialize a JSON-RPC-Response (without error)
  439. :Returns: | {"jsonrpc": "2.0", "result": ..., "id": ...}
  440. | "jsonrpc", "result", and "id" are always in this order.
  441. :Raises: TypeError if not JSON-serializable
  442. """
  443. return '{"jsonrpc": "2.0", "result": %s, "id": %s}' % \
  444. (self.dumps(result), self.dumps(id))
  445. def dumps_error( self, error, id=None ):
  446. """serialize a JSON-RPC-Response-error
  447. :Parameters:
  448. - error: a RPCFault instance
  449. :Returns: | {"jsonrpc": "2.0", "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...}
  450. | "jsonrpc", "result", "error" and "id" are always in this order, data is omitted if None.
  451. :Raises: ValueError if error is not a RPCFault instance,
  452. TypeError if not JSON-serializable
  453. """
  454. if not isinstance(error, RPCFault):
  455. raise ValueError("""error must be a RPCFault-instance.""")
  456. if error.error_data is None:
  457. return '{"jsonrpc": "2.0", "error": {"code":%s, "message": %s}, "id": %s}' % \
  458. (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(id))
  459. else:
  460. return '{"jsonrpc": "2.0", "error": {"code":%s, "message": %s, "data": %s}, "id": %s}' % \
  461. (self.dumps(error.error_code), self.dumps(error.error_message), self.dumps(error.error_data), self.dumps(id))
  462. def loads_request( self, string ):
  463. """de-serialize a JSON-RPC Request/Notification
  464. :Returns: | [method_name, params, id] or [method_name, params]
  465. | params is a tuple/list or dict (with only str-keys)
  466. | if id is missing, this is a Notification
  467. :Raises: RPCParseError, RPCInvalidRPC, RPCInvalidMethodParams
  468. """
  469. try:
  470. data = self.loads(string)
  471. except ValueError, err:
  472. raise RPCParseError("No valid JSON. (%s)" % str(err))
  473. if not isinstance(data, dict): raise RPCInvalidRPC("No valid RPC-package.")
  474. if "jsonrpc" not in data: raise RPCInvalidRPC("""Invalid Response, "jsonrpc" missing.""")
  475. if not isinstance(data["jsonrpc"], (str, unicode)):
  476. raise RPCInvalidRPC("""Invalid Response, "jsonrpc" must be a string.""")
  477. if data["jsonrpc"] != "2.0": raise RPCInvalidRPC("""Invalid jsonrpc version.""")
  478. if "method" not in data: raise RPCInvalidRPC("""Invalid Request, "method" is missing.""")
  479. if not isinstance(data["method"], (str, unicode)):
  480. raise RPCInvalidRPC("""Invalid Request, "method" must be a string.""")
  481. if "params" not in data: data["params"] = ()
  482. #convert params-keys from unicode to str
  483. elif isinstance(data["params"], dict):
  484. try:
  485. data["params"] = dictkeyclean(data["params"])
  486. except UnicodeEncodeError:
  487. raise RPCInvalidMethodParams("Parameter-names must be in ascii.")
  488. elif not isinstance(data["params"], (list, tuple)):
  489. raise RPCInvalidRPC("""Invalid Request, "params" must be an array or object.""")
  490. if not( len(data)==3 or ("id" in data and len(data)==4) ):
  491. raise RPCInvalidRPC("""Invalid Request, additional fields found.""")
  492. # notification / request
  493. if "id" not in data:
  494. return data["method"], data["params"] #notification
  495. else:
  496. return data["method"], data["params"], data["id"] #request
  497. def loads_response( self, string ):
  498. """de-serialize a JSON-RPC Response/error
  499. :Returns: | [result, id] for Responses
  500. :Raises: | RPCFault+derivates for error-packages/faults, RPCParseError, RPCInvalidRPC
  501. """
  502. try:
  503. data = self.loads(string)
  504. except ValueError, err:
  505. raise RPCParseError("No valid JSON. (%s)" % str(err))
  506. if not isinstance(data, dict): raise RPCInvalidRPC("No valid RPC-package.")
  507. if "jsonrpc" not in data: raise RPCInvalidRPC("""Invalid Response, "jsonrpc" missing.""")
  508. if not isinstance(data["jsonrpc"], (str, unicode)):
  509. raise RPCInvalidRPC("""Invalid Response, "jsonrpc" must be a string.""")
  510. if data["jsonrpc"] != "2.0": raise RPCInvalidRPC("""Invalid jsonrpc version.""")
  511. if "id" not in data: raise RPCInvalidRPC("""Invalid Response, "id" missing.""")
  512. if "result" not in data: data["result"] = None
  513. if "error" not in data: data["error"] = None
  514. if len(data) != 4: raise RPCInvalidRPC("""Invalid Response, additional or missing fields.""")
  515. #error
  516. if data["error"] is not None:
  517. if data["result"] is not None:
  518. raise RPCInvalidRPC("""Invalid Response, only "result" OR "error" allowed.""")
  519. if not isinstance(data["error"], dict): raise RPCInvalidRPC("Invalid Response, invalid error-object.")
  520. if "code" not in data["error"] or "message" not in data["error"]:
  521. raise RPCInvalidRPC("Invalid Response, invalid error-object.")
  522. if "data" not in data["error"]: data["error"]["data"] = None
  523. if len(data["error"]) != 3:
  524. raise RPCInvalidRPC("Invalid Response, invalid error-object.")
  525. error_data = data["error"]["data"]
  526. if data["error"]["code"] == PARSE_ERROR:
  527. raise RPCParseError(error_data)
  528. elif data["error"]["code"] == INVALID_REQUEST:
  529. raise RPCInvalidRPC(error_data)
  530. elif data["error"]["code"] == METHOD_NOT_FOUND:
  531. raise RPCMethodNotFound(error_data)
  532. elif data["error"]["code"] == INVALID_METHOD_PARAMS:
  533. raise RPCInvalidMethodParams(error_data)
  534. elif data["error"]["code"] == INTERNAL_ERROR:
  535. raise RPCInternalError(error_data)
  536. elif data["error"]["code"] == PROCEDURE_EXCEPTION:
  537. raise RPCProcedureException(error_data)
  538. elif data["error"]["code"] == AUTHENTIFICATION_ERROR:
  539. raise RPCAuthentificationError(error_data)
  540. elif data["error"]["code"] == PERMISSION_DENIED:
  541. raise RPCPermissionDenied(error_data)
  542. elif data["error"]["code"] == INVALID_PARAM_VALUES:
  543. raise RPCInvalidParamValues(error_data)
  544. else:
  545. raise RPCFault(data["error"]["code"], data["error"]["message"], error_data)
  546. #result
  547. else:
  548. return data["result"], data["id"]
  549. #=========================================
  550. # transports
  551. #----------------------
  552. # transport-logging
  553. import codecs
  554. import time
  555. def log_dummy( message ):
  556. """dummy-logger: do nothing"""
  557. pass
  558. def log_stdout( message ):
  559. """print message to STDOUT"""
  560. print message
  561. def log_file( filename ):
  562. """return a logfunc which logs to a file (in utf-8)"""
  563. def logfile( message ):
  564. f = codecs.open( filename, 'a', encoding='utf-8' )
  565. f.write( message+"\n" )
  566. f.close()
  567. return logfile
  568. def log_filedate( filename ):
  569. """return a logfunc which logs date+message to a file (in utf-8)"""
  570. def logfile( message ):
  571. f = codecs.open( filename, 'a', encoding='utf-8' )
  572. f.write( time.strftime("%Y-%m-%d %H:%M:%S ")+message+"\n" )
  573. f.close()
  574. return logfile
  575. #----------------------
  576. class Transport:
  577. """generic Transport-interface.
  578. This class, and especially its methods and docstrings,
  579. define the Transport-Interface.
  580. """
  581. def __init__(self):
  582. pass
  583. def send( self, data ):
  584. """send all data. must be implemented by derived classes."""
  585. raise NotImplementedError
  586. def recv( self ):
  587. """receive data. must be implemented by derived classes."""
  588. raise NotImplementedError
  589. def sendrecv( self, string ):
  590. """send + receive data"""
  591. self.send( string )
  592. return self.recv()
  593. def serve( self, handler, n=None ):
  594. """serve (forever or for n communicaions).
  595. - receive data
  596. - call result = handler(data)
  597. - send back result if not None
  598. The serving can be stopped by SIGINT.
  599. :TODO:
  600. - how to stop?
  601. maybe use a .run-file, and stop server if file removed?
  602. - maybe make n_current accessible? (e.g. for logging)
  603. """
  604. n_current = 0
  605. while 1:
  606. if n is not None and n_current >= n:
  607. break
  608. data = self.recv()
  609. result = handler(data)
  610. if result is not None:
  611. self.send( result )
  612. n_current += 1
  613. class TransportSTDINOUT(Transport):
  614. """receive from STDIN, send to STDOUT.
  615. Useful e.g. for debugging.
  616. """
  617. def send(self, string):
  618. """write data to STDOUT with '***SEND:' prefix """
  619. print "***SEND:"
  620. print string
  621. def recv(self):
  622. """read data from STDIN"""
  623. print "***RECV (please enter, ^D ends.):"
  624. return sys.stdin.read()
  625. import socket, select
  626. TERMINAL = '<!@#!@#!@#ZCX?>'
  627. TERMINAL_LEN = len(TERMINAL)
  628. class TransportSocket(Transport):
  629. """Transport via socket.
  630. :SeeAlso: python-module socket
  631. :TODO:
  632. - documentation
  633. - improve this (e.g. make sure that connections are closed, socket-files are deleted etc.)
  634. - exception-handling? (socket.error)
  635. """
  636. def __init__( self, addr, limit=4096, sock_type=socket.AF_INET, sock_prot=socket.SOCK_STREAM, timeout=5.0, logfunc=log_dummy ):
  637. """
  638. :Parameters:
  639. - addr: socket-address
  640. - timeout: timeout in seconds
  641. - logfunc: function for logging, logfunc(message)
  642. :Raises: socket.timeout after timeout
  643. """
  644. self.limit = limit
  645. self.addr = addr
  646. self.s_type = sock_type
  647. self.s_prot = sock_prot
  648. self.s = None
  649. self.timeout = timeout
  650. self.log = logfunc
  651. def connect( self ):
  652. self.close()
  653. self.log( "connect to %s" % repr(self.addr) )
  654. self.s = socket.socket( self.s_type, self.s_prot )
  655. self.s.settimeout( self.timeout )
  656. self.s.connect( self.addr )
  657. def close( self ):
  658. if self.s is not None:
  659. self.log( "close %s" % repr(self.addr) )
  660. self.s.close()
  661. self.s = None
  662. def __repr__(self):
  663. return "<TransportSocket, %s>" % repr(self.addr)
  664. def send( self, string ):
  665. if self.s is None:
  666. self.connect()
  667. self.log( "--> "+repr(string) )
  668. self.sendAll(self.s, string)
  669. def sendAll(self, socket, string):
  670. socket.sendall(string + TERMINAL)
  671. def recvAll(self, socket):
  672. allData = ''
  673. terminalReceived = False
  674. while not terminalReceived:
  675. data = socket.recv(self.limit)
  676. allData += data
  677. if len(allData) > TERMINAL_LEN:
  678. terminalReceived = allData[-TERMINAL_LEN:] == TERMINAL
  679. return allData[:-TERMINAL_LEN]
  680. def recv( self ):
  681. if self.s is None:
  682. self.connect()
  683. data = self.recvAll(self.s)
  684. self.log( "<-- "+repr(data) )
  685. return data
  686. def sendrecv( self, string ):
  687. """send data + receive data + close"""
  688. try:
  689. self.send( string )
  690. return self.recv()
  691. finally:
  692. self.close()
  693. def serve(self, handler, n=None):
  694. """open socket, wait for incoming connections and handle them.
  695. :Parameters:
  696. - n: serve n requests, None=forever
  697. """
  698. self.close()
  699. self.s = socket.socket( self.s_type, self.s_prot )
  700. try:
  701. self.log( "listen %s" % repr(self.addr) )
  702. self.s.bind( self.addr )
  703. self.s.listen(1)
  704. n_current = 0
  705. while 1:
  706. if n is not None and n_current >= n:
  707. break
  708. conn, addr = self.s.accept()
  709. self.log( "%s connected" % repr(addr) )
  710. data = self.recvAll(conn)
  711. self.log( "%s --> %s" % (repr(addr), repr(data)) )
  712. result = handler(data)
  713. if data is not None:
  714. self.log( "%s <-- %s" % (repr(addr), repr(result)) )
  715. self.sendAll(conn, result)
  716. self.log( "%s close" % repr(addr) )
  717. conn.close()
  718. n_current += 1
  719. finally:
  720. self.close()
  721. if hasattr(socket, 'AF_UNIX'):
  722. class TransportUnixSocket(TransportSocket):
  723. """Transport via Unix Domain Socket.
  724. """
  725. def __init__(self, addr=None, limit=4096, timeout=5.0, logfunc=log_dummy):
  726. """
  727. :Parameters:
  728. - addr: "socket_file"
  729. :Note: | The socket-file is not deleted.
  730. | If the socket-file begins with \x00, abstract sockets are used,
  731. and no socket-file is created.
  732. :SeeAlso: TransportSocket
  733. """
  734. TransportSocket.__init__( self, addr, limit, socket.AF_UNIX, socket.SOCK_STREAM, timeout, logfunc )
  735. class TransportTcpIp(TransportSocket):
  736. """Transport via TCP/IP.
  737. """
  738. def __init__(self, addr=None, limit=4096, timeout=5.0, logfunc=log_dummy):
  739. """
  740. :Parameters:
  741. - addr: ("host",port)
  742. :SeeAlso: TransportSocket
  743. """
  744. TransportSocket.__init__( self, addr, limit, socket.AF_INET, socket.SOCK_STREAM, timeout, logfunc )
  745. #=========================================
  746. # client side: server proxy
  747. class ServerProxy:
  748. """RPC-client: server proxy
  749. A logical connection to a RPC server.
  750. It works with different data/serializers and different transports.
  751. Notifications and id-handling/multicall are not yet implemented.
  752. :Example:
  753. see module-docstring
  754. :TODO: verbose/logging?
  755. """
  756. def __init__( self, data_serializer, transport ):
  757. """
  758. :Parameters:
  759. - data_serializer: a data_structure+serializer-instance
  760. - transport: a Transport instance
  761. """
  762. #TODO: check parameters
  763. self.__data_serializer = data_serializer
  764. if not isinstance(transport, Transport):
  765. raise ValueError('invalid "transport" (must be a Transport-instance)"')
  766. self.__transport = transport
  767. def __str__(self):
  768. return repr(self)
  769. def __repr__(self):
  770. return "<ServerProxy for %s, with serializer %s>" % (self.__transport, self.__data_serializer)
  771. def __req( self, methodname, args=None, kwargs=None, id=0 ):
  772. # JSON-RPC 1.0: only positional parameters
  773. if len(kwargs) > 0 and isinstance(self.data_serializer, JsonRpc10):
  774. raise ValueError("Only positional parameters allowed in JSON-RPC 1.0")
  775. # JSON-RPC 2.0: only args OR kwargs allowed!
  776. if len(args) > 0 and len(kwargs) > 0:
  777. raise ValueError("Only positional or named parameters are allowed!")
  778. if len(kwargs) == 0:
  779. req_str = self.__data_serializer.dumps_request( methodname, args, id )
  780. else:
  781. req_str = self.__data_serializer.dumps_request( methodname, kwargs, id )
  782. try:
  783. resp_str = self.__transport.sendrecv( req_str )
  784. except Exception,err:
  785. raise RPCTransportError(err)
  786. resp = self.__data_serializer.loads_response( resp_str )
  787. return resp[0]
  788. def __getattr__(self, name):
  789. # magic method dispatcher
  790. # note: to call a remote object with an non-standard name, use
  791. # result getattr(my_server_proxy, "strange-python-name")(args)
  792. return _method(self.__req, name)
  793. # request dispatcher
  794. class _method:
  795. """some "magic" to bind an RPC method to an RPC server.
  796. Supports "nested" methods (e.g. examples.getStateName).
  797. :Raises: AttributeError for method-names/attributes beginning with '_'.
  798. """
  799. def __init__(self, req, name):
  800. if name[0] == "_": #prevent rpc-calls for proxy._*-functions
  801. raise AttributeError("invalid attribute '%s'" % name)
  802. self.__req = req
  803. self.__name = name
  804. def __getattr__(self, name):
  805. if name[0] == "_": #prevent rpc-calls for proxy._*-functions
  806. raise AttributeError("invalid attribute '%s'" % name)
  807. return _method(self.__req, "%s.%s" % (self.__name, name))
  808. def __call__(self, *args, **kwargs):
  809. return self.__req(self.__name, args, kwargs)
  810. #=========================================
  811. # server side: Server
  812. import types
  813. class Server:
  814. """RPC-server.
  815. It works with different data/serializers and
  816. with different transports.
  817. :Example:
  818. see module-docstring
  819. :TODO:
  820. - mixed JSON-RPC 1.0/2.0 server?
  821. - logging/loglevels?
  822. """
  823. def __init__( self, data_serializer, transport, logfile=None ):
  824. """
  825. :Parameters:
  826. - data_serializer: a data_structure+serializer-instance
  827. - transport: a Transport instance
  828. - logfile: file to log ("unexpected") errors to
  829. """
  830. #TODO: check parameters
  831. self.__data_serializer = data_serializer
  832. if not isinstance(transport, Transport):
  833. raise ValueError('invalid "transport" (must be a Transport-instance)"')
  834. self.__transport = transport
  835. self.logfile = logfile
  836. if self.logfile is not None: #create logfile (or raise exception)
  837. f = codecs.open( self.logfile, 'a', encoding='utf-8' )
  838. f.close()
  839. self.funcs = {}
  840. def __repr__(self):
  841. return "<Server for %s, with serializer %s>" % (self.__transport, self.__data_serializer)
  842. def log(self, message):
  843. """write a message to the logfile (in utf-8)"""
  844. if self.logfile is not None:
  845. f = codecs.open( self.logfile, 'a', encoding='utf-8' )
  846. f.write( time.strftime("%Y-%m-%d %H:%M:%S ")+message+"\n" )
  847. f.close()
  848. def register_instance(self, myinst, name=None):
  849. """Add all functions of a class-instance to the RPC-services.
  850. All entries of the instance which do not begin with '_' are added.
  851. :Parameters:
  852. - myinst: class-instance containing the functions
  853. - name: | hierarchical prefix.
  854. | If omitted, the functions are added directly.
  855. | If given, the functions are added as "name.function".
  856. :TODO:
  857. - improve hierarchy?
  858. """
  859. for e in dir(myinst):
  860. if e[0][0] != "_":
  861. attr = getattr( myinst, e )
  862. if not isinstance( attr, types.MethodType ):
  863. continue
  864. if name is None:
  865. self.register_function( attr )
  866. else:
  867. self.register_function( attr, name="%s.%s" % (name, e) )
  868. def register_function(self, function, name=None):
  869. """Add a function to the RPC-services.
  870. :Parameters:
  871. - function: function to add
  872. - name: RPC-name for the function. If omitted/None, the original
  873. name of the function is used.
  874. """
  875. if name is None:
  876. self.funcs[function.__name__] = function
  877. else:
  878. self.funcs[name] = function
  879. def handle(self, rpcstr):
  880. """Handle a RPC-Request.
  881. :Parameters:
  882. - rpcstr: the received rpc-string
  883. :Returns: the data to send back or None if nothing should be sent back
  884. :Raises: RPCFault (and maybe others)
  885. """
  886. #TODO: id
  887. notification = False
  888. try:
  889. req = self.__data_serializer.loads_request( rpcstr )
  890. if len(req) == 2: #notification
  891. method, params = req
  892. notification = True
  893. else: #request
  894. method, params, id = req
  895. except RPCFault, err:
  896. return self.__data_serializer.dumps_error( err, id=None )
  897. except Exception, err:
  898. import traceback
  899. print traceback.format_exc()
  900. self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], repr(err)) )
  901. return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id=None )
  902. if method not in self.funcs:
  903. if notification:
  904. return None
  905. return self.__data_serializer.dumps_error( RPCFault(METHOD_NOT_FOUND, ERROR_MESSAGE[METHOD_NOT_FOUND]), id )
  906. try:
  907. if isinstance(params, dict):
  908. result = self.funcs[method]( **params )
  909. else:
  910. result = self.funcs[method]( *params )
  911. except RPCFault, err:
  912. if notification:
  913. return None
  914. return self.__data_serializer.dumps_error( err, id=None )
  915. except Exception, err:
  916. if notification:
  917. return None
  918. import traceback
  919. print traceback.format_exc()
  920. self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], repr(err)) )
  921. self.log(traceback.format_exc())
  922. return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id )
  923. print "Serializing response"
  924. if notification:
  925. return None
  926. try:
  927. return self.__data_serializer.dumps_response( result, id )
  928. except Exception, err:
  929. self.log( "%d (%s): %s" % (INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR], repr(err)) )
  930. return self.__data_serializer.dumps_error( RPCFault(INTERNAL_ERROR, ERROR_MESSAGE[INTERNAL_ERROR]), id )
  931. def serve(self, n=None):
  932. """serve (forever or for n communicaions).
  933. :See: Transport
  934. """
  935. self.__transport.serve( self.handle, n )
  936. #=========================================