Skip to content

Commit 11f8023

Browse files
committed
Merge pull request #160 from emgerner-msft/dev
remove unused code and flatten requests pipeline
2 parents 3c20f03 + d155c4d commit 11f8023

File tree

6 files changed

+131
-292
lines changed

6 files changed

+131
-292
lines changed

azure-storage-python.pyproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
<Compile Include="azure\storage\_error.py" />
6363
<Compile Include="azure\storage\_deserialization.py" />
6464
<Compile Include="azure\storage\_http\httpclient.py" />
65-
<Compile Include="azure\storage\_http\requestsclient.py" />
6665
<Compile Include="azure\storage\_http\__init__.py" />
6766
<Compile Include="azure\storage\_serialization.py" />
6867
<Compile Include="azure\storage\__init__.py" />

azure/storage/_http/__init__.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,18 @@
1515

1616
class HTTPError(Exception):
1717

18-
''' HTTP Exception when response status code >= 300 '''
18+
'''
19+
Represents an HTTP Exception when response status code >= 300.
20+
21+
:ivar int status:
22+
the status code of the response
23+
:ivar str message:
24+
the message
25+
:ivar bytes body:
26+
the body of the response
27+
'''
1928

2029
def __init__(self, status, message, respheader, respbody):
21-
'''Creates a new HTTPError with the specified status, message,
22-
response headers and body'''
2330
self.status = status
2431
self.respheader = respheader
2532
self.respbody = respbody
@@ -28,18 +35,18 @@ def __init__(self, status, message, respheader, respbody):
2835

2936
class HTTPResponse(object):
3037

31-
"""Represents a response from an HTTP request. An HTTPResponse has the
32-
following attributes:
38+
'''
39+
Represents a response from an HTTP request.
3340
34-
status:
41+
:ivar int status:
3542
the status code of the response
36-
message:
43+
:ivar str message:
3744
the message
38-
headers:
45+
:ivar list headers:
3946
the returned headers, as a list of (name, value) pairs
40-
body:
47+
:ivar bytes body:
4148
the body of the response
42-
"""
49+
'''
4350

4451
def __init__(self, status, message, headers, body):
4552
self.status = status
@@ -50,23 +57,21 @@ def __init__(self, status, message, headers, body):
5057

5158
class HTTPRequest(object):
5259

53-
'''Represents an HTTP Request. An HTTP Request consists of the following
54-
attributes:
55-
host:
60+
'''
61+
Represents an HTTP Request.
62+
63+
:ivar str host:
5664
the host name to connect to
57-
method:
65+
:ivar str method:
5866
the method to use to connect (string such as GET, POST, PUT, etc.)
59-
path:
67+
:ivar str path:
6068
the uri fragment
61-
query:
69+
:ivar list query:
6270
query parameters specified as a list of (name, value) pairs
63-
headers:
71+
:ivar list headers:
6472
header values specified as (name, value) pairs
65-
body:
73+
:ivar bytes body:
6674
the body of the request.
67-
protocol_override:
68-
specify to use this protocol instead of the global one stored in
69-
_HTTPClient.
7075
'''
7176

7277
def __init__(self):
@@ -76,4 +81,3 @@ def __init__(self):
7681
self.query = [] # list of (name, value)
7782
self.headers = [] # list of (header name, header value)
7883
self.body = ''
79-
self.protocol_override = None

azure/storage/_http/httpclient.py

Lines changed: 82 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -20,207 +20,121 @@
2020
HTTP_PORT,
2121
HTTPS_PORT,
2222
)
23-
from urlparse import urlparse
2423
from urllib2 import quote as url_quote
2524
else:
2625
from http.client import (
2726
HTTP_PORT,
2827
HTTPS_PORT,
2928
)
30-
from urllib.parse import urlparse
3129
from urllib.parse import quote as url_quote
3230

3331
from . import HTTPError, HTTPResponse
34-
from .requestsclient import _RequestsConnection
35-
36-
37-
DEBUG_REQUESTS = False
38-
DEBUG_RESPONSES = False
3932

4033
class _HTTPClient(object):
4134

4235
'''
4336
Takes the request and sends it to cloud service and returns the response.
4437
'''
4538

46-
def __init__(self, service_instance, cert_file=None, protocol='https',
47-
request_session=None, timeout=None, user_agent=''):
39+
def __init__(self, protocol=None, session=None, timeout=None):
4840
'''
49-
service_instance:
50-
service client instance.
51-
cert_file:
52-
certificate file name/location. This is only used in hosted
53-
service management.
54-
protocol:
41+
:param str protocol:
5542
http or https.
56-
request_session:
43+
:param requests.Session request_session:
5744
session object created with requests library (or compatible).
58-
timeout:
45+
:param int timeout:
5946
timeout for the http request, in seconds.
60-
user_agent:
61-
user agent string to set in http header.
6247
'''
63-
self.service_instance = service_instance
64-
self.cert_file = cert_file
6548
self.protocol = protocol
66-
self.proxy_host = None
67-
self.proxy_port = None
68-
self.proxy_user = None
69-
self.proxy_password = None
70-
self.request_session = request_session
49+
self.session = session
7150
self.timeout = timeout
72-
self.user_agent = user_agent
51+
52+
# By default, requests adds an Accept:*/* and Accept-Encoding to the session,
53+
# which causes issues with some Azure REST APIs. Removing these here gives us
54+
# the flexibility to add it back on a case by case basis.
55+
if 'Accept' in self.session.headers:
56+
del self.session.headers['Accept']
57+
58+
if 'Accept-Encoding' in self.session.headers:
59+
del self.session.headers['Accept-Encoding']
60+
61+
self.proxies = None
7362

7463
def set_proxy(self, host, port, user, password):
7564
'''
7665
Sets the proxy server host and port for the HTTP CONNECT Tunnelling.
7766
78-
host:
67+
Note that we set the proxies directly on the request later on rather than
68+
using the session object as requests has a bug where session proxy is ignored
69+
in favor of environment proxy. So, auth will not work unless it is passed
70+
directly when making the request as this overrides both.
71+
72+
:param str host:
7973
Address of the proxy. Ex: '192.168.0.100'
80-
port:
74+
:param int port:
8175
Port of the proxy. Ex: 6000
82-
user:
76+
:param str user:
8377
User for proxy authorization.
84-
password:
78+
:param str password:
8579
Password for proxy authorization.
8680
'''
87-
self.proxy_host = host
88-
self.proxy_port = port
89-
self.proxy_user = user
90-
self.proxy_password = password
91-
92-
def get_uri(self, request):
93-
''' Return the target uri for the request.'''
94-
protocol = request.protocol_override \
95-
if request.protocol_override else self.protocol
96-
protocol = protocol.lower()
97-
port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
98-
return protocol + '://' + request.host + ':' + str(port) + request.path
99-
100-
def get_connection(self, request):
101-
''' Create connection for the request. '''
102-
protocol = request.protocol_override \
103-
if request.protocol_override else self.protocol
104-
protocol = protocol.lower()
105-
target_host = request.host
106-
target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
107-
108-
connection = _RequestsConnection(
109-
target_host, protocol, self.request_session, self.timeout)
110-
proxy_host = self.proxy_host
111-
proxy_port = self.proxy_port
112-
113-
if self.proxy_host:
114-
headers = None
115-
if self.proxy_user and self.proxy_password:
116-
auth = base64.encodestring(
117-
"{0}:{1}".format(self.proxy_user, self.proxy_password).encode()).rstrip()
118-
headers = {'Proxy-Authorization': 'Basic {0}'.format(auth.decode())}
119-
connection.set_tunnel(proxy_host, int(proxy_port), headers)
120-
121-
return connection
122-
123-
def send_request_headers(self, connection, request_headers):
124-
if self.proxy_host and self.request_session is None:
125-
for i in connection._buffer:
126-
if i.startswith(b"Host: "):
127-
connection._buffer.remove(i)
128-
connection.putheader(
129-
'Host', "{0}:{1}".format(connection._tunnel_host,
130-
connection._tunnel_port))
131-
132-
for name, value in request_headers:
133-
if value:
134-
connection.putheader(name, value)
135-
136-
connection.putheader('User-Agent', self.user_agent)
137-
connection.endheaders()
138-
139-
def send_request_body(self, connection, request_body):
140-
if request_body:
141-
assert isinstance(request_body, bytes)
142-
connection.send(request_body)
81+
if user and password:
82+
proxy_string = '{}:{}@{}:{}'.format(user, password, host, port)
14383
else:
144-
connection.send(None)
145-
146-
def _update_request_uri_query(self, request):
147-
'''pulls the query string out of the URI and moves it into
148-
the query portion of the request object. If there are already
149-
query parameters on the request the parameters in the URI will
150-
appear after the existing parameters'''
151-
152-
if '?' in request.path:
153-
request.path, _, query_string = request.path.partition('?')
154-
if query_string:
155-
query_params = query_string.split('&')
156-
for query in query_params:
157-
if '=' in query:
158-
name, _, value = query.partition('=')
159-
request.query.append((name, value))
160-
161-
request.path = url_quote(request.path, '/()$=\',')
162-
163-
# add encoded queries to request.path.
164-
if request.query:
165-
request.path += '?'
166-
for name, value in request.query:
167-
if value is not None:
168-
request.path += name + '=' + url_quote(value, '/()$=\',') + '&'
169-
request.path = request.path[:-1]
170-
171-
return request.path, request.query
84+
proxy_string = '{}:{}'.format(host, port)
85+
86+
self.proxies = {}
87+
self.proxies['http'] = 'http://{}'.format(proxy_string)
88+
self.proxies['https'] = 'https://{}'.format(proxy_string)
17289

17390
def perform_request(self, request):
174-
''' Sends request to cloud service server and return the response. '''
175-
connection = self.get_connection(request)
176-
try:
177-
connection.putrequest(request.method, request.path)
178-
179-
self.send_request_headers(connection, request.headers)
180-
self.send_request_body(connection, request.body)
181-
182-
if DEBUG_REQUESTS and request.body:
183-
print('request:')
184-
try:
185-
print(request.body)
186-
except:
187-
pass
188-
189-
resp = connection.getresponse()
190-
status = int(resp.status)
191-
message = resp.reason
192-
respheaders = resp.getheaders()
193-
194-
# for consistency across platforms, make header names lowercase
195-
for i, value in enumerate(respheaders):
196-
respheaders[i] = (value[0].lower(), value[1])
197-
198-
respbody = None
199-
if resp.length is None:
200-
respbody = resp.read()
201-
elif resp.length > 0:
202-
respbody = resp.read(resp.length)
203-
204-
if DEBUG_RESPONSES and respbody:
205-
print('response:')
206-
try:
207-
print(respbody)
208-
except:
209-
pass
210-
211-
response = HTTPResponse(
212-
status, resp.reason, respheaders, respbody)
213-
if status == 307:
214-
new_url = urlparse(dict(headers)['location'])
215-
request.host = new_url.hostname
216-
request.path = new_url.path
217-
request.path, request.query = self._update_request_uri_query(request)
218-
return self.perform_request(request)
219-
if status >= 300:
220-
# This exception will be caught by the general error handler
221-
# and raised as an azure http exception
222-
raise HTTPError(status, message, respheaders, respbody)
223-
224-
return response
225-
finally:
226-
connection.close()
91+
'''
92+
Sends an HTTPRequest to Azure Storage and returns an HTTPResponse. If
93+
the response code indicates an error, raise an HTTPError.
94+
95+
:param HTTPRequest request:
96+
The request to serialize and send.
97+
:return: An HTTPResponse containing the parsed HTTP response.
98+
:rtype: :class:`~azure.storage._http.HTTPResponse`
99+
'''
100+
# Verify the body is in bytes
101+
if request.body:
102+
assert isinstance(request.body, bytes)
103+
104+
# Construct the URI
105+
uri = self.protocol.lower() + '://' + request.host + request.path
106+
107+
# Serialize the queries
108+
queries = {}
109+
for name, value in request.query:
110+
if value:
111+
queries[name] = value
112+
113+
# Serialize headers
114+
headers = {}
115+
for name, value in request.headers:
116+
if value:
117+
headers[name] = value
118+
119+
# Send the request
120+
response = self.session.request(request.method,
121+
uri,
122+
params=queries,
123+
headers=headers,
124+
data=request.body or None,
125+
timeout=self.timeout,
126+
proxies=self.proxies)
127+
128+
# Parse the response
129+
status = int(response.status_code)
130+
respheaders = []
131+
for key, name in response.headers.items():
132+
respheaders.append((key.lower(), name))
133+
134+
# Construct an error or a response based on status code
135+
if status >= 300:
136+
# This exception will be caught by the general error handler
137+
# and raised as an azure http exception
138+
raise HTTPError(status, response.reason, respheaders, response.content)
139+
else:
140+
return HTTPResponse(status, response.reason, respheaders, response.content)

0 commit comments

Comments
 (0)