da360462a5
This is establishing the API for a future optimization. We want to be able to offer true socket-level caching, but can't do that with httplib2 right now. For now, we will just fake the optimization by returning an iterator over the image body, which happens to already be fully loaded into a string. Change-Id: I2d36e3cdd45b26d7c7c27ba050bf6a4b5765df6c
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
"""
|
|
OpenStack Client interface. Handles the REST calls and responses.
|
|
"""
|
|
|
|
import copy
|
|
import logging
|
|
import os
|
|
import StringIO
|
|
import urlparse
|
|
|
|
import httplib2
|
|
|
|
try:
|
|
import json
|
|
except ImportError:
|
|
import simplejson as json
|
|
|
|
# Python 2.5 compat fix
|
|
if not hasattr(urlparse, 'parse_qsl'):
|
|
import cgi
|
|
urlparse.parse_qsl = cgi.parse_qsl
|
|
|
|
|
|
from glanceclient.common import exceptions
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
USER_AGENT = 'python-glanceclient'
|
|
CHUNKSIZE = 1024 * 64 # 64kB
|
|
|
|
|
|
class HTTPClient(httplib2.Http):
|
|
|
|
def __init__(self, endpoint, token=None, timeout=600, insecure=False):
|
|
super(HTTPClient, self).__init__(timeout=timeout)
|
|
self.endpoint = endpoint
|
|
self.auth_token = token
|
|
|
|
# httplib2 overrides
|
|
self.force_exception_to_status_code = True
|
|
self.disable_ssl_certificate_validation = insecure
|
|
|
|
def http_log(self, args, kwargs, resp, body):
|
|
if os.environ.get('GLANCECLIENT_DEBUG', False):
|
|
ch = logging.StreamHandler()
|
|
logger.setLevel(logging.DEBUG)
|
|
logger.addHandler(ch)
|
|
elif not logger.isEnabledFor(logging.DEBUG):
|
|
return
|
|
|
|
string_parts = ['curl -i']
|
|
for element in args:
|
|
if element in ('GET', 'POST'):
|
|
string_parts.append(' -X %s' % element)
|
|
else:
|
|
string_parts.append(' %s' % element)
|
|
|
|
for element in kwargs['headers']:
|
|
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
|
|
string_parts.append(header)
|
|
|
|
logger.debug("REQ: %s\n" % "".join(string_parts))
|
|
if 'raw_body' in kwargs:
|
|
logger.debug("REQ BODY (RAW): %s\n" % (kwargs['raw_body']))
|
|
if 'body' in kwargs:
|
|
logger.debug("REQ BODY: %s\n" % (kwargs['body']))
|
|
logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
|
|
|
|
def _http_request(self, url, method, **kwargs):
|
|
""" Send an http request with the specified characteristics.
|
|
|
|
Wrapper around httplib2.Http.request to handle tasks such as
|
|
setting headers, JSON encoding/decoding, and error handling.
|
|
"""
|
|
url = self.endpoint + url
|
|
|
|
# Copy the kwargs so we can reuse the original in case of redirects
|
|
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
|
|
kwargs['headers'].setdefault('User-Agent', USER_AGENT)
|
|
if self.auth_token:
|
|
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
|
|
|
|
resp, body = super(HTTPClient, self).request(url, method, **kwargs)
|
|
self.http_log((url, method,), kwargs, resp, body)
|
|
|
|
if 400 <= resp.status < 600:
|
|
logger.exception("Request returned failure status.")
|
|
raise exceptions.from_response(resp, body)
|
|
elif resp.status in (301, 302, 305):
|
|
# Redirected. Reissue the request to the new location.
|
|
return self._http_request(resp['location'], method, **kwargs)
|
|
|
|
#NOTE(bcwaldon): body has been loaded to a string at this point,
|
|
# but we want to move to a world where it can be read from a
|
|
# socket-level cache. This is here until we can do that.
|
|
body_iter = ResponseBodyIterator(StringIO.StringIO(body))
|
|
|
|
return resp, body_iter
|
|
|
|
def json_request(self, method, url, **kwargs):
|
|
kwargs.setdefault('headers', {})
|
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
|
|
|
if 'body' in kwargs:
|
|
kwargs['body'] = json.dumps(kwargs['body'])
|
|
|
|
resp, body_iter = self._http_request(url, method, **kwargs)
|
|
body = ''.join([chunk for chunk in body_iter])
|
|
|
|
if body:
|
|
try:
|
|
body = json.loads(body)
|
|
except ValueError:
|
|
logger.debug("Could not decode JSON from body: %s" % body)
|
|
else:
|
|
logger.debug("No body was returned.")
|
|
body = None
|
|
|
|
return resp, body
|
|
|
|
def raw_request(self, method, url, **kwargs):
|
|
kwargs.setdefault('headers', {})
|
|
kwargs['headers'].setdefault('Content-Type',
|
|
'application/octet-stream')
|
|
return self._http_request(url, method, **kwargs)
|
|
|
|
|
|
class ResponseBodyIterator(object):
|
|
"""A class that acts as an iterator over an HTTP response."""
|
|
|
|
def __init__(self, resp):
|
|
self.resp = resp
|
|
|
|
def __iter__(self):
|
|
while True:
|
|
yield self.next()
|
|
|
|
def next(self):
|
|
chunk = self.resp.read(CHUNKSIZE)
|
|
if chunk:
|
|
return chunk
|
|
else:
|
|
raise StopIteration()
|