18
18
"""
19
19
20
20
import socket
21
+ import requests
21
22
import sys
22
23
import logging
23
24
import warnings
24
- from functools import wraps
25
25
26
+ from distutils .version import StrictVersion
27
+ from requests .exceptions import RequestException , SSLError
26
28
from urllib import quote as _quote
27
29
from urlparse import urlparse , urlunparse
28
- from httplib import HTTPException , HTTPConnection , HTTPSConnection
29
30
from time import sleep , time
30
31
31
32
from swiftclient .exceptions import ClientException , InvalidHeadersException
32
- from swiftclient .utils import get_environ_proxies
33
-
34
- try :
35
- from swiftclient .https_connection import HTTPSConnectionNoSSLComp
36
- except ImportError :
37
- HTTPSConnectionNoSSLComp = HTTPSConnection
38
-
39
33
40
34
try :
41
35
from logging import NullHandler
@@ -50,6 +44,18 @@ def emit(self, record):
50
44
def createLock (self ):
51
45
self .lock = None
52
46
47
+ # requests version 1.2.3 try to encode headers in ascii, preventing
48
+ # utf-8 encoded header to be 'prepared'
49
+ if StrictVersion (requests .__version__ ) < StrictVersion ('2.0.0' ):
50
+ from requests .structures import CaseInsensitiveDict
51
+
52
+ def prepare_unicode_headers (self , headers ):
53
+ if headers :
54
+ self .headers = CaseInsensitiveDict (headers )
55
+ else :
56
+ self .headers = CaseInsensitiveDict ()
57
+ requests .models .PreparedRequest .prepare_headers = prepare_unicode_headers
58
+
53
59
logger = logging .getLogger ("swiftclient" )
54
60
logger .addHandler (NullHandler ())
55
61
@@ -124,68 +130,93 @@ def encode_utf8(value):
124
130
from json import loads as json_loads
125
131
126
132
127
- def http_connection (url , proxy = None , ssl_compression = True ):
128
- """
129
- Make an HTTPConnection or HTTPSConnection
133
+ class HTTPConnection :
134
+ def __init__ (self , url , proxy = None , cacert = None , insecure = False ,
135
+ ssl_compression = False ):
136
+ """
137
+ Make an HTTPConnection or HTTPSConnection
138
+
139
+ :param url: url to connect to
140
+ :param proxy: proxy to connect through, if any; None by default; str
141
+ of the format 'http://127.0.0.1:8888' to set one
142
+ :param cacert: A CA bundle file to use in verifying a TLS server
143
+ certificate.
144
+ :param insecure: Allow to access servers without checking SSL certs.
145
+ The server's certificate will not be verified.
146
+ :param ssl_compression: SSL compression should be disabled by default
147
+ and this setting is not usable as of now. The
148
+ parameter is kept for backward compatibility.
149
+ :raises ClientException: Unable to handle protocol scheme
150
+ """
151
+ self .url = url
152
+ self .parsed_url = urlparse (url )
153
+ self .host = self .parsed_url .netloc
154
+ self .port = self .parsed_url .port
155
+ self .requests_args = {}
156
+ if self .parsed_url .scheme not in ('http' , 'https' ):
157
+ raise ClientException ("Unsupported scheme" )
158
+ self .requests_args ['verify' ] = not insecure
159
+ if cacert :
160
+ # verify requests parameter is used to pass the CA_BUNDLE file
161
+ # see: http://docs.python-requests.org/en/latest/user/advanced/
162
+ self .requests_args ['verify' ] = cacert
163
+ if proxy :
164
+ proxy_parsed = urlparse (proxy )
165
+ if not proxy_parsed .scheme :
166
+ raise ClientException ("Proxy's missing scheme" )
167
+ self .requests_args ['proxies' ] = {
168
+ proxy_parsed .scheme : '%s://%s' % (
169
+ proxy_parsed .scheme , proxy_parsed .netloc
170
+ )
171
+ }
172
+ self .requests_args ['stream' ] = True
173
+
174
+ def _request (self , * arg , ** kwarg ):
175
+ """ Final wrapper before requests call, to be patched in tests """
176
+ return requests .request (* arg , ** kwarg )
177
+
178
+ def request (self , method , full_path , data = None , headers = {}, files = None ):
179
+ """ Encode url and header, then call requests.request """
180
+ headers = dict ((encode_utf8 (x ), encode_utf8 (y )) for x , y in
181
+ headers .iteritems ())
182
+ url = encode_utf8 ("%s://%s%s" % (
183
+ self .parsed_url .scheme ,
184
+ self .parsed_url .netloc ,
185
+ full_path ))
186
+ self .resp = self ._request (method , url , headers = headers , data = data ,
187
+ files = files , ** self .requests_args )
188
+ return self .resp
189
+
190
+ def putrequest (self , full_path , data = None , headers = {}, files = None ):
191
+ """
192
+ Use python-requests files upload
130
193
131
- :param url: url to connect to
132
- :param proxy: proxy to connect through, if any; None by default; str of the
133
- format 'http://127.0.0.1:8888' to set one
134
- :param ssl_compression: Whether to enable compression at the SSL layer.
135
- If set to 'False' and the pyOpenSSL library is
136
- present an attempt to disable SSL compression
137
- will be made. This may provide a performance
138
- increase for https upload/download operations.
139
- :returns: tuple of (parsed url, connection object)
140
- :raises ClientException: Unable to handle protocol scheme
141
- """
142
- url = encode_utf8 (url )
143
- parsed = urlparse (url )
144
- if proxy :
145
- proxy_parsed = urlparse (proxy )
146
- else :
147
- proxies = get_environ_proxies (parsed .netloc )
148
- proxy = proxies .get (parsed .scheme , None )
149
- proxy_parsed = urlparse (proxy ) if proxy else None
150
- host = proxy_parsed .netloc if proxy else parsed .netloc
151
- if parsed .scheme == 'http' :
152
- conn = HTTPConnection (host )
153
- elif parsed .scheme == 'https' :
154
- if ssl_compression is True :
155
- conn = HTTPSConnection (host )
156
- else :
157
- conn = HTTPSConnectionNoSSLComp (host )
158
- else :
159
- raise ClientException ('Cannot handle protocol scheme %s for url %s' %
160
- (parsed .scheme , repr (url )))
161
-
162
- def putheader_wrapper (func ):
163
-
164
- @wraps (func )
165
- def putheader_escaped (key , value ):
166
- func (encode_utf8 (key ), encode_utf8 (value ))
167
- return putheader_escaped
168
- conn .putheader = putheader_wrapper (conn .putheader )
169
-
170
- def request_wrapper (func ):
171
-
172
- @wraps (func )
173
- def request_escaped (method , url , body = None , headers = None ):
174
- validate_headers (headers )
175
- url = encode_utf8 (url )
176
- if body :
177
- body = encode_utf8 (body )
178
- func (method , url , body = body , headers = headers or {})
179
- return request_escaped
180
- conn .request = request_wrapper (conn .request )
181
- if proxy :
182
- try :
183
- # python 2.6 method
184
- conn ._set_tunnel (parsed .hostname , parsed .port )
185
- except AttributeError :
186
- # python 2.7 method
187
- conn .set_tunnel (parsed .hostname , parsed .port )
188
- return parsed , conn
194
+ :param data: Use data generator for chunked-transfer
195
+ :param files: Use files for default transfer
196
+ """
197
+ return self .request ('PUT' , full_path , data , headers , files )
198
+
199
+ def getresponse (self ):
200
+ """ Adapt requests response to httplib interface """
201
+ self .resp .status = self .resp .status_code
202
+ old_getheader = self .resp .raw .getheader
203
+
204
+ def getheaders ():
205
+ return self .resp .headers .items ()
206
+
207
+ def getheader (k , v = None ):
208
+ return old_getheader (k .lower (), v )
209
+
210
+ self .resp .getheaders = getheaders
211
+ self .resp .getheader = getheader
212
+ self .resp .read = self .resp .raw .read
213
+ return self .resp
214
+
215
+
216
+ def http_connection (* arg , ** kwarg ):
217
+ """ :returns: tuple of (parsed url, connection object) """
218
+ conn = HTTPConnection (* arg , ** kwarg )
219
+ return conn .parsed_url , conn
189
220
190
221
191
222
def get_auth_1_0 (url , user , key , snet ):
@@ -890,27 +921,16 @@ def put_object(url, token=None, container=None, name=None, contents=None,
890
921
if hasattr (contents , 'read' ):
891
922
if chunk_size is None :
892
923
chunk_size = 65536
893
- conn .putrequest ('PUT' , path )
894
- for header , value in headers .iteritems ():
895
- conn .putheader (header , value )
896
924
if content_length is None :
897
- conn . putheader ( 'Transfer-Encoding' , 'chunked' )
898
- conn . endheaders ()
899
- chunk = contents .read (chunk_size )
900
- while chunk :
901
- conn . send ( '%x \r \n %s \r \n ' % ( len ( chunk ), chunk ))
902
- chunk = contents . read ( chunk_size )
903
- conn .send ( '0 \r \n \r \n ' )
925
+ def chunk_reader ():
926
+ while True :
927
+ data = contents .read (chunk_size )
928
+ if not data :
929
+ break
930
+ yield data
931
+ conn .putrequest ( path , headers = headers , data = chunk_reader () )
904
932
else :
905
- conn .endheaders ()
906
- left = content_length
907
- while left > 0 :
908
- size = chunk_size
909
- if size > left :
910
- size = left
911
- chunk = contents .read (size )
912
- conn .send (chunk )
913
- left -= len (chunk )
933
+ conn .putrequest (path , headers = headers , files = {"file" : contents })
914
934
else :
915
935
if chunk_size is not None :
916
936
warn_msg = '%s object has no \" read\" method, ignoring chunk_size' \
@@ -1129,6 +1149,8 @@ def get_auth(self):
1129
1149
1130
1150
def http_connection (self ):
1131
1151
return http_connection (self .url ,
1152
+ cacert = self .cacert ,
1153
+ insecure = self .insecure ,
1132
1154
ssl_compression = self .ssl_compression )
1133
1155
1134
1156
def _add_response_dict (self , target_dict , kwargs ):
@@ -1160,7 +1182,9 @@ def _retry(self, reset_func, func, *args, **kwargs):
1160
1182
rv = func (self .url , self .token , * args , ** kwargs )
1161
1183
self ._add_response_dict (caller_response_dict , kwargs )
1162
1184
return rv
1163
- except (socket .error , HTTPException ) as e :
1185
+ except SSLError :
1186
+ raise
1187
+ except (socket .error , RequestException ) as e :
1164
1188
self ._add_response_dict (caller_response_dict , kwargs )
1165
1189
if self .attempts > self .retries :
1166
1190
logger .exception (e )
0 commit comments