PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/tests/manual/python/jsonrpc.py

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