github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/cli/sawtooth_cli/rest_client.py (about) 1 # Copyright 2016 Intel Corporation 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # ------------------------------------------------------------------------------ 15 16 import json 17 from base64 import b64encode 18 from http.client import RemoteDisconnected 19 import requests 20 # pylint: disable=no-name-in-module,import-error 21 # needed for the google.protobuf imports to pass pylint 22 from google.protobuf.message import Message as BaseMessage 23 24 from sawtooth_cli.exceptions import CliException 25 26 27 class RestClient(object): 28 def __init__(self, base_url=None, user=None): 29 self._base_url = base_url or 'http://localhost:8008' 30 31 if user: 32 b64_string = b64encode(user.encode()).decode() 33 self._auth_header = 'Basic {}'.format(b64_string) 34 else: 35 self._auth_header = None 36 37 def list_blocks(self, limit=None): 38 """Return a block generator. 39 40 Args: 41 limit (int): The page size of requests 42 """ 43 return self._get_data('/blocks', limit=limit) 44 45 def get_block(self, block_id): 46 return self._get('/blocks/' + block_id)['data'] 47 48 def list_batches(self): 49 return self._get_data('/batches') 50 51 def get_batch(self, batch_id): 52 return self._get('/batches/' + batch_id)['data'] 53 54 def list_peers(self): 55 return self._get('/peers')['data'] 56 57 def get_status(self): 58 return self._get('/status')['data'] 59 60 def list_transactions(self): 61 return self._get_data('/transactions') 62 63 def get_transaction(self, transaction_id): 64 return self._get('/transactions/' + transaction_id)['data'] 65 66 def list_state(self, subtree=None, head=None): 67 return self._get('/state', address=subtree, head=head) 68 69 def get_leaf(self, address, head=None): 70 return self._get('/state/' + address, head=head) 71 72 def get_statuses(self, batch_ids, wait=None): 73 """Fetches the committed status for a list of batch ids. 74 75 Args: 76 batch_ids (list of str): The ids to get the status of. 77 wait (optional, int): Indicates that the api should wait to 78 respond until the batches are committed or the specified 79 time in seconds has elapsed. 80 81 Returns: 82 list of dict: Dicts with 'id' and 'status' properties 83 """ 84 return self._post('/batch_statuses', batch_ids, wait=wait)['data'] 85 86 def send_batches(self, batch_list): 87 """Sends a list of batches to the validator. 88 89 Args: 90 batch_list (:obj:`BatchList`): the list of batches 91 92 Returns: 93 dict: the json result data, as a dict 94 """ 95 if isinstance(batch_list, BaseMessage): 96 batch_list = batch_list.SerializeToString() 97 98 return self._post('/batches', batch_list) 99 100 def _get(self, path, **queries): 101 code, json_result = self._submit_request( 102 self._base_url + path, 103 params=self._format_queries(queries), 104 ) 105 106 # concat any additional pages of data 107 while code == 200 and 'next' in json_result.get('paging', {}): 108 previous_data = json_result.get('data', []) 109 code, json_result = self._submit_request( 110 json_result['paging']['next']) 111 json_result['data'] = previous_data + json_result.get('data', []) 112 113 if code == 200: 114 return json_result 115 elif code == 404: 116 raise CliException( 117 '{}: There is no resource with the identifier "{}"'.format( 118 self._base_url, path.split('/')[-1])) 119 else: 120 raise CliException( 121 "{}: {} {}".format(self._base_url, code, json_result)) 122 123 def _get_data(self, path, **queries): 124 url = self._base_url + path 125 params = self._format_queries(queries) 126 127 while url: 128 code, json_result = self._submit_request( 129 url, 130 params=params, 131 ) 132 133 if code == 404: 134 raise CliException( 135 '{}: There is no resource with the identifier "{}"'.format( 136 self._base_url, path.split('/')[-1])) 137 elif code != 200: 138 raise CliException( 139 "{}: {} {}".format(self._base_url, code, json_result)) 140 141 for item in json_result.get('data', []): 142 yield item 143 144 url = json_result['paging'].get('next', None) 145 146 def _post(self, path, data, **queries): 147 if isinstance(data, bytes): 148 headers = {'Content-Type': 'application/octet-stream'} 149 else: 150 data = json.dumps(data).encode() 151 headers = {'Content-Type': 'application/json'} 152 headers['Content-Length'] = '%d' % len(data) 153 154 code, json_result = self._submit_request( 155 self._base_url + path, 156 params=self._format_queries(queries), 157 data=data, 158 headers=headers, 159 method='POST') 160 161 if code == 200 or code == 201 or code == 202: 162 return json_result 163 else: 164 raise CliException("({}): {}".format(code, json_result)) 165 166 def _submit_request(self, url, params=None, data=None, headers=None, 167 method="GET"): 168 """Submits the given request, and handles the errors appropriately. 169 170 Args: 171 url (str): the request to send. 172 params (dict): params to be passed along to get/post 173 data (bytes): the data to include in the request. 174 headers (dict): the headers to include in the request. 175 method (str): the method to use for the request, "POST" or "GET". 176 177 Returns: 178 tuple of (int, str): The response status code and the json parsed 179 body, or the error message. 180 181 Raises: 182 `CliException`: If any issues occur with the URL. 183 """ 184 if headers is None: 185 headers = {} 186 187 if self._auth_header is not None: 188 headers['Authorization'] = self._auth_header 189 190 try: 191 if method == 'POST': 192 result = requests.post( 193 url, params=params, data=data, headers=headers) 194 elif method == 'GET': 195 result = requests.get( 196 url, params=params, data=data, headers=headers) 197 result.raise_for_status() 198 return (result.status_code, result.json()) 199 except requests.exceptions.HTTPError as e: 200 return (e.response.status_code, e.response.reason) 201 except RemoteDisconnected as e: 202 raise CliException(e) 203 except (requests.exceptions.MissingSchema, 204 requests.exceptions.InvalidURL) as e: 205 raise CliException(e) 206 except requests.exceptions.ConnectionError as e: 207 raise CliException( 208 ('Unable to connect to "{}": ' 209 'make sure URL is correct').format(self._base_url)) 210 211 @staticmethod 212 def _format_queries(queries): 213 queries = {k: v for k, v in queries.items() if v is not None} 214 return queries if queries else ''