Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / connection / kamakicon.py @ b04a1abf

History | View | Annotate | Download (7 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 urllib2 import quote
36
from objpool.http import get_http_connection
37
from traceback import format_stack
38

    
39
from kamaki.clients.connection import KamakiConnection, KamakiResponse
40
from kamaki.clients.connection.errors import KamakiConnectionError
41
from kamaki.clients.connection.errors import KamakiResponseError
42

    
43
from json import loads
44

    
45
from time import sleep
46
from httplib import ResponseNotReady
47

    
48

    
49
class KamakiHTTPResponse(KamakiResponse):
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
                from kamaki.clients import recvlog
77
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
78
                raise
79

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

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

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

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

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

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

    
121

    
122
class KamakiHTTPConnection(KamakiConnection):
123

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

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

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

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

156
        :param data: (binary object)
157

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

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

166
        :returns: (KamakiHTTPResponse) a response object
167

168
        :raises KamakiConnectionError: Connection failures
169
        """
170
        (scheme, netloc) = self._retrieve_connection_info(async_params)
171
        headers = dict(self.headers)
172
        for k, v in async_headers.items():
173
            v = quote(v.encode('utf-8')) if isinstance(v, unicode) else str(v)
174
            headers[k] = v
175

    
176
        #de-unicode headers to prepare them for http
177
        http_headers = {}
178
        for k, v in headers.items():
179
            v = quote(v.encode('utf-8')) if isinstance(v, unicode) else str(v)
180
            http_headers[k] = v
181

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