Repository: libcloud Updated Branches: refs/heads/requests [created] 2519c34d7
Move logging connection classes to another module -why don't people just use a logging proxy instead? so much easier! Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/cb0f4995 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/cb0f4995 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/cb0f4995 Branch: refs/heads/requests Commit: cb0f499590c783382b8b5261439e9415fb869278 Parents: fec8af6 Author: anthony-shaw <[email protected]> Authored: Sat Mar 26 20:38:48 2016 +1100 Committer: anthony-shaw <[email protected]> Committed: Sat Mar 26 20:38:48 2016 +1100 ---------------------------------------------------------------------- libcloud/common/base.py | 190 -------------------------- libcloud/utils/loggingconnection.py | 220 +++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 190 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/cb0f4995/libcloud/common/base.py ---------------------------------------------------------------------- diff --git a/libcloud/common/base.py b/libcloud/common/base.py index 1f353ab..6f62e0d 100644 --- a/libcloud/common/base.py +++ b/libcloud/common/base.py @@ -21,14 +21,11 @@ import copy import binascii import time -import xml.dom.minidom - try: from lxml import etree as ET except ImportError: from xml.etree import ElementTree as ET -from pipes import quote as pquote try: import simplejson as json @@ -41,8 +38,6 @@ from libcloud.utils.py3 import PY3, PY25 from libcloud.utils.py3 import httplib from libcloud.utils.py3 import urlparse from libcloud.utils.py3 import urlencode -from libcloud.utils.py3 import StringIO -from libcloud.utils.py3 import u from libcloud.utils.py3 import b from libcloud.utils.misc import lowercase_keys, retry @@ -63,8 +58,6 @@ __all__ = [ 'ConnectionKey', 'ConnectionUserAndKey', 'CertificateConnection', - 'LoggingHTTPConnection', - 'LoggingHTTPSConnection', 'Response', 'HTTPResponse', @@ -322,189 +315,6 @@ class RawResponse(Response): return self._reason -# TODO: Move this to a better location/package -class LoggingConnection(): - """ - Debug class to log all HTTP(s) requests as they could be made - with the curl command. - - :cvar log: file-like object that logs entries are written to. - """ - - log = None - http_proxy_used = False - - def _log_response(self, r): - rv = "# -------- begin %d:%d response ----------\n" % (id(self), id(r)) - ht = "" - v = r.version - if r.version == 10: - v = "HTTP/1.0" - if r.version == 11: - v = "HTTP/1.1" - ht += "%s %s %s\r\n" % (v, r.status, r.reason) - body = r.read() - for h in r.getheaders(): - ht += "%s: %s\r\n" % (h[0].title(), h[1]) - ht += "\r\n" - - # this is evil. laugh with me. ha arharhrhahahaha - class fakesock(object): - def __init__(self, s): - self.s = s - - def makefile(self, *args, **kwargs): - if PY3: - from io import BytesIO - cls = BytesIO - else: - cls = StringIO - - return cls(b(self.s)) - rr = r - headers = lowercase_keys(dict(r.getheaders())) - - encoding = headers.get('content-encoding', None) - content_type = headers.get('content-type', None) - - if encoding in ['zlib', 'deflate']: - body = decompress_data('zlib', body) - elif encoding in ['gzip', 'x-gzip']: - body = decompress_data('gzip', body) - - pretty_print = os.environ.get('LIBCLOUD_DEBUG_PRETTY_PRINT_RESPONSE', - False) - - if r.chunked: - ht += "%x\r\n" % (len(body)) - ht += body.decode('utf-8') - ht += "\r\n0\r\n" - else: - if pretty_print and content_type == 'application/json': - try: - body = json.loads(body.decode('utf-8')) - body = json.dumps(body, sort_keys=True, indent=4) - except: - # Invalid JSON or server is lying about content-type - pass - elif pretty_print and content_type == 'text/xml': - try: - elem = xml.dom.minidom.parseString(body.decode('utf-8')) - body = elem.toprettyxml() - except Exception: - # Invalid XML - pass - - ht += u(body) - - if sys.version_info >= (2, 6) and sys.version_info < (2, 7): - cls = HTTPResponse - else: - cls = httplib.HTTPResponse - - rr = cls(sock=fakesock(ht), method=r._method, - debuglevel=r.debuglevel) - rr.begin() - rv += ht - rv += ("\n# -------- end %d:%d response ----------\n" - % (id(self), id(r))) - - rr._original_data = body - return (rr, rv) - - def _log_curl(self, method, url, body, headers): - cmd = ["curl"] - - if self.http_proxy_used: - if self.proxy_username and self.proxy_password: - proxy_url = 'http://%s:%s@%s:%s' % (self.proxy_username, - self.proxy_password, - self.proxy_host, - self.proxy_port) - else: - proxy_url = 'http://%s:%s' % (self.proxy_host, - self.proxy_port) - proxy_url = pquote(proxy_url) - cmd.extend(['--proxy', proxy_url]) - - cmd.extend(['-i']) - - if method.lower() == 'head': - # HEAD method need special handling - cmd.extend(["--head"]) - else: - cmd.extend(["-X", pquote(method)]) - - for h in headers: - cmd.extend(["-H", pquote("%s: %s" % (h, headers[h]))]) - - cert_file = getattr(self, 'cert_file', None) - - if cert_file: - cmd.extend(["--cert", pquote(cert_file)]) - - # TODO: in python 2.6, body can be a file-like object. - if body is not None and len(body) > 0: - cmd.extend(["--data-binary", pquote(body)]) - - cmd.extend(["--compress"]) - cmd.extend([pquote("%s://%s:%d%s" % (self.protocol, self.host, - self.port, url))]) - return " ".join(cmd) - - -class LoggingHTTPSConnection(LoggingConnection, LibcloudHTTPSConnection): - """ - Utility Class for logging HTTPS connections - """ - - protocol = 'https' - - def getresponse(self): - r = LibcloudHTTPSConnection.getresponse(self) - if self.log is not None: - r, rv = self._log_response(r) - self.log.write(rv + "\n") - self.log.flush() - return r - - def request(self, method, url, body=None, headers=None): - headers.update({'X-LC-Request-ID': str(id(self))}) - if self.log is not None: - pre = "# -------- begin %d request ----------\n" % id(self) - self.log.write(pre + - self._log_curl(method, url, body, headers) + "\n") - self.log.flush() - return LibcloudHTTPSConnection.request(self, method, url, body, - headers) - - -class LoggingHTTPConnection(LoggingConnection, LibcloudHTTPConnection): - """ - Utility Class for logging HTTP connections - """ - - protocol = 'http' - - def getresponse(self): - r = LibcloudHTTPConnection.getresponse(self) - if self.log is not None: - r, rv = self._log_response(r) - self.log.write(rv + "\n") - self.log.flush() - return r - - def request(self, method, url, body=None, headers=None): - headers.update({'X-LC-Request-ID': str(id(self))}) - if self.log is not None: - pre = '# -------- begin %d request ----------\n' % id(self) - self.log.write(pre + - self._log_curl(method, url, body, headers) + "\n") - self.log.flush() - return LibcloudHTTPConnection.request(self, method, url, - body, headers) - - class Connection(object): """ A Base Connection class to derive from. http://git-wip-us.apache.org/repos/asf/libcloud/blob/cb0f4995/libcloud/utils/loggingconnection.py ---------------------------------------------------------------------- diff --git a/libcloud/utils/loggingconnection.py b/libcloud/utils/loggingconnection.py new file mode 100644 index 0000000..b7a1996 --- /dev/null +++ b/libcloud/utils/loggingconnection.py @@ -0,0 +1,220 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +try: + import simplejson as json +except: + import json + +from pipes import quote as pquote +import xml.dom.minidom + +import sys +import os +import httplib + +from libcloud.common.base import LibcloudHTTPConnection, \ + LibcloudHTTPSConnection, \ + HTTPResponse +from libcloud.utils.py3 import PY3 +from libcloud.utils.py3 import StringIO +from libcloud.utils.py3 import u +from libcloud.utils.py3 import b + + +from libcloud.utils.misc import lowercase_keys +from libcloud.utils.compression import decompress_data + + +class LoggingConnection(): + """ + Debug class to log all HTTP(s) requests as they could be made + with the curl command. + + :cvar log: file-like object that logs entries are written to. + """ + + log = None + http_proxy_used = False + + def _log_response(self, r): + rv = "# -------- begin %d:%d response ----------\n" % (id(self), id(r)) + ht = "" + v = r.version + if r.version == 10: + v = "HTTP/1.0" + if r.version == 11: + v = "HTTP/1.1" + ht += "%s %s %s\r\n" % (v, r.status, r.reason) + body = r.read() + for h in r.getheaders(): + ht += "%s: %s\r\n" % (h[0].title(), h[1]) + ht += "\r\n" + + # this is evil. laugh with me. ha arharhrhahahaha + class fakesock(object): + def __init__(self, s): + self.s = s + + def makefile(self, *args, **kwargs): + if PY3: + from io import BytesIO + cls = BytesIO + else: + cls = StringIO + + return cls(b(self.s)) + rr = r + headers = lowercase_keys(dict(r.getheaders())) + + encoding = headers.get('content-encoding', None) + content_type = headers.get('content-type', None) + + if encoding in ['zlib', 'deflate']: + body = decompress_data('zlib', body) + elif encoding in ['gzip', 'x-gzip']: + body = decompress_data('gzip', body) + + pretty_print = os.environ.get('LIBCLOUD_DEBUG_PRETTY_PRINT_RESPONSE', + False) + + if r.chunked: + ht += "%x\r\n" % (len(body)) + ht += body.decode('utf-8') + ht += "\r\n0\r\n" + else: + if pretty_print and content_type == 'application/json': + try: + body = json.loads(body.decode('utf-8')) + body = json.dumps(body, sort_keys=True, indent=4) + except: + # Invalid JSON or server is lying about content-type + pass + elif pretty_print and content_type == 'text/xml': + try: + elem = xml.dom.minidom.parseString(body.decode('utf-8')) + body = elem.toprettyxml() + except Exception: + # Invalid XML + pass + + ht += u(body) + + if sys.version_info >= (2, 6) and sys.version_info < (2, 7): + cls = HTTPResponse + else: + cls = httplib.HTTPResponse + + rr = cls(sock=fakesock(ht), method=r._method, + debuglevel=r.debuglevel) + rr.begin() + rv += ht + rv += ("\n# -------- end %d:%d response ----------\n" + % (id(self), id(r))) + + rr._original_data = body + return (rr, rv) + + def _log_curl(self, method, url, body, headers): + cmd = ["curl"] + + if self.http_proxy_used: + if self.proxy_username and self.proxy_password: + proxy_url = 'http://%s:%s@%s:%s' % (self.proxy_username, + self.proxy_password, + self.proxy_host, + self.proxy_port) + else: + proxy_url = 'http://%s:%s' % (self.proxy_host, + self.proxy_port) + proxy_url = pquote(proxy_url) + cmd.extend(['--proxy', proxy_url]) + + cmd.extend(['-i']) + + if method.lower() == 'head': + # HEAD method need special handling + cmd.extend(["--head"]) + else: + cmd.extend(["-X", pquote(method)]) + + for h in headers: + cmd.extend(["-H", pquote("%s: %s" % (h, headers[h]))]) + + cert_file = getattr(self, 'cert_file', None) + + if cert_file: + cmd.extend(["--cert", pquote(cert_file)]) + + # TODO: in python 2.6, body can be a file-like object. + if body is not None and len(body) > 0: + cmd.extend(["--data-binary", pquote(body)]) + + cmd.extend(["--compress"]) + cmd.extend([pquote("%s://%s:%d%s" % (self.protocol, self.host, + self.port, url))]) + return " ".join(cmd) + + +class LoggingHTTPSConnection(LoggingConnection, LibcloudHTTPSConnection): + """ + Utility Class for logging HTTPS connections + """ + + protocol = 'https' + + def getresponse(self): + r = LibcloudHTTPSConnection.getresponse(self) + if self.log is not None: + r, rv = self._log_response(r) + self.log.write(rv + "\n") + self.log.flush() + return r + + def request(self, method, url, body=None, headers=None): + headers.update({'X-LC-Request-ID': str(id(self))}) + if self.log is not None: + pre = "# -------- begin %d request ----------\n" % id(self) + self.log.write(pre + + self._log_curl(method, url, body, headers) + "\n") + self.log.flush() + return LibcloudHTTPSConnection.request(self, method, url, body, + headers) + + +class LoggingHTTPConnection(LoggingConnection, LibcloudHTTPConnection): + """ + Utility Class for logging HTTP connections + """ + + protocol = 'http' + + def getresponse(self): + r = LibcloudHTTPConnection.getresponse(self) + if self.log is not None: + r, rv = self._log_response(r) + self.log.write(rv + "\n") + self.log.flush() + return r + + def request(self, method, url, body=None, headers=None): + headers.update({'X-LC-Request-ID': str(id(self))}) + if self.log is not None: + pre = '# -------- begin %d request ----------\n' % id(self) + self.log.write(pre + + self._log_curl(method, url, body, headers) + "\n") + self.log.flush() + return LibcloudHTTPConnection.request(self, method, url, + body, headers)
