authproxy.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """
  2. Copyright 2011 Jeff Garzik
  3. AuthServiceProxy has the following improvements over python-jsonrpc's
  4. ServiceProxy class:
  5. - HTTP connections persist for the life of the AuthServiceProxy object
  6. (if server supports HTTP/1.1)
  7. - sends protocol 'version', per JSON-RPC 1.1
  8. - sends proper, incrementing 'id'
  9. - sends Basic HTTP authentication headers
  10. - parses all JSON numbers that look like floats as Decimal
  11. - uses standard Python json lib
  12. Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
  13. Copyright (c) 2007 Jan-Klaas Kollhof
  14. This file is part of jsonrpc.
  15. jsonrpc is free software; you can redistribute it and/or modify
  16. it under the terms of the GNU Lesser General Public License as published by
  17. the Free Software Foundation; either version 2.1 of the License, or
  18. (at your option) any later version.
  19. This software is distributed in the hope that it will be useful,
  20. but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. GNU Lesser General Public License for more details.
  23. You should have received a copy of the GNU Lesser General Public License
  24. along with this software; if not, write to the Free Software
  25. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  26. """
  27. try:
  28. import http.client as httplib
  29. except ImportError:
  30. import httplib
  31. import base64
  32. import decimal
  33. import json
  34. import logging
  35. try:
  36. import urllib.parse as urlparse
  37. except ImportError:
  38. import urlparse
  39. USER_AGENT = "AuthServiceProxy/0.1"
  40. HTTP_TIMEOUT = 30
  41. log = logging.getLogger("BitcoinRPC")
  42. class JSONRPCException(Exception):
  43. def __init__(self, rpc_error):
  44. parent_args = []
  45. try:
  46. parent_args.append(rpc_error['message'])
  47. except:
  48. pass
  49. Exception.__init__(self, *parent_args)
  50. self.error = rpc_error
  51. self.code = rpc_error['code'] if 'code' in rpc_error else None
  52. self.message = rpc_error['message'] if 'message' in rpc_error else None
  53. def __str__(self):
  54. return '%d: %s' % (self.code, self.message)
  55. def __repr__(self):
  56. return '<%s \'%s\'>' % (self.__class__.__name__, self)
  57. def EncodeDecimal(o):
  58. if isinstance(o, decimal.Decimal):
  59. return float(round(o, 8))
  60. raise TypeError(repr(o) + " is not JSON serializable")
  61. class AuthServiceProxy(object):
  62. __id_count = 0
  63. def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
  64. self.__service_url = service_url
  65. self.__service_name = service_name
  66. self.__url = urlparse.urlparse(service_url)
  67. if self.__url.port is None:
  68. port = 80
  69. else:
  70. port = self.__url.port
  71. (user, passwd) = (self.__url.username, self.__url.password)
  72. try:
  73. user = user.encode('utf8')
  74. except AttributeError:
  75. pass
  76. try:
  77. passwd = passwd.encode('utf8')
  78. except AttributeError:
  79. pass
  80. authpair = user + b':' + passwd
  81. self.__auth_header = b'Basic ' + base64.b64encode(authpair)
  82. self.__timeout = timeout
  83. if connection:
  84. # Callables re-use the connection of the original proxy
  85. self.__conn = connection
  86. elif self.__url.scheme == 'https':
  87. self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
  88. timeout=timeout)
  89. else:
  90. self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
  91. timeout=timeout)
  92. def __getattr__(self, name):
  93. if name.startswith('__') and name.endswith('__'):
  94. # Python internal stuff
  95. raise AttributeError
  96. if self.__service_name is not None:
  97. name = "%s.%s" % (self.__service_name, name)
  98. return AuthServiceProxy(self.__service_url, name, self.__timeout, self.__conn)
  99. def __call__(self, *args):
  100. AuthServiceProxy.__id_count += 1
  101. log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
  102. json.dumps(args, default=EncodeDecimal)))
  103. postdata = json.dumps({'version': '1.1',
  104. 'method': self.__service_name,
  105. 'params': args,
  106. 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
  107. self.__conn.request('POST', self.__url.path, postdata,
  108. {'Host': self.__url.hostname,
  109. 'User-Agent': USER_AGENT,
  110. 'Authorization': self.__auth_header,
  111. 'Content-type': 'application/json'})
  112. self.__conn.sock.settimeout(self.__timeout)
  113. response = self._get_response()
  114. if response.get('error') is not None:
  115. raise JSONRPCException(response['error'])
  116. elif 'result' not in response:
  117. raise JSONRPCException({
  118. 'code': -343, 'message': 'missing JSON-RPC result'})
  119. return response['result']
  120. def batch_(self, rpc_calls):
  121. """Batch RPC call.
  122. Pass array of arrays: [ [ "method", params... ], ... ]
  123. Returns array of results.
  124. """
  125. batch_data = []
  126. for rpc_call in rpc_calls:
  127. AuthServiceProxy.__id_count += 1
  128. m = rpc_call.pop(0)
  129. batch_data.append({"jsonrpc":"2.0", "method":m, "params":rpc_call, "id":AuthServiceProxy.__id_count})
  130. postdata = json.dumps(batch_data, default=EncodeDecimal)
  131. log.debug("--> "+postdata)
  132. self.__conn.request('POST', self.__url.path, postdata,
  133. {'Host': self.__url.hostname,
  134. 'User-Agent': USER_AGENT,
  135. 'Authorization': self.__auth_header,
  136. 'Content-type': 'application/json'})
  137. results = []
  138. responses = self._get_response()
  139. for response in responses:
  140. if response['error'] is not None:
  141. raise JSONRPCException(response['error'])
  142. elif 'result' not in response:
  143. raise JSONRPCException({
  144. 'code': -343, 'message': 'missing JSON-RPC result'})
  145. else:
  146. results.append(response['result'])
  147. return results
  148. def _get_response(self):
  149. http_response = self.__conn.getresponse()
  150. if http_response is None:
  151. raise JSONRPCException({
  152. 'code': -342, 'message': 'missing HTTP response from server'})
  153. responsedata = http_response.read().decode('utf8')
  154. response = json.loads(responsedata, parse_float=decimal.Decimal)
  155. if "error" in response and response["error"] is None:
  156. log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
  157. else:
  158. log.debug("<-- "+responsedata)
  159. return response