2020 HTTP_PORT ,
2121 HTTPS_PORT ,
2222 )
23- from urlparse import urlparse
2423 from urllib2 import quote as url_quote
2524else :
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
3331from . import HTTPError , HTTPResponse
34- from .requestsclient import _RequestsConnection
35-
36-
37- DEBUG_REQUESTS = False
38- DEBUG_RESPONSES = False
3932
4033class _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