You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

authproxy.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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. Exception.__init__(self)
  45. self.error = rpc_error
  46. def EncodeDecimal(o):
  47. if isinstance(o, decimal.Decimal):
  48. return round(o, 8)
  49. raise TypeError(repr(o) + " is not JSON serializable")
  50. class AuthServiceProxy(object):
  51. __id_count = 0
  52. def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
  53. self.__service_url = service_url
  54. self._service_name = service_name
  55. self.__url = urlparse.urlparse(service_url)
  56. if self.__url.port is None:
  57. port = 80
  58. else:
  59. port = self.__url.port
  60. (user, passwd) = (self.__url.username, self.__url.password)
  61. try:
  62. user = user.encode('utf8')
  63. except AttributeError:
  64. pass
  65. try:
  66. passwd = passwd.encode('utf8')
  67. except AttributeError:
  68. pass
  69. authpair = user + b':' + passwd
  70. self.__auth_header = b'Basic ' + base64.b64encode(authpair)
  71. if connection:
  72. # Callables re-use the connection of the original proxy
  73. self.__conn = connection
  74. elif self.__url.scheme == 'https':
  75. self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
  76. None, None, False,
  77. timeout)
  78. else:
  79. self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
  80. False, timeout)
  81. def __getattr__(self, name):
  82. if name.startswith('__') and name.endswith('__'):
  83. # Python internal stuff
  84. raise AttributeError
  85. if self._service_name is not None:
  86. name = "%s.%s" % (self._service_name, name)
  87. return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
  88. def _request(self, method, path, postdata):
  89. '''
  90. Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
  91. This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
  92. '''
  93. headers = {'Host': self.__url.hostname,
  94. 'User-Agent': USER_AGENT,
  95. 'Authorization': self.__auth_header,
  96. 'Content-type': 'application/json'}
  97. try:
  98. self.__conn.request(method, path, postdata, headers)
  99. return self._get_response()
  100. except httplib.BadStatusLine as e:
  101. if e.line == "''": # if connection was closed, try again
  102. self.__conn.close()
  103. self.__conn.request(method, path, postdata, headers)
  104. return self._get_response()
  105. else:
  106. raise
  107. def __call__(self, *args):
  108. AuthServiceProxy.__id_count += 1
  109. log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
  110. json.dumps(args, default=EncodeDecimal)))
  111. postdata = json.dumps({'version': '1.1',
  112. 'method': self._service_name,
  113. 'params': args,
  114. 'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
  115. response = self._request('POST', self.__url.path, postdata)
  116. if response['error'] is not None:
  117. raise JSONRPCException(response['error'])
  118. elif 'result' not in response:
  119. raise JSONRPCException({
  120. 'code': -343, 'message': 'missing JSON-RPC result'})
  121. else:
  122. return response['result']
  123. def _batch(self, rpc_call_list):
  124. postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal)
  125. log.debug("--> "+postdata)
  126. return self._request('POST', self.__url.path, postdata)
  127. def _get_response(self):
  128. http_response = self.__conn.getresponse()
  129. if http_response is None:
  130. raise JSONRPCException({
  131. 'code': -342, 'message': 'missing HTTP response from server'})
  132. responsedata = http_response.read().decode('utf8')
  133. response = json.loads(responsedata, parse_float=decimal.Decimal)
  134. if "error" in response and response["error"] is None:
  135. log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
  136. else:
  137. log.debug("<-- "+responsedata)
  138. return response