1#------------------------------------------------------------------------- 2# Copyright (c) Microsoft. All rights reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14#-------------------------------------------------------------------------- 15import base64 16import sys 17 18if sys.version_info < (3,): 19 from httplib import ( 20 HTTP_PORT, 21 HTTPS_PORT, 22 ) 23 from urllib2 import quote as url_quote 24else: 25 from http.client import ( 26 HTTP_PORT, 27 HTTPS_PORT, 28 ) 29 from urllib.parse import quote as url_quote 30 31from . import HTTPError, HTTPResponse 32from .._serialization import _get_data_bytes_or_stream_only 33 34class _HTTPClient(object): 35 ''' 36 Takes the request and sends it to cloud service and returns the response. 37 ''' 38 39 def __init__(self, protocol=None, session=None, timeout=None): 40 ''' 41 :param str protocol: 42 http or https. 43 :param requests.Session request_session: 44 session object created with requests library (or compatible). 45 :param int timeout: 46 timeout for the http request, in seconds. 47 ''' 48 self.protocol = protocol 49 self.session = session 50 self.timeout = timeout 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 62 63 def set_proxy(self, host, port, user, password): 64 ''' 65 Sets the proxy server host and port for the HTTP CONNECT Tunnelling. 66 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: 73 Address of the proxy. Ex: '192.168.0.100' 74 :param int port: 75 Port of the proxy. Ex: 6000 76 :param str user: 77 User for proxy authorization. 78 :param str password: 79 Password for proxy authorization. 80 ''' 81 if user and password: 82 proxy_string = '{}:{}@{}:{}'.format(user, password, host, port) 83 else: 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) 89 90 def perform_request(self, request): 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 or either a file-like/stream object 101 if request.body: 102 request.body = _get_data_bytes_or_stream_only('request.body', request.body) 103 104 # Construct the URI 105 uri = self.protocol.lower() + '://' + request.host + request.path 106 107 # Send the request 108 response = self.session.request(request.method, 109 uri, 110 params=request.query, 111 headers=request.headers, 112 data=request.body or None, 113 timeout=self.timeout, 114 proxies=self.proxies) 115 116 # Parse the response 117 status = int(response.status_code) 118 respheaders = {} 119 for key, name in response.headers.items(): 120 respheaders[key.lower()] = name 121 122 wrap = HTTPResponse(status, response.reason, respheaders, response.content) 123 response.close() 124 125 return wrap 126