summaryrefslogtreecommitdiff
path: root/swiftclient
diff options
context:
space:
mode:
Diffstat (limited to 'swiftclient')
-rw-r--r--swiftclient/client.py204
-rw-r--r--swiftclient/https_connection.py95
-rw-r--r--swiftclient/utils.py50
3 files changed, 114 insertions, 235 deletions
diff --git a/swiftclient/client.py b/swiftclient/client.py
index d23d467..ee80769 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -18,24 +18,18 @@ OpenStack Swift client library used internally
"""
import socket
+import requests
import sys
import logging
import warnings
-from functools import wraps
+from distutils.version import StrictVersion
+from requests.exceptions import RequestException, SSLError
from urllib import quote as _quote
from urlparse import urlparse, urlunparse
-from httplib import HTTPException, HTTPConnection, HTTPSConnection
from time import sleep, time
from swiftclient.exceptions import ClientException, InvalidHeadersException
-from swiftclient.utils import get_environ_proxies
-
-try:
- from swiftclient.https_connection import HTTPSConnectionNoSSLComp
-except ImportError:
- HTTPSConnectionNoSSLComp = HTTPSConnection
-
try:
from logging import NullHandler
@@ -50,6 +44,18 @@ except ImportError:
def createLock(self):
self.lock = None
+# requests version 1.2.3 try to encode headers in ascii, preventing
+# utf-8 encoded header to be 'prepared'
+if StrictVersion(requests.__version__) < StrictVersion('2.0.0'):
+ from requests.structures import CaseInsensitiveDict
+
+ def prepare_unicode_headers(self, headers):
+ if headers:
+ self.headers = CaseInsensitiveDict(headers)
+ else:
+ self.headers = CaseInsensitiveDict()
+ requests.models.PreparedRequest.prepare_headers = prepare_unicode_headers
+
logger = logging.getLogger("swiftclient")
logger.addHandler(NullHandler())
@@ -124,68 +130,93 @@ except ImportError:
from json import loads as json_loads
-def http_connection(url, proxy=None, ssl_compression=True):
- """
- Make an HTTPConnection or HTTPSConnection
+class HTTPConnection:
+ def __init__(self, url, proxy=None, cacert=None, insecure=False,
+ ssl_compression=False):
+ """
+ Make an HTTPConnection or HTTPSConnection
+
+ :param url: url to connect to
+ :param proxy: proxy to connect through, if any; None by default; str
+ of the format 'http://127.0.0.1:8888' to set one
+ :param cacert: A CA bundle file to use in verifying a TLS server
+ certificate.
+ :param insecure: Allow to access servers without checking SSL certs.
+ The server's certificate will not be verified.
+ :param ssl_compression: SSL compression should be disabled by default
+ and this setting is not usable as of now. The
+ parameter is kept for backward compatibility.
+ :raises ClientException: Unable to handle protocol scheme
+ """
+ self.url = url
+ self.parsed_url = urlparse(url)
+ self.host = self.parsed_url.netloc
+ self.port = self.parsed_url.port
+ self.requests_args = {}
+ if self.parsed_url.scheme not in ('http', 'https'):
+ raise ClientException("Unsupported scheme")
+ self.requests_args['verify'] = not insecure
+ if cacert:
+ # verify requests parameter is used to pass the CA_BUNDLE file
+ # see: http://docs.python-requests.org/en/latest/user/advanced/
+ self.requests_args['verify'] = cacert
+ if proxy:
+ proxy_parsed = urlparse(proxy)
+ if not proxy_parsed.scheme:
+ raise ClientException("Proxy's missing scheme")
+ self.requests_args['proxies'] = {
+ proxy_parsed.scheme: '%s://%s' % (
+ proxy_parsed.scheme, proxy_parsed.netloc
+ )
+ }
+ self.requests_args['stream'] = True
+
+ def _request(self, *arg, **kwarg):
+ """ Final wrapper before requests call, to be patched in tests """
+ return requests.request(*arg, **kwarg)
+
+ def request(self, method, full_path, data=None, headers={}, files=None):
+ """ Encode url and header, then call requests.request """
+ headers = dict((encode_utf8(x), encode_utf8(y)) for x, y in
+ headers.iteritems())
+ url = encode_utf8("%s://%s%s" % (
+ self.parsed_url.scheme,
+ self.parsed_url.netloc,
+ full_path))
+ self.resp = self._request(method, url, headers=headers, data=data,
+ files=files, **self.requests_args)
+ return self.resp
+
+ def putrequest(self, full_path, data=None, headers={}, files=None):
+ """
+ Use python-requests files upload
- :param url: url to connect to
- :param proxy: proxy to connect through, if any; None by default; str of the
- format 'http://127.0.0.1:8888' to set one
- :param ssl_compression: Whether to enable compression at the SSL layer.
- If set to 'False' and the pyOpenSSL library is
- present an attempt to disable SSL compression
- will be made. This may provide a performance
- increase for https upload/download operations.
- :returns: tuple of (parsed url, connection object)
- :raises ClientException: Unable to handle protocol scheme
- """
- url = encode_utf8(url)
- parsed = urlparse(url)
- if proxy:
- proxy_parsed = urlparse(proxy)
- else:
- proxies = get_environ_proxies(parsed.netloc)
- proxy = proxies.get(parsed.scheme, None)
- proxy_parsed = urlparse(proxy) if proxy else None
- host = proxy_parsed.netloc if proxy else parsed.netloc
- if parsed.scheme == 'http':
- conn = HTTPConnection(host)
- elif parsed.scheme == 'https':
- if ssl_compression is True:
- conn = HTTPSConnection(host)
- else:
- conn = HTTPSConnectionNoSSLComp(host)
- else:
- raise ClientException('Cannot handle protocol scheme %s for url %s' %
- (parsed.scheme, repr(url)))
-
- def putheader_wrapper(func):
-
- @wraps(func)
- def putheader_escaped(key, value):
- func(encode_utf8(key), encode_utf8(value))
- return putheader_escaped
- conn.putheader = putheader_wrapper(conn.putheader)
-
- def request_wrapper(func):
-
- @wraps(func)
- def request_escaped(method, url, body=None, headers=None):
- validate_headers(headers)
- url = encode_utf8(url)
- if body:
- body = encode_utf8(body)
- func(method, url, body=body, headers=headers or {})
- return request_escaped
- conn.request = request_wrapper(conn.request)
- if proxy:
- try:
- # python 2.6 method
- conn._set_tunnel(parsed.hostname, parsed.port)
- except AttributeError:
- # python 2.7 method
- conn.set_tunnel(parsed.hostname, parsed.port)
- return parsed, conn
+ :param data: Use data generator for chunked-transfer
+ :param files: Use files for default transfer
+ """
+ return self.request('PUT', full_path, data, headers, files)
+
+ def getresponse(self):
+ """ Adapt requests response to httplib interface """
+ self.resp.status = self.resp.status_code
+ old_getheader = self.resp.raw.getheader
+
+ def getheaders():
+ return self.resp.headers.items()
+
+ def getheader(k, v=None):
+ return old_getheader(k.lower(), v)
+
+ self.resp.getheaders = getheaders
+ self.resp.getheader = getheader
+ self.resp.read = self.resp.raw.read
+ return self.resp
+
+
+def http_connection(*arg, **kwarg):
+ """ :returns: tuple of (parsed url, connection object) """
+ conn = HTTPConnection(*arg, **kwarg)
+ return conn.parsed_url, conn
def get_auth_1_0(url, user, key, snet):
@@ -893,27 +924,16 @@ def put_object(url, token=None, container=None, name=None, contents=None,
if hasattr(contents, 'read'):
if chunk_size is None:
chunk_size = 65536
- conn.putrequest('PUT', path)
- for header, value in headers.iteritems():
- conn.putheader(header, value)
if content_length is None:
- conn.putheader('Transfer-Encoding', 'chunked')
- conn.endheaders()
- chunk = contents.read(chunk_size)
- while chunk:
- conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
- chunk = contents.read(chunk_size)
- conn.send('0\r\n\r\n')
+ def chunk_reader():
+ while True:
+ data = contents.read(chunk_size)
+ if not data:
+ break
+ yield data
+ conn.putrequest(path, headers=headers, data=chunk_reader())
else:
- conn.endheaders()
- left = content_length
- while left > 0:
- size = chunk_size
- if size > left:
- size = left
- chunk = contents.read(size)
- conn.send(chunk)
- left -= len(chunk)
+ conn.putrequest(path, headers=headers, files={"file": contents})
else:
if chunk_size is not None:
warn_msg = '%s object has no \"read\" method, ignoring chunk_size'\
@@ -1132,6 +1152,8 @@ class Connection(object):
def http_connection(self):
return http_connection(self.url,
+ cacert=self.cacert,
+ insecure=self.insecure,
ssl_compression=self.ssl_compression)
def _add_response_dict(self, target_dict, kwargs):
@@ -1163,7 +1185,9 @@ class Connection(object):
rv = func(self.url, self.token, *args, **kwargs)
self._add_response_dict(caller_response_dict, kwargs)
return rv
- except (socket.error, HTTPException) as e:
+ except SSLError:
+ raise
+ except (socket.error, RequestException) as e:
self._add_response_dict(caller_response_dict, kwargs)
if self.attempts > self.retries:
logger.exception(e)
diff --git a/swiftclient/https_connection.py b/swiftclient/https_connection.py
deleted file mode 100644
index 2a2dc1f..0000000
--- a/swiftclient/https_connection.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Copyright (c) 2013 OpenStack, LLC.
-#
-# Licensed 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.
-
-"""
-HTTPS/SSL related functionality
-"""
-
-import socket
-
-from httplib import HTTPSConnection
-
-import OpenSSL
-
-try:
- from eventlet.green.OpenSSL.SSL import GreenConnection
- from eventlet.greenio import GreenSocket
- from eventlet.patcher import is_monkey_patched
-
- def getsockopt(self, *args, **kwargs):
- return self.fd.getsockopt(*args, **kwargs)
- # The above is a workaround for an eventlet bug in getsockopt.
- # TODO(mclaren): Workaround can be removed when this fix lands:
- # https://bitbucket.org/eventlet/eventlet/commits/609f230
- GreenSocket.getsockopt = getsockopt
-except ImportError:
- def is_monkey_patched(*args):
- return False
-
-
-class HTTPSConnectionNoSSLComp(HTTPSConnection):
- """
- Extended HTTPSConnection which uses the OpenSSL library
- for disabling SSL compression.
- Note: This functionality can eventually be replaced
- with native Python 3.3 code.
- """
- def __init__(self, host):
- HTTPSConnection.__init__(self, host)
- self.setcontext()
-
- def setcontext(self):
- """
- Set up the OpenSSL context.
- """
- self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
- # Disable SSL layer compression.
- self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
-
- def connect(self):
- """
- Connect to an SSL port using the OpenSSL library and apply
- per-connection parameters.
- """
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock = OpenSSLConnectionDelegator(self.context, sock)
- self.sock.connect((self.host, self.port))
-
-
-class OpenSSLConnectionDelegator(object):
- """
- An OpenSSL.SSL.Connection delegator.
-
- Supplies an additional 'makefile' method which httplib requires
- and is not present in OpenSSL.SSL.Connection.
-
- Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
- a delegator must be used.
- """
- def __init__(self, *args, **kwargs):
- if is_monkey_patched('socket'):
- # If we are running in a monkey patched environment
- # use eventlet's GreenConnection -- it handles eventlet's
- # non-blocking sockets correctly.
- Connection = GreenConnection
- else:
- Connection = OpenSSL.SSL.Connection
- self.connection = Connection(*args, **kwargs)
-
- def __getattr__(self, name):
- return getattr(self.connection, name)
-
- def makefile(self, *args, **kwargs):
- return socket._fileobject(self.connection, *args, **kwargs)
diff --git a/swiftclient/utils.py b/swiftclient/utils.py
index a74eada..a038dcc 100644
--- a/swiftclient/utils.py
+++ b/swiftclient/utils.py
@@ -14,23 +14,6 @@
# limitations under the License.
"""Miscellaneous utility functions for use with Swift."""
-import sys
-import os
-
-_ver = sys.version_info
-
-#: Python 2.x?
-is_py2 = (_ver[0] == 2)
-
-#: Python 3.x?
-is_py3 = (_ver[0] == 3)
-
-if is_py2:
- from urllib import getproxies, proxy_bypass
-elif is_py3:
- from urllib.request import getproxies, proxy_bypass
-
-
TRUE_VALUES = set(('true', '1', 'yes', 'on', 't', 'y'))
@@ -72,36 +55,3 @@ def prt_bytes(bytes, human_flag):
bytes = '%12s' % bytes
return(bytes)
-
-
-# get_environ_proxies function, borrowed from python Requests
-# (www.python-requests.org)
-def get_environ_proxies(netloc):
- """Return a dict of environment proxies."""
-
- get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
-
- # First check whether no_proxy is defined. If it is, check that the URL
- # we're getting isn't in the no_proxy list.
- no_proxy = get_proxy('no_proxy')
-
- if no_proxy:
- # We need to check whether we match here. We need to see if we match
- # the end of the netloc, both with and without the port.
- no_proxy = no_proxy.replace(' ', '').split(',')
-
- for host in no_proxy:
- if netloc.endswith(host) or netloc.split(':')[0].endswith(host):
- # The URL does match something in no_proxy, so we don't want
- # to apply the proxies on this URL.
- return {}
-
- # If the system proxy settings indicate that this URL should be bypassed,
- # don't proxy.
- if proxy_bypass(netloc):
- return {}
-
- # If we get here, we either didn't have no_proxy set or we're not going
- # anywhere that no_proxy applies to, and the system settings don't require
- # bypassing the proxy for the current URL.
- return getproxies()