Files
OpenMTC/build/lib/futile/net/http/client/RestClientAsync.py
Christian Klopp e9af73dfe8 initial commit
2017-11-07 14:41:38 +01:00

328 lines
8.8 KiB
Python

'''
Created on 21.05.2011
@author: kca
'''
from base64 import b64encode
from cStringIO import StringIO
from logging import DEBUG
from socket import getservbyname
from urllib2 import quote
from urlparse import urlparse
#import vertx
from aplus import Promise
from futile import ObjectProxy
from futile.logging import LoggerMixin
from futile.net.http.exc import NetworkError, HTTPError
def compose_qs(values):
return "&".join([ "%s=%s" % (quote(k), quote(v)) for k, v in dict(values).iteritems() ])
class LoggingResponseWrapper(LoggerMixin, ObjectProxy):
def __init__(self, response, *args, **kw):
super(LoggingResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__buffer = StringIO()
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
self.logger.debug("Read data:\n %s", self.__buffer.getvalue())
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
class CachingHttplibResponseWrapper(ObjectProxy, LoggerMixin):
def __init__(self, response, path, tag, last_modified, cache, *args, **kw):
super(CachingHttplibResponseWrapper, self).__init__(proxyobject = response, *args, **kw)
self.__cache = cache
self.__buffer = StringIO()
self.__path = path
self.__tag = tag
self.__last_modified = last_modified
self.__finalized = False
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __enter__(self):
return self
def read(self, n = None):
s = self._o.read(n)
self.__buffer.write(s)
return s
def readline(self):
s = self._o.readline()
self.__buffer.write(s)
return s
def readlines(self, sizehint = None):
lines = self._o.readlines(sizehint)
self.__buffer.write(''.join(lines))
return lines
def close(self):
if self.__finalized:
self.logger.debug("%s is already finalized" % (self, ))
return
self.__finalized = True
try:
if not self._o.isclosed():
self.__buffer.write(self._o.read())
val = self.__buffer.getvalue()
self.logger.debug("Putting to cache: %s -> %s, %s\n %s", self.__path, self.__tag, self.__last_modified, val)
self.__cache[self.__path] = (self.__tag, self.__last_modified, val)
except:
self.logger.exception("Finalizing response failed")
finally:
self._o.close()
self.__buffer.close()
def __getattr__(self, name):
return getattr(self._o, name)
class closing(ObjectProxy):
def __getattr__(self, k):
return getattr(self._o, k)
def __enter__(self):
return self._o
def __exit__(self, exc_type, exc_val, exc_tb):
self._o.close()
def close(self):
self._o.close()
class RestClient(LoggerMixin):
ERROR_RESPONSE_MAX = 320
get_timeout = timeout = 120.0
def __init__(self, uri, username=None, password=None, certfile=None,
keyfile=None, content_type="text/plain", headers=None,
cache=True, timeout=None, get_timeout=None,
component_name = "server", connection_manager = None,
*args, **kw):
super(RestClient, self).__init__(*args, **kw)
self.logger.debug("Creating RestClient for %s", uri)
self.timeout = timeout or self.timeout
self.get_timeout = get_timeout or timeout or self.get_timeout
if cache:
if cache is True:
from futile.caching import LRUCache
cache = LRUCache()
self.__cache = cache
if "://" not in uri:
uri = "http://" + uri
self.__content_type = content_type
self.component_name = component_name
info = urlparse(uri)
if info.scheme == "https":
if bool(certfile) ^ bool(keyfile):
raise ValueError("Must give both certfile and keyfile if any")
if certfile:
from os.path import exists
if not exists(certfile):
raise ValueError("Certificate file not found: %s" % (certfile, ))
if not exists(keyfile):
raise ValueError("Key file not found: %s" % (keyfile, ))
elif info.scheme != "http":
raise ValueError(info.scheme)
port = info.port and int(info.port) or getservbyname(info.scheme)
self.__base = info.path or ""
#if not self.__base.endswith("/"):
# self.__base += "/"
if not username:
username = info.username
if not headers:
headers = {}
headers.setdefault("Accept", "*/*")
headers["Accept-Encoding"] = "identity"
if username:
password = password or info.password or ""
headers["Authorization"] = "Basic " + b64encode("%s:%s" % (username, password))
self.__headers = headers
#if not connection_manager:
# #from SimpleConnectionManager import SimpleConnectionManager as connection_manager
# from ConnectionPoolManager import ConnectionPoolManager as connection_manager
#
# self.__connection_manager = connection_manager(host = info.hostname, port = port,
# certfile = certfile, keyfile = keyfile, force_ssl = info.scheme == "https")
#
self.client= vertx.create_http_client()
self.client.host = info.netloc.split(":")[0]
self.client.port = port
#temporary test server
#import json
#self.srv = vertx.create_http_server()
#def srv_handle(re):
# re.response.put_header("Content-Type","application/json; charset=utf-8")
# re.response.put_header("Location","locationlocation.location")
# re.response.end(json.dumps({"One":"Two"}))
#self.srv.request_handler(srv_handle)
#self.srv.listen(5000)
def request(self, method, path, data = None, headers = {}, args = None):
if isinstance(data, unicode):
data = data.encode("utf-8")
fullpath = self.__base + path
request_headers = self.__headers.copy()
if args:
fullpath += ("?" in fullpath and "&" or "?") + compose_qs(args)
if headers:
request_headers.update(headers)
if method == "GET":
timeout = self.get_timeout
try:
etag, modified, cached = self.__cache[fullpath]
if etag:
request_headers["If-None-Match"] = etag
request_headers["If-Modified-Since"] = modified
except KeyError:
request_headers.pop("If-None-Match", None)
request_headers.pop("If-Modified-Since", None)
else:
timeout = self.timeout
request_headers.setdefault("Content-Type", self.__content_type)
log_headers = request_headers
if self.logger.isEnabledFor(DEBUG) and "Authorization" in request_headers:
log_headers = request_headers.copy()
log_headers["Authorization"] = "<purged>"
if method == "GET":
self.logger.debug("%s: %s (%s)", method, fullpath, log_headers)
else:
self.logger.debug("%s: %s (%s)\n%s", method, fullpath, log_headers, repr(data))
#t = time()
promise=Promise()
try:
#response = self.__connection_manager.request(method, fullpath, data, request_headers, timeout)
def response_handler(resp):
if resp.status_code == 304:
try:
promise.fulfill(closing(StringIO(cached)))
except NameError:
promise.reject(NetworkError("Error: The %s returned 304 though no cached version is available. Request was: %s %s" % (self.component_name, method, fullpath)))
if resp.status_code < 200 or resp.status_code >= 300:
try:
promise.reject(HTTPError(msg = resp.status_message, status = resp.status_code))
except:
promise.reject(HTTPError(msg = "Http error", status = response.status))
else:
promise.fulfill(resp)
req=self.client.request(method,fullpath,response_handler)
for head,value in request_headers.items():
req.put_header(head,value)
if data:
req.chunked = True
req.write_str(data)
req.end()
except Exception as e:
print "Exception triggered: %s"%e
promise.reject(e)
return promise
#if method == "DELETE":
# try:
# self.__cache.pop(fullpath, None)
# except AttributeError:
# pass
#else:
# etag = response.getheader("Etag")
# modified = response.getheader("Last-Modified")
# if etag or modified:
# if not modified:
# modified = datetime.utcnow().strftime("%a, %d %b %Y %X GMT")
# response = CachingHttplibResponseWrapper(response, fullpath, etag, modified, self.__cache)
# elif self.logger.isEnabledFor(DEBUG):
# response = LoggingResponseWrapper(response)
def get(self, path, headers = None, args = None):
p = self.request("GET", path, headers = headers, args = args)
return p
def post(self, path, data, headers = None):
p = self.request("POST", path, data, headers)
return p
add = post
def put(self, path, data, headers = None):
p = self.request("PUT", path, data)
return p
update = put
def delete(self, path, headers = None):
p = self.request("DELETE", path, None, headers)
return p