diff options
| author | Jamie Lennox <jamielennox@redhat.com> | 2014-11-25 13:25:12 +1000 |
|---|---|---|
| committer | Flavio Percoco <fpercoco@redhat.com> | 2015-06-11 13:11:56 +0000 |
| commit | 5ce9c7dc964be0b3e8f9f273169b77ada85cd8ec (patch) | |
| tree | c02ac042c63cb3f474c32bd56677d15188ea451e /glanceclient/common/http.py | |
| parent | db6420b44779411d6d1f238f6b887f83f1988986 (diff) | |
| download | python-glanceclient-5ce9c7dc964be0b3e8f9f273169b77ada85cd8ec.tar.gz | |
Make glanceclient accept a session object
To make this work we create a different HTTPClient that extends the
basic keystoneclient Adapter. The Adapter is a standard set of
parameters that all clients should know how to use like region_name and
user_agent. We extend this with the glance specific response
manipulation like loading and sending iterables.
Implements: bp session-objects
Change-Id: Ie8eb4bbf7d1a037099a6d4b272cab70525fbfc85
Diffstat (limited to 'glanceclient/common/http.py')
| -rw-r--r-- | glanceclient/common/http.py | 191 |
1 files changed, 125 insertions, 66 deletions
diff --git a/glanceclient/common/http.py b/glanceclient/common/http.py index f746db5..b5f37cb 100644 --- a/glanceclient/common/http.py +++ b/glanceclient/common/http.py @@ -17,6 +17,8 @@ import copy import logging import socket +from keystoneclient import adapter +from keystoneclient import exceptions as ksc_exc from oslo_utils import importutils from oslo_utils import netutils import requests @@ -50,7 +52,71 @@ USER_AGENT = 'python-glanceclient' CHUNKSIZE = 1024 * 64 # 64kB -class HTTPClient(object): +class _BaseHTTPClient(object): + + @staticmethod + def _chunk_body(body): + chunk = body + while chunk: + chunk = body.read(CHUNKSIZE) + if chunk == '': + break + yield chunk + + def _set_common_request_kwargs(self, headers, kwargs): + """Handle the common parameters used to send the request.""" + + # Default Content-Type is octet-stream + content_type = headers.get('Content-Type', 'application/octet-stream') + + # NOTE(jamielennox): remove this later. Managers should pass json= if + # they want to send json data. + data = kwargs.pop("data", None) + if data is not None and not isinstance(data, six.string_types): + try: + data = json.dumps(data) + content_type = 'application/json' + except TypeError: + # Here we assume it's + # a file-like object + # and we'll chunk it + data = self._chunk_body(data) + + headers['Content-Type'] = content_type + kwargs['stream'] = content_type == 'application/octet-stream' + + return data + + def _handle_response(self, resp): + if not resp.ok: + LOG.debug("Request returned failure status %s." % resp.status_code) + raise exc.from_response(resp, resp.content) + elif resp.status_code == requests.codes.MULTIPLE_CHOICES: + raise exc.from_response(resp) + + content_type = resp.headers.get('Content-Type') + + # Read body into string if it isn't obviously image data + if content_type == 'application/octet-stream': + # Do not read all response in memory when downloading an image. + body_iter = _close_after_stream(resp, CHUNKSIZE) + else: + content = resp.text + if content_type and content_type.startswith('application/json'): + # Let's use requests json method, it should take care of + # response encoding + body_iter = resp.json() + else: + body_iter = six.StringIO(content) + try: + body_iter = json.loads(''.join([c for c in body_iter])) + except ValueError: + body_iter = None + + return resp, body_iter + + +class HTTPClient(_BaseHTTPClient): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint @@ -123,15 +189,16 @@ class HTTPClient(object): LOG.debug(msg) @staticmethod - def log_http_response(resp, body=None): + def log_http_response(resp): status = (resp.raw.version / 10.0, resp.status_code, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] headers = resp.headers.items() dump.extend(['%s: %s' % safe_header(k, v) for k, v in headers]) dump.append('') - if body: - body = encodeutils.safe_decode(body) - dump.extend([body, '']) + content_type = resp.headers.get('Content-Type') + + if content_type != 'application/octet-stream': + dump.extend([resp.text, '']) LOG.debug('\n'.join([encodeutils.safe_decode(x, errors='ignore') for x in dump])) @@ -155,37 +222,13 @@ class HTTPClient(object): as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects - headers = kwargs.pop("headers", {}) - headers = headers and copy.deepcopy(headers) or {} + headers = copy.deepcopy(kwargs.pop('headers', {})) if self.identity_headers: for k, v in six.iteritems(self.identity_headers): headers.setdefault(k, v) - # Default Content-Type is octet-stream - content_type = headers.get('Content-Type', 'application/octet-stream') - - def chunk_body(body): - chunk = body - while chunk: - chunk = body.read(CHUNKSIZE) - if chunk == '': - break - yield chunk - - data = kwargs.pop("data", None) - if data is not None and not isinstance(data, six.string_types): - try: - data = json.dumps(data) - content_type = 'application/json' - except TypeError: - # Here we assume it's - # a file-like object - # and we'll chunk it - data = chunk_body(data) - - headers['Content-Type'] = content_type - stream = True if content_type == 'application/octet-stream' else False + data = self._set_common_request_kwargs(headers, kwargs) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) @@ -195,20 +238,20 @@ class HTTPClient(object): # complain. headers = self.encode_headers(headers) + if self.endpoint.endswith("/") or url.startswith("/"): + conn_url = "%s%s" % (self.endpoint, url) + else: + conn_url = "%s/%s" % (self.endpoint, url) + self.log_curl_request(method, conn_url, headers, data, kwargs) + try: - if self.endpoint.endswith("/") or url.startswith("/"): - conn_url = "%s%s" % (self.endpoint, url) - else: - conn_url = "%s/%s" % (self.endpoint, url) - self.log_curl_request(method, conn_url, headers, data, kwargs) resp = self.session.request(method, conn_url, data=data, - stream=stream, headers=headers, **kwargs) except requests.exceptions.Timeout as e: - message = ("Error communicating with %(endpoint)s %(e)s" % + message = ("Error communicating with %(endpoint)s: %(e)s" % dict(url=conn_url, e=e)) raise exc.InvalidEndpoint(message=message) except (requests.exceptions.ConnectionError, ProtocolError) as e: @@ -225,34 +268,8 @@ class HTTPClient(object): {'endpoint': endpoint, 'e': e}) raise exc.CommunicationError(message=message) - if not resp.ok: - LOG.debug("Request returned failure status %s." % resp.status_code) - raise exc.from_response(resp, resp.text) - elif resp.status_code == requests.codes.MULTIPLE_CHOICES: - raise exc.from_response(resp) - - content_type = resp.headers.get('Content-Type') - - # Read body into string if it isn't obviously image data - if content_type == 'application/octet-stream': - # Do not read all response in memory when - # downloading an image. - body_iter = _close_after_stream(resp, CHUNKSIZE) - self.log_http_response(resp) - else: - content = resp.text - self.log_http_response(resp, content) - if content_type and content_type.startswith('application/json'): - # Let's use requests json method, - # it should take care of response - # encoding - body_iter = resp.json() - else: - body_iter = six.StringIO(content) - try: - body_iter = json.loads(''.join([c for c in body_iter])) - except ValueError: - body_iter = None + resp, body_iter = self._handle_response(resp) + self.log_http_response(resp) return resp, body_iter def head(self, url, **kwargs): @@ -283,3 +300,45 @@ def _close_after_stream(response, chunk_size): # This will return the connection to the HTTPConnectionPool in urllib3 # and ideally reduce the number of HTTPConnectionPool full warnings. response.close() + + +class SessionClient(adapter.Adapter, _BaseHTTPClient): + + def __init__(self, session, **kwargs): + kwargs.setdefault('user_agent', USER_AGENT) + kwargs.setdefault('service_type', 'image') + super(SessionClient, self).__init__(session, **kwargs) + + def request(self, url, method, **kwargs): + headers = kwargs.pop('headers', {}) + kwargs['raise_exc'] = False + data = self._set_common_request_kwargs(headers, kwargs) + + try: + resp = super(SessionClient, self).request(url, + method, + headers=headers, + data=data, + **kwargs) + except ksc_exc.RequestTimeout as e: + message = ("Error communicating with %(endpoint)s %(e)s" % + dict(url=conn_url, e=e)) + raise exc.InvalidEndpoint(message=message) + except ksc_exc.ConnectionRefused as e: + conn_url = self.get_endpoint(auth=kwargs.get('auth')) + conn_url = "%s/%s" % (conn_url.rstrip('/'), url.lstrip('/')) + message = ("Error finding address for %(url)s: %(e)s" % + dict(url=conn_url, e=e)) + raise exc.CommunicationError(message=message) + + return self._handle_response(resp) + + +def get_http_client(endpoint=None, session=None, **kwargs): + if session: + return SessionClient(session, **kwargs) + elif endpoint: + return HTTPClient(endpoint, **kwargs) + else: + raise AttributeError('Constructing a client must contain either an ' + 'endpoint or a session') |
