Commit c3a0f72d authored by Samuel GAIST's avatar Samuel GAIST
Browse files

[webapi] Refactor to use requests

Rather than doing things manually, rely on a known
and proven library to do the web requests.

Also, fail directly in this central point. There's
no sense in propagating returning invalid values and
status that must be checked again at every call.
parent 4ef644a3
......@@ -34,158 +34,86 @@
###################################################################################
import socket
import traceback
import requests
import simplejson as json
import six
import simplejson
import logging
logger = logging.getLogger(__name__)
from urllib.parse import urlparse
class WebAPI(object):
"""Manages all the interactions with the Web API
"""
def __init__(self, platform, user, token):
self.platform = platform
self.parsed = six.moves.urllib.parse.urlparse(self.platform)
self.user = user
self.token = token
if self.token is None: self.user = '**anonymous**'
self.connection = None
self.users = 0 #usage counter for the current connection
def connect(self):
"""Establish a connection with the Web API
"""
if self.connection is not None:
self.users += 1
return
if not self.platform:
logger.error("No `platform' set up. Set it with " \
"`beat config platform <url>'")
return
port = self.parsed.port
if port is None:
if self.parsed.scheme.lower() == 'https': port = 443
else: port = 80
connector = six.moves.http_client.HTTPConnection
if self.parsed.scheme.lower() == 'https':
connector = six.moves.http_client.HTTPSConnection
self.connection = connector(self.parsed.hostname, port)
try:
self.connection.connect()
except socket.error as ex:
logger.error('Failed to establish a connection with the Web API, ' \
'reason: %s', ex.strerror)
self.connection = None
except:
logger.error('Failed to establish a connection with the Web API, ' \
'reason: %s', traceback.format_exc())
self.connection = None
self.users += 1 #this should be the first user
def is_anonymous(self):
"""Tells if the user has not set credentials for accessing the platform"""
return self.token is None
def disconnect(self):
"""Disconnects from the remote server"""
self.users -= 1
if self.users <= 0 and self.connected:
self.connection.close()
self.connection = None
@property
def connected(self):
"""``True`` if it is connected to the Web-API. ``False`` otherwise"""
return self.connection is not None
def __enter__(self):
self.connect()
return self
def __exit__(self, *exc):
self.disconnect()
"""Central class for all remote service related calls"""
API_VERSION = "v1"
API_PATH = "/api/{}".format(API_VERSION)
def _make_path(self, path):
def __init__(self, platform, user, token=None):
self.platform = platform
self.parsed = urlparse(self.platform)
self.user = "**anonymous**" if token is None else user
self.token = token
url = self.parsed.path
if url != '':
if url[0] != '/': url = '/' + url
if url[-1] == '/': url = url[:-1]
if path[0] == '/': path = path[1:]
return url + '/' + path
def is_anonymous(self):
return self.token is None
def __enter__(self):
return self
def _make_headers(self):
"""Return the headers
"""
def __exit__(self, *exc):
return
if self.token is None: return {}
def _make_headers(self):
if self.token is None:
return None
return {
'Authorization': 'Token %s' % (self.token),
}
return {
"Authorization": "Token %s" % (self.token),
"Content-Type": "application/json",
}
def __build_url(self, path):
url = "{schema}://{host}{path}".format(
schema=self.parsed[0], host=self.parsed[1], path=path
)
return url
def _message(self, type, url, data=None):
"""Sends a message to the Web-API"""
def get(self, path):
url = self.__build_url(path)
answer = requests.get(url, headers=self._make_headers())
# Establish the connection
if not self.connected: return (None, None)
# if answer.status_code < 200 or answer.status_code >= 300:
if answer.status_code not in [200, 204]:
raise RuntimeError("GET error %i : %s" % (answer.status_code, url))
# Setup the request
headers = self._make_headers()
return answer.json()
if data is not None:
data = simplejson.dumps(data)
headers['Content-Type'] = 'application/json'
headers['Content-Length'] = len(data)
def post(self, path, data=None):
url = self.__build_url(path)
answer = requests.post(url, json=data, headers=self._make_headers())
# Send the request
try:
url = self._make_path(url)
logger.debug("[%s] `%s://%s%s' with secret token...",
type.upper(), self.parsed.scheme, self.parsed.hostname, url)
self.connection.request(type, url, data, headers)
response = self.connection.getresponse()
logger.debug("response status code is `%d': %s", response.status,
response.reason)
except:
logger.error("Failed to use the web API, reason: %s",
traceback.format_exc())
self.disconnect()
return (None, None)
if answer.status_code not in [200, 201]:
raise RuntimeError("POST error %i : %s" % (answer.status_code, url))
return (response.status, response.read())
try:
return answer.json()
except json.JSONDecodeError:
return answer.text
def put(self, path, data=None):
url = self.__build_url(path)
answer = requests.put(url, json=data, headers=self._make_headers())
def get(self, url):
return self._message('GET', url)
if answer.status_code not in [200, 204]:
raise RuntimeError("PUT error %i : %s" % (answer.status_code, url))
def post(self, url, data=None):
return self._message('POST', url, data)
try:
return answer.json()
except json.JSONDecodeError:
return answer.text
def put(self, url, data):
return self._message('PUT', url, data)
def delete(self, path):
url = self.__build_url(path)
answer = requests.delete(url, headers=self._make_headers())
def delete(self, url):
return self._message('DELETE', url)
# Should respond a 204 status and an empty body
if answer.status_code not in [200, 204]:
raise RuntimeError("DELETE error %i : %s" % (answer.status_code, url))
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment