3
Copyright 2011 Jeff Garzik
5
AuthServiceProxy has the following improvements over python-jsonrpc's
8
- HTTP connections persist for the life of the AuthServiceProxy object
9
(if server supports HTTP/1.1)
10
- sends protocol 'version', per JSON-RPC 1.1
11
- sends proper, incrementing 'id'
12
- sends Basic HTTP authentication headers
13
- parses all JSON numbers that look like floats as Decimal
14
- uses standard Python json lib
16
Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
18
Copyright (c) 2007 Jan-Klaas Kollhof
20
This file is part of jsonrpc.
22
jsonrpc is free software; you can redistribute it and/or modify
23
it under the terms of the GNU Lesser General Public License as published by
24
the Free Software Foundation; either version 2.1 of the License, or
25
(at your option) any later version.
27
This software is distributed in the hope that it will be useful,
28
but WITHOUT ANY WARRANTY; without even the implied warranty of
29
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30
GNU Lesser General Public License for more details.
32
You should have received a copy of the GNU Lesser General Public License
33
along with this software; if not, write to the Free Software
34
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
38
import http.client as httplib
46
import urllib.parse as urlparse
50
USER_AGENT = "AuthServiceProxy/0.1"
54
log = logging.getLogger("BitcoinRPC")
56
class JSONRPCException(Exception):
57
def __init__(self, rpc_error):
58
Exception.__init__(self)
59
self.error = rpc_error
63
if isinstance(o, decimal.Decimal):
65
raise TypeError(repr(o) + " is not JSON serializable")
67
class AuthServiceProxy(object):
70
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
71
self.__service_url = service_url
72
self.__service_name = service_name
73
self.__url = urlparse.urlparse(service_url)
74
if self.__url.port is None:
77
port = self.__url.port
78
(user, passwd) = (self.__url.username, self.__url.password)
80
user = user.encode('utf8')
81
except AttributeError:
84
passwd = passwd.encode('utf8')
85
except AttributeError:
87
authpair = user + b':' + passwd
88
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
91
# Callables re-use the connection of the original proxy
92
self.__conn = connection
93
elif self.__url.scheme == 'https':
94
self.__conn = httplib.HTTPSConnection(self.__url.hostname, port,
98
self.__conn = httplib.HTTPConnection(self.__url.hostname, port,
101
def __getattr__(self, name):
102
if name.startswith('__') and name.endswith('__'):
103
# Python internal stuff
105
if self.__service_name is not None:
106
name = "%s.%s" % (self.__service_name, name)
107
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
109
def __call__(self, *args):
110
AuthServiceProxy.__id_count += 1
112
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
113
json.dumps(args, default=EncodeDecimal)))
114
postdata = json.dumps({'version': '1.1',
115
'method': self.__service_name,
117
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
118
self.__conn.request('POST', self.__url.path, postdata,
119
{'Host': self.__url.hostname,
120
'User-Agent': USER_AGENT,
121
'Authorization': self.__auth_header,
122
'Content-type': 'application/json'})
124
response = self._get_response()
125
if response['error'] is not None:
126
raise JSONRPCException(response['error'])
127
elif 'result' not in response:
128
raise JSONRPCException({
129
'code': -343, 'message': 'missing JSON-RPC result'})
131
return response['result']
133
def _batch(self, rpc_call_list):
134
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal)
135
log.debug("--> "+postdata)
136
self.__conn.request('POST', self.__url.path, postdata,
137
{'Host': self.__url.hostname,
138
'User-Agent': USER_AGENT,
139
'Authorization': self.__auth_header,
140
'Content-type': 'application/json'})
142
return self._get_response()
144
def _get_response(self):
145
http_response = self.__conn.getresponse()
146
if http_response is None:
147
raise JSONRPCException({
148
'code': -342, 'message': 'missing HTTP response from server'})
150
responsedata = http_response.read().decode('utf8')
151
response = json.loads(responsedata, parse_float=decimal.Decimal)
152
if "error" in response and response["error"] is None:
153
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
155
log.debug("<-- "+responsedata)