Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / connection / kamakicon.py @ 023230b5

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
def _encode(v):
50
    if v and isinstance(v, unicode):
51
        return quote(v.encode('utf-8'))
52
    return v
53

    
54

    
55
class KamakiHTTPResponse(KamakiResponse):
56

    
57
    def _get_response(self):
58
        if self.prefetched:
59
            return
60

    
61
        try:
62
            while True:
63
                try:
64
                    r = self.request.getresponse()
65
                except ResponseNotReady:
66
                    sleep(0.001)
67
                else:
68
                    break
69
            self.prefetched = True
70
            headers = {}
71
            for k, v in r.getheaders():
72
                headers.update({k: v})
73
            self.headers = headers
74
            self.content = r.read()
75
            self.status_code = r.status
76
            self.status = r.reason
77
        finally:
78
            try:
79
                self.request.close()
80
            except Exception as err:
81
                from kamaki.clients import recvlog
82
                recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
83
                raise
84

    
85
    @property
86
    def text(self):
87
        """
88
        :returns: (str) content
89
        """
90
        self._get_response()
91
        return '%s' % self._content
92

    
93
    @text.setter
94
    def text(self, v):
95
        pass
96

    
97
    @property
98
    def json(self):
99
        """
100
        :returns: (dict) the json-formated content
101

102
        :raises KamakiResponseError: if content is not json formated
103
        """
104
        self._get_response()
105
        try:
106
            return loads(self._content)
107
        except ValueError as err:
108
            KamakiResponseError('Response not formated in JSON - %s' % err)
109

    
110
    @json.setter
111
    def json(self, v):
112
        pass
113

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

    
126

    
127
class KamakiHTTPConnection(KamakiConnection):
128

    
129
    def _retrieve_connection_info(self, extra_params={}):
130
        """ Set self.url to scheme://netloc/?params
131
        :param extra_params: (dict) key:val for url parameters
132

133
        :returns: (scheme, netloc)
134
        """
135
        if self.url:
136
            url = self.url if self.url[-1] == '/' else (self.url + '/')
137
        else:
138
            url = 'http://127.0.0.1'
139
        if self.path:
140
            url += self.path[1:] if self.path[0] == '/' else self.path
141
        params = dict(self.params)
142
        params.update(extra_params)
143
        for i, (key, val) in enumerate(params.items()):
144
            val = _encode(val)
145
            url += '%s%s' % ('&' if i else '?', key)
146
            if val:
147
                url += '=%s' % val
148

    
149
        parsed = urlparse(url)
150
        self.url = url
151
        self.path = parsed.path or '/'
152
        if parsed.query:
153
            self.path += '?%s' % parsed.query
154
        return (parsed.scheme, parsed.netloc)
155

    
156
    def perform_request(
157
            self,
158
            method=None, data=None, async_headers={}, async_params={}):
159
        """
160
        :param method: (str) http method ('get', 'post', etc.)
161

162
        :param data: (binary object)
163

164
        :param async_headers: (dict) key:val headers that are used only for one
165
            request instance as opposed to self.headers, which remain to be
166
            used by following or parallel requests
167

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

172
        :returns: (KamakiHTTPResponse) a response object
173

174
        :raises KamakiConnectionError: Connection failures
175
        """
176
        (scheme, netloc) = self._retrieve_connection_info(async_params)
177
        headers = dict(self.headers)
178
        for k, v in async_headers.items():
179
            v = _encode(v)
180
            headers[k] = v
181

    
182
        #de-unicode headers to prepare them for http
183
        http_headers = {}
184
        for k, v in headers.items():
185
            v = _encode(v)
186
            http_headers[k] = v
187

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