Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / connection / kamakicon.py @ 226df276

History | View | Annotate | Download (6.8 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, self.list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, self.list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from urlparse import urlparse
35
from objpool.http import get_http_connection
36
from traceback import format_stack
37

    
38
from kamaki.clients.connection import HTTPConnection, HTTPResponse
39
from kamaki.clients.connection.errors import HTTPConnectionError
40
from kamaki.clients.connection.errors import HTTPResponseError
41
from kamaki.clients import recvlog
42

    
43
from json import loads
44

    
45
from time import sleep
46
from httplib import ResponseNotReady
47

    
48

    
49
class KamakiHTTPResponse(HTTPResponse):
50

    
51
    def _get_response(self):
52
        if self.prefetched:
53
            return
54

    
55
        try:
56
            ready = False
57
            while not ready:
58
                try:
59
                    r = self.request.getresponse()
60
                except ResponseNotReady:
61
                    sleep(0.001)
62
                    continue
63
                break
64
            self.prefetched = True
65
            headers = {}
66
            for k, v in r.getheaders():
67
                headers.update({k: v})
68
            self.headers = headers
69
            self.content = r.read()
70
            self.status_code = r.status
71
            self.status = r.reason
72
        finally:
73
            try:
74
                self.request.close()
75
            except Exception as err:
76
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
77
                raise
78

    
79
    @property
80
    def text(self):
81
        """
82
        :returns: (str) content
83
        """
84
        self._get_response()
85
        return '%s' % self._content
86

    
87
    @text.setter
88
    def test(self, v):
89
        pass
90

    
91
    @property
92
    def json(self):
93
        """
94
        :returns: (dict) the json-formated content
95

96
        :raises HTTPResponseError: if content is not json formated
97
        """
98
        self._get_response()
99
        try:
100
            return loads(self._content)
101
        except ValueError as err:
102
            HTTPResponseError('Response not formated in JSON - %s' % err)
103

    
104
    @json.setter
105
    def json(self, v):
106
        pass
107

    
108
    def release(self):
109
        """ Release the connection. Should always be called if the response
110
        content hasn't been used.
111
        """
112
        if not self.prefetched:
113
            try:
114
                self.request.close()
115
            except Exception as err:
116
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
117
                raise
118

    
119

    
120
class KamakiHTTPConnection(HTTPConnection):
121

    
122
    def _retrieve_connection_info(self, extra_params={}):
123
        """
124
        :param extra_params: (dict) key:val for url parameters
125

126
        :returns: (scheme, netloc, url?with&params)
127
        """
128
        if self.url:
129
            url = self.url if self.url[-1] == '/' else (self.url + '/')
130
        else:
131
            url = 'http://127.0.0.1'
132
        if self.path:
133
            url += self.path[1:] if self.path[0] == '/' else self.path
134
        params = dict(self.params)
135
        for k, v in extra_params.items():
136
            params[k] = v
137
        for i, (key, val) in enumerate(params.items()):
138
            param_str = '%s%s' % ('?' if i == 0 else '&', key)
139
            if val is not None:
140
                param_str += '=%s' % val
141
            url += param_str
142

    
143
        parsed = urlparse(url)
144
        self.url = url
145
        self.path = parsed.path if parsed.path else '/'
146
        self.path += '?%s' % parsed.query if parsed.query else ''
147
        return (parsed.scheme, parsed.netloc)
148

    
149
    def perform_request(
150
            self,
151
            method=None, data=None, async_headers={}, async_params={}):
152
        """
153
        :param method: (str) http method ('get', 'post', etc.)
154

155
        :param data: (binary object)
156

157
        :param async_headers: (dict) key:val headers that are used only for one
158
            request instance as opposed to self.headers, which remain to be
159
            used by following or parallel requests
160

161
        :param async_params: (dict) key:val url parameters that are used only
162
            for one request instance as opposed to self.params, which remain to
163
            be used by following or parallel requests
164

165
        :returns: (KamakiHTTPResponse) a response object
166

167
        :raises HTTPConnectionError: Connection failures
168
        """
169
        (scheme, netloc) = self._retrieve_connection_info(
170
            extra_params=async_params)
171
        headers = dict(self.headers)
172
        for k, v in async_headers.items():
173
            headers[k] = v
174

    
175
        #de-unicode headers to prepare them for http
176
        http_headers = {}
177
        for k, v in headers.items():
178
            http_headers[str(k)] = str(v)
179

    
180
        #get connection from pool
181
        try:
182
            conn = get_http_connection(netloc=netloc, scheme=scheme)
183
        except ValueError as ve:
184
            raise HTTPConnectionError(
185
                'Cannot establish connection to %s %s' % (self.url, ve),
186
                errno=-1)
187
        try:
188
            #Be carefull, all non-body variables should not be unicode
189
            conn.request(
190
                method=str(method.upper()),
191
                url=str(self.path),
192
                headers=http_headers,
193
                body=data)
194
        except IOError as ioe:
195
            raise HTTPConnectionError(
196
                'Cannot connect to %s: %s' % (self.url, ioe.strerror),
197
                errno=ioe.errno)
198
        except Exception as err:
199
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
200
            conn.close()
201
            raise
202
        return KamakiHTTPResponse(conn)