Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/authproxy.py
Show All 30 Lines | |||||
- sends proper, incrementing 'id' | - sends proper, incrementing 'id' | ||||
- sends Basic HTTP authentication headers | - sends Basic HTTP authentication headers | ||||
- parses all JSON numbers that look like floats as Decimal | - parses all JSON numbers that look like floats as Decimal | ||||
- uses standard Python json lib | - uses standard Python json lib | ||||
""" | """ | ||||
import base64 | import base64 | ||||
import decimal | import decimal | ||||
from http import HTTPStatus | |||||
import http.client | import http.client | ||||
import json | import json | ||||
import logging | import logging | ||||
import os | import os | ||||
import socket | import socket | ||||
import time | import time | ||||
import urllib.parse | import urllib.parse | ||||
HTTP_TIMEOUT = 30 | HTTP_TIMEOUT = 30 | ||||
USER_AGENT = "AuthServiceProxy/0.1" | USER_AGENT = "AuthServiceProxy/0.1" | ||||
log = logging.getLogger("BitcoinRPC") | log = logging.getLogger("BitcoinRPC") | ||||
class JSONRPCException(Exception): | class JSONRPCException(Exception): | ||||
def __init__(self, rpc_error): | def __init__(self, rpc_error, http_status=None): | ||||
try: | try: | ||||
errmsg = '{} ({})'.format(rpc_error['message'], rpc_error['code']) | errmsg = '{} ({})'.format(rpc_error['message'], rpc_error['code']) | ||||
except (KeyError, TypeError): | except (KeyError, TypeError): | ||||
errmsg = '' | errmsg = '' | ||||
super().__init__(errmsg) | super().__init__(errmsg) | ||||
self.error = rpc_error | self.error = rpc_error | ||||
self.http_status = http_status | |||||
def EncodeDecimal(o): | def EncodeDecimal(o): | ||||
if isinstance(o, decimal.Decimal): | if isinstance(o, decimal.Decimal): | ||||
return str(o) | return str(o) | ||||
raise TypeError(repr(o) + " is not JSON serializable") | raise TypeError(repr(o) + " is not JSON serializable") | ||||
▲ Show 20 Lines • Show All 72 Lines • ▼ Show 20 Lines | def get_request(self, *args, **argsn): | ||||
return {'version': '1.1', | return {'version': '1.1', | ||||
'method': self._service_name, | 'method': self._service_name, | ||||
'params': args or argsn, | 'params': args or argsn, | ||||
'id': AuthServiceProxy.__id_count} | 'id': AuthServiceProxy.__id_count} | ||||
def __call__(self, *args, **argsn): | def __call__(self, *args, **argsn): | ||||
postdata = json.dumps(self.get_request( | postdata = json.dumps(self.get_request( | ||||
*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) | *args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) | ||||
response = self._request( | response, status = self._request( | ||||
'POST', self.__url.path, postdata.encode('utf-8')) | 'POST', self.__url.path, postdata.encode('utf-8')) | ||||
if response['error'] is not None: | if response['error'] is not None: | ||||
raise JSONRPCException(response['error']) | raise JSONRPCException(response['error'], status) | ||||
elif 'result' not in response: | elif 'result' not in response: | ||||
raise JSONRPCException({ | raise JSONRPCException({ | ||||
'code': -343, 'message': 'missing JSON-RPC result'}) | 'code': -343, 'message': 'missing JSON-RPC result'}, status) | ||||
elif status != HTTPStatus.OK: | |||||
raise JSONRPCException({ | |||||
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status) | |||||
else: | else: | ||||
return response['result'] | return response['result'] | ||||
def batch(self, rpc_call_list): | def batch(self, rpc_call_list): | ||||
postdata = json.dumps( | postdata = json.dumps( | ||||
list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) | list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) | ||||
log.debug("--> " + postdata) | log.debug("--> " + postdata) | ||||
return self._request('POST', self.__url.path, postdata.encode('utf-8')) | response, status = self._request( | ||||
'POST', self.__url.path, postdata.encode('utf-8')) | |||||
if status != HTTPStatus.OK: | |||||
raise JSONRPCException({ | |||||
'code': -342, 'message': 'non-200 HTTP status code but no JSON-RPC error'}, status) | |||||
return response | |||||
def _get_response(self): | def _get_response(self): | ||||
req_start_time = time.time() | req_start_time = time.time() | ||||
try: | try: | ||||
http_response = self.__conn.getresponse() | http_response = self.__conn.getresponse() | ||||
except socket.timeout: | except socket.timeout: | ||||
raise JSONRPCException({ | raise JSONRPCException({ | ||||
'code': -344, | 'code': -344, | ||||
'message': '{!r} RPC took longer than {} seconds. Consider ' | 'message': '{!r} RPC took longer than {} seconds. Consider ' | ||||
'using larger timeout for calls that take ' | 'using larger timeout for calls that take ' | ||||
'longer to return.'.format(self._service_name, | 'longer to return.'.format(self._service_name, | ||||
self.__conn.timeout)}) | self.__conn.timeout)}) | ||||
if http_response is None: | if http_response is None: | ||||
raise JSONRPCException({ | raise JSONRPCException({ | ||||
'code': -342, 'message': 'missing HTTP response from server'}) | 'code': -342, 'message': 'missing HTTP response from server'}) | ||||
content_type = http_response.getheader('Content-Type') | content_type = http_response.getheader('Content-Type') | ||||
if content_type != 'application/json': | if content_type != 'application/json': | ||||
raise JSONRPCException({ | raise JSONRPCException( | ||||
'code': -342, 'message': 'non-JSON HTTP response with \'{} {}\' from server'.format( | {'code': -342, | ||||
http_response.status, http_response.reason)}) | 'message': 'non-JSON HTTP response with \'{} {}\' from server'.format( | ||||
http_response.status, http_response.reason)}, | |||||
http_response.status) | |||||
responsedata = http_response.read().decode('utf8') | responsedata = http_response.read().decode('utf8') | ||||
response = json.loads(responsedata, parse_float=decimal.Decimal) | response = json.loads(responsedata, parse_float=decimal.Decimal) | ||||
elapsed = time.time() - req_start_time | elapsed = time.time() - req_start_time | ||||
if "error" in response and response["error"] is None: | if "error" in response and response["error"] is None: | ||||
log.debug("<-{}- [{:.6f}] {}".format(response["id"], elapsed, json.dumps( | log.debug("<-{}- [{:.6f}] {}".format(response["id"], elapsed, json.dumps( | ||||
response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) | response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) | ||||
else: | else: | ||||
log.debug("<-- [{:.6f}] {}".format(elapsed, responsedata)) | log.debug("<-- [{:.6f}] {}".format(elapsed, responsedata)) | ||||
return response | return response, http_response.status | ||||
def __truediv__(self, relative_uri): | def __truediv__(self, relative_uri): | ||||
return AuthServiceProxy("{}/{}".format(self.__service_url, | return AuthServiceProxy("{}/{}".format(self.__service_url, | ||||
relative_uri), self._service_name, connection=self.__conn) | relative_uri), self._service_name, connection=self.__conn) | ||||
def _set_conn(self, connection=None): | def _set_conn(self, connection=None): | ||||
port = 80 if self.__url.port is None else self.__url.port | port = 80 if self.__url.port is None else self.__url.port | ||||
if connection: | if connection: | ||||
self.__conn = connection | self.__conn = connection | ||||
self.timeout = connection.timeout | self.timeout = connection.timeout | ||||
elif self.__url.scheme == 'https': | elif self.__url.scheme == 'https': | ||||
self.__conn = http.client.HTTPSConnection( | self.__conn = http.client.HTTPSConnection( | ||||
self.__url.hostname, port, timeout=self.timeout) | self.__url.hostname, port, timeout=self.timeout) | ||||
else: | else: | ||||
self.__conn = http.client.HTTPConnection( | self.__conn = http.client.HTTPConnection( | ||||
self.__url.hostname, port, timeout=self.timeout) | self.__url.hostname, port, timeout=self.timeout) |