1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
import copy
import httplib
import logging
import urlparse
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 import exc
logger = logging.getLogger(__name__)
USER_AGENT = 'python-glanceclient'
CHUNKSIZE = 1024 * 64 # 64kB
class HTTPClient(object):
def __init__(self, endpoint, token=None, timeout=600, insecure=False):
parts = urlparse.urlparse(endpoint)
self.connection_class = self.get_connection_class(parts.scheme)
self.endpoint = (parts.hostname, parts.port)
self.auth_token = token
@staticmethod
def get_connection_class(scheme):
try:
return getattr(httplib, '%sConnection' % scheme.upper())
except AttributeError:
msg = 'Unsupported scheme: %s' % scheme
raise exc.InvalidEndpoint(msg)
def get_connection(self):
return self.connection_class(*self.endpoint)
def http_log(self, args, kwargs, resp):
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", resp)
def _http_request(self, url, method, **kwargs):
""" Send an http request with the specified characteristics.
Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
as setting headers and error handling.
"""
# 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)
conn = self.get_connection()
conn.request(method, url, **kwargs)
resp = conn.getresponse()
self.http_log((url, method,), kwargs, resp)
if 400 <= resp.status < 600:
logger.exception("Request returned failure status.")
raise exc.from_response(resp)
elif resp.status in (301, 302, 305):
# Redirected. Reissue the request to the new location.
return self._http_request(resp['location'], method, **kwargs)
body_iter = ResponseBodyIterator(resp)
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()
|