File: httpclient.py

package info (click to toggle)
azure-multiapi-storage-python 0.5.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 23,652 kB
  • sloc: python: 222,802; sh: 61; makefile: 3
file content (226 lines) | stat: -rw-r--r-- 8,266 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#-------------------------------------------------------------------------
# Copyright (c) Microsoft.  All rights reserved.
#
# 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.
#--------------------------------------------------------------------------
import base64
import sys

if sys.version_info < (3,):
    from httplib import (
        HTTP_PORT,
        HTTPS_PORT,
        )
    from urlparse import urlparse
    from urllib2 import quote as url_quote
else:
    from http.client import (
        HTTP_PORT,
        HTTPS_PORT,
        )
    from urllib.parse import urlparse
    from urllib.parse import quote as url_quote

from . import HTTPError, HTTPResponse
from .requestsclient import _RequestsConnection


DEBUG_REQUESTS = False
DEBUG_RESPONSES = False

class _HTTPClient(object):

    '''
    Takes the request and sends it to cloud service and returns the response.
    '''

    def __init__(self, service_instance, cert_file=None, protocol='https',
                 request_session=None, timeout=None, user_agent=''):
        '''
        service_instance:
            service client instance.
        cert_file:
            certificate file name/location. This is only used in hosted
            service management.
        protocol:
            http or https.
        request_session:
            session object created with requests library (or compatible).
        timeout:
            timeout for the http request, in seconds.
        user_agent:
            user agent string to set in http header.
        '''
        self.service_instance = service_instance
        self.cert_file = cert_file
        self.protocol = protocol
        self.proxy_host = None
        self.proxy_port = None
        self.proxy_user = None
        self.proxy_password = None
        self.request_session = request_session
        self.timeout = timeout
        self.user_agent = user_agent

    def set_proxy(self, host, port, user, password):
        '''
        Sets the proxy server host and port for the HTTP CONNECT Tunnelling.

        host:
            Address of the proxy. Ex: '192.168.0.100'
        port:
            Port of the proxy. Ex: 6000
        user:
            User for proxy authorization.
        password:
            Password for proxy authorization.
        '''
        self.proxy_host = host
        self.proxy_port = port
        self.proxy_user = user
        self.proxy_password = password

    def get_uri(self, request):
        ''' Return the target uri for the request.'''
        protocol = request.protocol_override \
            if request.protocol_override else self.protocol
        protocol = protocol.lower()
        port = HTTP_PORT if protocol == 'http' else HTTPS_PORT
        return protocol + '://' + request.host + ':' + str(port) + request.path

    def get_connection(self, request):
        ''' Create connection for the request. '''
        protocol = request.protocol_override \
            if request.protocol_override else self.protocol
        protocol = protocol.lower()
        target_host = request.host
        target_port = HTTP_PORT if protocol == 'http' else HTTPS_PORT

        connection = _RequestsConnection(
            target_host, protocol, self.request_session, self.timeout)
        proxy_host = self.proxy_host
        proxy_port = self.proxy_port

        if self.proxy_host:
            headers = None
            if self.proxy_user and self.proxy_password:
                auth = base64.encodestring(
                    "{0}:{1}".format(self.proxy_user, self.proxy_password).encode()).rstrip()
                headers = {'Proxy-Authorization': 'Basic {0}'.format(auth.decode())}
            connection.set_tunnel(proxy_host, int(proxy_port), headers)

        return connection

    def send_request_headers(self, connection, request_headers):
        if self.proxy_host and self.request_session is None:
            for i in connection._buffer:
                if i.startswith(b"Host: "):
                    connection._buffer.remove(i)
            connection.putheader(
                'Host', "{0}:{1}".format(connection._tunnel_host,
                                            connection._tunnel_port))

        for name, value in request_headers:
            if value:
                connection.putheader(name, value)

        connection.putheader('User-Agent', self.user_agent)
        connection.endheaders()

    def send_request_body(self, connection, request_body):
        if request_body:
            assert isinstance(request_body, bytes)
            connection.send(request_body)
        else:
            connection.send(None)

    def _update_request_uri_query(self, request):
        '''pulls the query string out of the URI and moves it into
        the query portion of the request object.  If there are already
        query parameters on the request the parameters in the URI will
        appear after the existing parameters'''

        if '?' in request.path:
            request.path, _, query_string = request.path.partition('?')
            if query_string:
                query_params = query_string.split('&')
                for query in query_params:
                    if '=' in query:
                        name, _, value = query.partition('=')
                        request.query.append((name, value))

        request.path = url_quote(request.path, '/()$=\',')

        # add encoded queries to request.path.
        if request.query:
            request.path += '?'
            for name, value in request.query:
                if value is not None:
                    request.path += name + '=' + url_quote(value, '/()$=\',') + '&'
            request.path = request.path[:-1]

        return request.path, request.query

    def perform_request(self, request):
        ''' Sends request to cloud service server and return the response. '''
        connection = self.get_connection(request)
        try:
            connection.putrequest(request.method, request.path)

            self.send_request_headers(connection, request.headers)
            self.send_request_body(connection, request.body)

            if DEBUG_REQUESTS and request.body:
                print('request:')
                try:
                    print(request.body)
                except:
                    pass

            resp = connection.getresponse()
            status = int(resp.status)
            message = resp.reason
            respheaders = resp.getheaders()

            # for consistency across platforms, make header names lowercase
            for i, value in enumerate(respheaders):
                respheaders[i] = (value[0].lower(), value[1])

            respbody = None
            if resp.length is None:
                respbody = resp.read()
            elif resp.length > 0:
                respbody = resp.read(resp.length)

            if DEBUG_RESPONSES and respbody:
                print('response:')
                try:
                    print(respbody)
                except:
                    pass

            response = HTTPResponse(
                status, resp.reason, respheaders, respbody)
            if status == 307:
                new_url = urlparse(dict(headers)['location'])
                request.host = new_url.hostname
                request.path = new_url.path
                request.path, request.query = self._update_request_uri_query(request)
                return self.perform_request(request)
            if status >= 300:
                # This exception will be caught by the general error handler
                # and raised as an azure http exception
                raise HTTPError(status, message, respheaders, respbody)

            return response
        finally:
            connection.close()