Revision 2a7c3583 lib/rapi/client.py
b/lib/rapi/client.py | ||
---|---|---|
19 | 19 |
# 02110-1301, USA. |
20 | 20 |
|
21 | 21 |
|
22 |
"""Ganeti RAPI client.""" |
|
22 |
"""Ganeti RAPI client. |
|
23 |
|
|
24 |
@attention: To use the RAPI client, the application B{must} call |
|
25 |
C{pycurl.global_init} during initialization and |
|
26 |
C{pycurl.global_cleanup} before exiting the process. This is very |
|
27 |
important in multi-threaded programs. See curl_global_init(3) and |
|
28 |
curl_global_cleanup(3) for details. The decorator L{UsesRapiClient} |
|
29 |
can be used. |
|
30 |
|
|
31 |
""" |
|
23 | 32 |
|
24 | 33 |
# No Ganeti-specific modules should be imported. The RAPI client is supposed to |
25 | 34 |
# be standalone. |
26 | 35 |
|
27 |
import sys |
|
28 |
import httplib |
|
29 |
import urllib2 |
|
30 | 36 |
import logging |
31 | 37 |
import simplejson |
32 |
import socket |
|
33 | 38 |
import urllib |
34 |
import OpenSSL |
|
35 |
import distutils.version |
|
39 |
import threading |
|
40 |
import pycurl |
|
41 |
|
|
42 |
try: |
|
43 |
from cStringIO import StringIO |
|
44 |
except ImportError: |
|
45 |
from StringIO import StringIO |
|
36 | 46 |
|
37 | 47 |
|
38 | 48 |
GANETI_RAPI_PORT = 5080 |
... | ... | |
61 | 71 |
_REQ_DATA_VERSION_FIELD = "__version__" |
62 | 72 |
_INST_CREATE_REQV1 = "instance-create-reqv1" |
63 | 73 |
|
74 |
# Older pycURL versions don't have all error constants |
|
75 |
try: |
|
76 |
_CURLE_SSL_CACERT = pycurl.E_SSL_CACERT |
|
77 |
_CURLE_SSL_CACERT_BADFILE = pycurl.E_SSL_CACERT_BADFILE |
|
78 |
except AttributeError: |
|
79 |
_CURLE_SSL_CACERT = 60 |
|
80 |
_CURLE_SSL_CACERT_BADFILE = 77 |
|
81 |
|
|
82 |
_CURL_SSL_CERT_ERRORS = frozenset([ |
|
83 |
_CURLE_SSL_CACERT, |
|
84 |
_CURLE_SSL_CACERT_BADFILE, |
|
85 |
]) |
|
86 |
|
|
64 | 87 |
|
65 | 88 |
class Error(Exception): |
66 | 89 |
"""Base error class for this module. |
... | ... | |
85 | 108 |
self.code = code |
86 | 109 |
|
87 | 110 |
|
88 |
def FormatX509Name(x509_name): |
|
89 |
"""Formats an X509 name. |
|
90 |
|
|
91 |
@type x509_name: OpenSSL.crypto.X509Name |
|
111 |
def UsesRapiClient(fn): |
|
112 |
"""Decorator for code using RAPI client to initialize pycURL. |
|
92 | 113 |
|
93 | 114 |
""" |
94 |
try: |
|
95 |
# Only supported in pyOpenSSL 0.7 and above |
|
96 |
get_components_fn = x509_name.get_components |
|
97 |
except AttributeError: |
|
98 |
return repr(x509_name) |
|
99 |
else: |
|
100 |
return "".join("/%s=%s" % (name, value) |
|
101 |
for name, value in get_components_fn()) |
|
102 |
|
|
103 |
|
|
104 |
class CertAuthorityVerify: |
|
105 |
"""Certificate verificator for SSL context. |
|
106 |
|
|
107 |
Configures SSL context to verify server's certificate. |
|
115 |
def wrapper(*args, **kwargs): |
|
116 |
# curl_global_init(3) and curl_global_cleanup(3) must be called with only |
|
117 |
# one thread running. This check is just a safety measure -- it doesn't |
|
118 |
# cover all cases. |
|
119 |
assert threading.activeCount() == 1, \ |
|
120 |
"Found active threads when initializing pycURL" |
|
121 |
|
|
122 |
pycurl.global_init(pycurl.GLOBAL_ALL) |
|
123 |
try: |
|
124 |
return fn(*args, **kwargs) |
|
125 |
finally: |
|
126 |
pycurl.global_cleanup() |
|
127 |
|
|
128 |
return wrapper |
|
129 |
|
|
130 |
|
|
131 |
def GenericCurlConfig(verbose=False, use_signal=False, |
|
132 |
use_curl_cabundle=False, cafile=None, capath=None, |
|
133 |
proxy=None, verify_hostname=False, |
|
134 |
connect_timeout=None, timeout=None, |
|
135 |
_pycurl_version_fn=pycurl.version_info): |
|
136 |
"""Curl configuration function generator. |
|
137 |
|
|
138 |
@type verbose: bool |
|
139 |
@param verbose: Whether to set cURL to verbose mode |
|
140 |
@type use_signal: bool |
|
141 |
@param use_signal: Whether to allow cURL to use signals |
|
142 |
@type use_curl_cabundle: bool |
|
143 |
@param use_curl_cabundle: Whether to use cURL's default CA bundle |
|
144 |
@type cafile: string |
|
145 |
@param cafile: In which file we can find the certificates |
|
146 |
@type capath: string |
|
147 |
@param capath: In which directory we can find the certificates |
|
148 |
@type proxy: string |
|
149 |
@param proxy: Proxy to use, None for default behaviour and empty string for |
|
150 |
disabling proxies (see curl_easy_setopt(3)) |
|
151 |
@type verify_hostname: bool |
|
152 |
@param verify_hostname: Whether to verify the remote peer certificate's |
|
153 |
commonName |
|
154 |
@type connect_timeout: number |
|
155 |
@param connect_timeout: Timeout for establishing connection in seconds |
|
156 |
@type timeout: number |
|
157 |
@param timeout: Timeout for complete transfer in seconds (see |
|
158 |
curl_easy_setopt(3)). |
|
108 | 159 |
|
109 | 160 |
""" |
110 |
_CAPATH_MINVERSION = "0.9"
|
|
111 |
_DEFVFYPATHS_MINVERSION = "0.9"
|
|
161 |
if use_curl_cabundle and (cafile or capath):
|
|
162 |
raise Error("Can not use default CA bundle when CA file or path is set")
|
|
112 | 163 |
|
113 |
_PYOPENSSL_VERSION = OpenSSL.__version__ |
|
114 |
_PARSED_PYOPENSSL_VERSION = distutils.version.LooseVersion(_PYOPENSSL_VERSION) |
|
115 |
|
|
116 |
_SUPPORT_CAPATH = (_PARSED_PYOPENSSL_VERSION >= _CAPATH_MINVERSION) |
|
117 |
_SUPPORT_DEFVFYPATHS = (_PARSED_PYOPENSSL_VERSION >= _DEFVFYPATHS_MINVERSION) |
|
118 |
|
|
119 |
def __init__(self, cafile=None, capath=None, use_default_verify_paths=False): |
|
120 |
"""Initializes this class. |
|
164 |
def _ConfigCurl(curl, logger): |
|
165 |
"""Configures a cURL object |
|
121 | 166 |
|
122 |
@type cafile: string |
|
123 |
@param cafile: In which file we can find the certificates |
|
124 |
@type capath: string |
|
125 |
@param capath: In which directory we can find the certificates |
|
126 |
@type use_default_verify_paths: bool |
|
127 |
@param use_default_verify_paths: Whether the platform provided CA |
|
128 |
certificates are to be used for |
|
129 |
verification purposes |
|
167 |
@type curl: pycurl.Curl |
|
168 |
@param curl: cURL object |
|
130 | 169 |
|
131 | 170 |
""" |
132 |
self._cafile = cafile |
|
133 |
self._capath = capath |
|
134 |
self._use_default_verify_paths = use_default_verify_paths |
|
135 |
|
|
136 |
if self._capath is not None and not self._SUPPORT_CAPATH: |
|
137 |
raise Error(("PyOpenSSL %s has no support for a CA directory," |
|
138 |
" version %s or above is required") % |
|
139 |
(self._PYOPENSSL_VERSION, self._CAPATH_MINVERSION)) |
|
140 |
|
|
141 |
if self._use_default_verify_paths and not self._SUPPORT_DEFVFYPATHS: |
|
142 |
raise Error(("PyOpenSSL %s has no support for using default verification" |
|
143 |
" paths, version %s or above is required") % |
|
144 |
(self._PYOPENSSL_VERSION, self._DEFVFYPATHS_MINVERSION)) |
|
145 |
|
|
146 |
@staticmethod |
|
147 |
def _VerifySslCertCb(logger, _, cert, errnum, errdepth, ok): |
|
148 |
"""Callback for SSL certificate verification. |
|
149 |
|
|
150 |
@param logger: Logging object |
|
151 |
|
|
152 |
""" |
|
153 |
if ok: |
|
154 |
log_fn = logger.debug |
|
171 |
logger.debug("Using cURL version %s", pycurl.version) |
|
172 |
|
|
173 |
# pycurl.version_info returns a tuple with information about the used |
|
174 |
# version of libcurl. Item 5 is the SSL library linked to it. |
|
175 |
# e.g.: (3, '7.18.0', 463360, 'x86_64-pc-linux-gnu', 1581, 'GnuTLS/2.0.4', |
|
176 |
# 0, '1.2.3.3', ...) |
|
177 |
sslver = _pycurl_version_fn()[5] |
|
178 |
if not sslver: |
|
179 |
raise Error("No SSL support in cURL") |
|
180 |
|
|
181 |
lcsslver = sslver.lower() |
|
182 |
if lcsslver.startswith("openssl/"): |
|
183 |
pass |
|
184 |
elif lcsslver.startswith("gnutls/"): |
|
185 |
if capath: |
|
186 |
raise Error("cURL linked against GnuTLS has no support for a" |
|
187 |
" CA path (%s)" % (pycurl.version, )) |
|
155 | 188 |
else: |
156 |
log_fn = logger.error |
|
157 |
|
|
158 |
log_fn("Verifying SSL certificate at depth %s, subject '%s', issuer '%s'", |
|
159 |
errdepth, FormatX509Name(cert.get_subject()), |
|
160 |
FormatX509Name(cert.get_issuer())) |
|
161 |
|
|
162 |
if not ok: |
|
163 |
try: |
|
164 |
# Only supported in pyOpenSSL 0.7 and above |
|
165 |
# pylint: disable-msg=E1101 |
|
166 |
fn = OpenSSL.crypto.X509_verify_cert_error_string |
|
167 |
except AttributeError: |
|
168 |
errmsg = "" |
|
169 |
else: |
|
170 |
errmsg = ":%s" % fn(errnum) |
|
171 |
|
|
172 |
logger.error("verify error:num=%s%s", errnum, errmsg) |
|
173 |
|
|
174 |
return ok |
|
175 |
|
|
176 |
def __call__(self, ctx, logger): |
|
177 |
"""Configures an SSL context to verify certificates. |
|
178 |
|
|
179 |
@type ctx: OpenSSL.SSL.Context |
|
180 |
@param ctx: SSL context |
|
181 |
|
|
182 |
""" |
|
183 |
if self._use_default_verify_paths: |
|
184 |
ctx.set_default_verify_paths() |
|
185 |
|
|
186 |
if self._cafile or self._capath: |
|
187 |
if self._SUPPORT_CAPATH: |
|
188 |
ctx.load_verify_locations(self._cafile, self._capath) |
|
189 |
else: |
|
190 |
ctx.load_verify_locations(self._cafile) |
|
191 |
|
|
192 |
ctx.set_verify(OpenSSL.SSL.VERIFY_PEER, |
|
193 |
lambda conn, cert, errnum, errdepth, ok: \ |
|
194 |
self._VerifySslCertCb(logger, conn, cert, |
|
195 |
errnum, errdepth, ok)) |
|
196 |
|
|
197 |
|
|
198 |
class _HTTPSConnectionOpenSSL(httplib.HTTPSConnection): |
|
199 |
"""HTTPS Connection handler that verifies the SSL certificate. |
|
200 |
|
|
201 |
""" |
|
202 |
# Python before version 2.6 had its own httplib.FakeSocket wrapper for |
|
203 |
# sockets |
|
204 |
_SUPPORT_FAKESOCKET = (sys.hexversion < 0x2060000) |
|
205 |
|
|
206 |
def __init__(self, *args, **kwargs): |
|
207 |
"""Initializes this class. |
|
208 |
|
|
209 |
""" |
|
210 |
httplib.HTTPSConnection.__init__(self, *args, **kwargs) |
|
211 |
self._logger = None |
|
212 |
self._config_ssl_verification = None |
|
213 |
|
|
214 |
def Setup(self, logger, config_ssl_verification): |
|
215 |
"""Sets the SSL verification config function. |
|
216 |
|
|
217 |
@param logger: Logging object |
|
218 |
@type config_ssl_verification: callable |
|
219 |
|
|
220 |
""" |
|
221 |
assert self._logger is None |
|
222 |
assert self._config_ssl_verification is None |
|
223 |
|
|
224 |
self._logger = logger |
|
225 |
self._config_ssl_verification = config_ssl_verification |
|
226 |
|
|
227 |
def connect(self): |
|
228 |
"""Connect to the server specified when the object was created. |
|
229 |
|
|
230 |
This ensures that SSL certificates are verified. |
|
231 |
|
|
232 |
""" |
|
233 |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
234 |
|
|
235 |
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) |
|
236 |
ctx.set_options(OpenSSL.SSL.OP_NO_SSLv2) |
|
237 |
|
|
238 |
if self._config_ssl_verification: |
|
239 |
self._config_ssl_verification(ctx, self._logger) |
|
240 |
|
|
241 |
ssl = OpenSSL.SSL.Connection(ctx, sock) |
|
242 |
ssl.connect((self.host, self.port)) |
|
243 |
|
|
244 |
if self._SUPPORT_FAKESOCKET: |
|
245 |
self.sock = httplib.FakeSocket(sock, ssl) |
|
189 |
raise NotImplementedError("cURL uses unsupported SSL version '%s'" % |
|
190 |
sslver) |
|
191 |
|
|
192 |
curl.setopt(pycurl.VERBOSE, verbose) |
|
193 |
curl.setopt(pycurl.NOSIGNAL, not use_signal) |
|
194 |
|
|
195 |
# Whether to verify remote peer's CN |
|
196 |
if verify_hostname: |
|
197 |
# curl_easy_setopt(3): "When CURLOPT_SSL_VERIFYHOST is 2, that |
|
198 |
# certificate must indicate that the server is the server to which you |
|
199 |
# meant to connect, or the connection fails. [...] When the value is 1, |
|
200 |
# the certificate must contain a Common Name field, but it doesn't matter |
|
201 |
# what name it says. [...]" |
|
202 |
curl.setopt(pycurl.SSL_VERIFYHOST, 2) |
|
246 | 203 |
else: |
247 |
self.sock = _SslSocketWrapper(ssl) |
|
248 |
|
|
249 |
|
|
250 |
class _SslSocketWrapper(object): |
|
251 |
def __init__(self, sock): |
|
252 |
"""Initializes this class. |
|
253 |
|
|
254 |
""" |
|
255 |
self._sock = sock |
|
256 |
|
|
257 |
def __getattr__(self, name): |
|
258 |
"""Forward everything to underlying socket. |
|
259 |
|
|
260 |
""" |
|
261 |
return getattr(self._sock, name) |
|
262 |
|
|
263 |
def makefile(self, mode, bufsize): |
|
264 |
"""Fake makefile method. |
|
265 |
|
|
266 |
makefile() on normal file descriptors uses dup2(2), which doesn't work with |
|
267 |
SSL sockets and therefore is not implemented by pyOpenSSL. This fake method |
|
268 |
works with the httplib module, but might not work for other modules. |
|
269 |
|
|
270 |
""" |
|
271 |
# pylint: disable-msg=W0212 |
|
272 |
return socket._fileobject(self._sock, mode, bufsize) |
|
273 |
|
|
274 |
|
|
275 |
class _HTTPSHandler(urllib2.HTTPSHandler): |
|
276 |
def __init__(self, logger, config_ssl_verification): |
|
277 |
"""Initializes this class. |
|
278 |
|
|
279 |
@param logger: Logging object |
|
280 |
@type config_ssl_verification: callable |
|
281 |
@param config_ssl_verification: Function to configure SSL context for |
|
282 |
certificate verification |
|
283 |
|
|
284 |
""" |
|
285 |
urllib2.HTTPSHandler.__init__(self) |
|
286 |
self._logger = logger |
|
287 |
self._config_ssl_verification = config_ssl_verification |
|
288 |
|
|
289 |
def _CreateHttpsConnection(self, *args, **kwargs): |
|
290 |
"""Wrapper around L{_HTTPSConnectionOpenSSL} to add SSL verification. |
|
291 |
|
|
292 |
This wrapper is necessary provide a compatible API to urllib2. |
|
293 |
|
|
294 |
""" |
|
295 |
conn = _HTTPSConnectionOpenSSL(*args, **kwargs) |
|
296 |
conn.Setup(self._logger, self._config_ssl_verification) |
|
297 |
return conn |
|
298 |
|
|
299 |
def https_open(self, req): |
|
300 |
"""Creates HTTPS connection. |
|
301 |
|
|
302 |
Called by urllib2. |
|
303 |
|
|
304 |
""" |
|
305 |
return self.do_open(self._CreateHttpsConnection, req) |
|
306 |
|
|
307 |
|
|
308 |
class _RapiRequest(urllib2.Request): |
|
309 |
def __init__(self, method, url, headers, data): |
|
310 |
"""Initializes this class. |
|
204 |
curl.setopt(pycurl.SSL_VERIFYHOST, 0) |
|
205 |
|
|
206 |
if cafile or capath or use_curl_cabundle: |
|
207 |
# Require certificates to be checked |
|
208 |
curl.setopt(pycurl.SSL_VERIFYPEER, True) |
|
209 |
if cafile: |
|
210 |
curl.setopt(pycurl.CAINFO, str(cafile)) |
|
211 |
if capath: |
|
212 |
curl.setopt(pycurl.CAPATH, str(capath)) |
|
213 |
# Not changing anything for using default CA bundle |
|
214 |
else: |
|
215 |
# Disable SSL certificate verification |
|
216 |
curl.setopt(pycurl.SSL_VERIFYPEER, False) |
|
311 | 217 |
|
312 |
""" |
|
313 |
urllib2.Request.__init__(self, url, data=data, headers=headers) |
|
314 |
self._method = method |
|
218 |
if proxy is not None: |
|
219 |
curl.setopt(pycurl.PROXY, str(proxy)) |
|
315 | 220 |
|
316 |
def get_method(self): |
|
317 |
"""Returns the HTTP request method. |
|
221 |
# Timeouts |
|
222 |
if connect_timeout is not None: |
|
223 |
curl.setopt(pycurl.CONNECTTIMEOUT, connect_timeout) |
|
224 |
if timeout is not None: |
|
225 |
curl.setopt(pycurl.TIMEOUT, timeout) |
|
318 | 226 |
|
319 |
""" |
|
320 |
return self._method |
|
227 |
return _ConfigCurl |
|
321 | 228 |
|
322 | 229 |
|
323 | 230 |
class GanetiRapiClient(object): |
... | ... | |
328 | 235 |
_json_encoder = simplejson.JSONEncoder(sort_keys=True) |
329 | 236 |
|
330 | 237 |
def __init__(self, host, port=GANETI_RAPI_PORT, |
331 |
username=None, password=None, |
|
332 |
config_ssl_verification=None, ignore_proxy=False, |
|
333 |
logger=logging): |
|
334 |
"""Constructor. |
|
238 |
username=None, password=None, logger=logging, |
|
239 |
curl_config_fn=None, curl=None): |
|
240 |
"""Initializes this class. |
|
335 | 241 |
|
336 | 242 |
@type host: string |
337 | 243 |
@param host: the ganeti cluster master to interact with |
... | ... | |
341 | 247 |
@param username: the username to connect with |
342 | 248 |
@type password: string |
343 | 249 |
@param password: the password to connect with |
344 |
@type config_ssl_verification: callable |
|
345 |
@param config_ssl_verification: Function to configure SSL context for |
|
346 |
certificate verification |
|
347 |
@type ignore_proxy: bool |
|
348 |
@param ignore_proxy: Whether to ignore proxy settings |
|
250 |
@type curl_config_fn: callable |
|
251 |
@param curl_config_fn: Function to configure C{pycurl.Curl} object |
|
349 | 252 |
@param logger: Logging object |
350 | 253 |
|
351 | 254 |
""" |
... | ... | |
355 | 258 |
|
356 | 259 |
self._base_url = "https://%s:%s" % (host, port) |
357 | 260 |
|
358 |
handlers = [_HTTPSHandler(self._logger, config_ssl_verification)] |
|
359 |
|
|
261 |
# Create pycURL object if not supplied |
|
262 |
if not curl: |
|
263 |
curl = pycurl.Curl() |
|
264 |
|
|
265 |
# Default cURL settings |
|
266 |
curl.setopt(pycurl.VERBOSE, False) |
|
267 |
curl.setopt(pycurl.FOLLOWLOCATION, False) |
|
268 |
curl.setopt(pycurl.MAXREDIRS, 5) |
|
269 |
curl.setopt(pycurl.NOSIGNAL, True) |
|
270 |
curl.setopt(pycurl.USERAGENT, self.USER_AGENT) |
|
271 |
curl.setopt(pycurl.SSL_VERIFYHOST, 0) |
|
272 |
curl.setopt(pycurl.SSL_VERIFYPEER, False) |
|
273 |
curl.setopt(pycurl.HTTPHEADER, [ |
|
274 |
"Accept: %s" % HTTP_APP_JSON, |
|
275 |
"Content-type: %s" % HTTP_APP_JSON, |
|
276 |
]) |
|
277 |
|
|
278 |
# Setup authentication |
|
360 | 279 |
if username is not None: |
361 |
pwmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() |
|
362 |
pwmgr.add_password(None, self._base_url, username, password) |
|
363 |
handlers.append(urllib2.HTTPBasicAuthHandler(pwmgr)) |
|
280 |
if password is None: |
|
281 |
raise Error("Password not specified") |
|
282 |
curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) |
|
283 |
curl.setopt(pycurl.USERPWD, str("%s:%s" % (username, password))) |
|
364 | 284 |
elif password: |
365 | 285 |
raise Error("Specified password without username") |
366 | 286 |
|
367 |
if ignore_proxy: |
|
368 |
handlers.append(urllib2.ProxyHandler({})) |
|
369 |
|
|
370 |
self._http = urllib2.build_opener(*handlers) # pylint: disable-msg=W0142 |
|
287 |
# Call external configuration function |
|
288 |
if curl_config_fn: |
|
289 |
curl_config_fn(curl, logger) |
|
371 | 290 |
|
372 |
self._headers = { |
|
373 |
"Accept": HTTP_APP_JSON, |
|
374 |
"Content-type": HTTP_APP_JSON, |
|
375 |
"User-Agent": self.USER_AGENT, |
|
376 |
} |
|
291 |
self._curl = curl |
|
377 | 292 |
|
378 | 293 |
@staticmethod |
379 | 294 |
def _EncodeQuery(query): |
... | ... | |
427 | 342 |
""" |
428 | 343 |
assert path.startswith("/") |
429 | 344 |
|
345 |
curl = self._curl |
|
346 |
|
|
430 | 347 |
if content: |
431 | 348 |
encoded_content = self._json_encoder.encode(content) |
432 | 349 |
else: |
433 |
encoded_content = None
|
|
350 |
encoded_content = ""
|
|
434 | 351 |
|
435 | 352 |
# Build URL |
436 | 353 |
urlparts = [self._base_url, path] |
... | ... | |
440 | 357 |
|
441 | 358 |
url = "".join(urlparts) |
442 | 359 |
|
443 |
self._logger.debug("Sending request %s %s to %s:%s" |
|
444 |
" (headers=%r, content=%r)", |
|
445 |
method, url, self._host, self._port, self._headers, |
|
446 |
encoded_content) |
|
360 |
self._logger.debug("Sending request %s %s to %s:%s (content=%r)", |
|
361 |
method, url, self._host, self._port, encoded_content) |
|
362 |
|
|
363 |
# Buffer for response |
|
364 |
encoded_resp_body = StringIO() |
|
447 | 365 |
|
448 |
req = _RapiRequest(method, url, self._headers, encoded_content) |
|
366 |
# Configure cURL |
|
367 |
curl.setopt(pycurl.CUSTOMREQUEST, str(method)) |
|
368 |
curl.setopt(pycurl.URL, str(url)) |
|
369 |
curl.setopt(pycurl.POSTFIELDS, str(encoded_content)) |
|
370 |
curl.setopt(pycurl.WRITEFUNCTION, encoded_resp_body.write) |
|
449 | 371 |
|
450 | 372 |
try: |
451 |
resp = self._http.open(req) |
|
452 |
encoded_response_content = resp.read() |
|
453 |
except (OpenSSL.SSL.Error, OpenSSL.crypto.Error), err: |
|
454 |
raise CertificateError("SSL issue: %s (%r)" % (err, err)) |
|
455 |
except urllib2.HTTPError, err: |
|
456 |
raise GanetiApiError(str(err), code=err.code) |
|
457 |
except urllib2.URLError, err: |
|
458 |
raise GanetiApiError(str(err)) |
|
459 |
|
|
460 |
if encoded_response_content: |
|
461 |
response_content = simplejson.loads(encoded_response_content) |
|
373 |
# Send request and wait for response |
|
374 |
try: |
|
375 |
curl.perform() |
|
376 |
except pycurl.error, err: |
|
377 |
if err.args[0] in _CURL_SSL_CERT_ERRORS: |
|
378 |
raise CertificateError("SSL certificate error %s" % err) |
|
379 |
|
|
380 |
raise GanetiApiError(str(err)) |
|
381 |
finally: |
|
382 |
# Reset settings to not keep references to large objects in memory |
|
383 |
# between requests |
|
384 |
curl.setopt(pycurl.POSTFIELDS, "") |
|
385 |
curl.setopt(pycurl.WRITEFUNCTION, lambda _: None) |
|
386 |
|
|
387 |
# Get HTTP response code |
|
388 |
http_code = curl.getinfo(pycurl.RESPONSE_CODE) |
|
389 |
|
|
390 |
# Was anything written to the response buffer? |
|
391 |
if encoded_resp_body.tell(): |
|
392 |
response_content = simplejson.loads(encoded_resp_body.getvalue()) |
|
462 | 393 |
else: |
463 | 394 |
response_content = None |
464 | 395 |
|
465 |
# TODO: Are there other status codes that are valid? (redirect?) |
|
466 |
if resp.code != HTTP_OK: |
|
396 |
if http_code != HTTP_OK: |
|
467 | 397 |
if isinstance(response_content, dict): |
468 | 398 |
msg = ("%s %s: %s" % |
469 | 399 |
(response_content["code"], |
... | ... | |
472 | 402 |
else: |
473 | 403 |
msg = str(response_content) |
474 | 404 |
|
475 |
raise GanetiApiError(msg, code=resp.code)
|
|
405 |
raise GanetiApiError(msg, code=http_code)
|
|
476 | 406 |
|
477 | 407 |
return response_content |
478 | 408 |
|
Also available in: Unified diff