Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.4 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 kamaki.clients.connection import HTTPConnection, HTTPResponse
37
from kamaki.clients.connection.errors import HTTPConnectionError
38
from kamaki.clients.connection.errors import HTTPResponseError
39
from socket import gaierror, error
40

    
41
from json import loads
42

    
43
from time import sleep
44
from httplib import ResponseNotReady
45

    
46

    
47
class KamakiHTTPResponse(HTTPResponse):
48

    
49
    def _get_response(self):
50
        if self.prefetched:
51
            return
52

    
53
        ready = False
54
        while not ready:
55
            try:
56
                r = self.request.getresponse()
57
            except ResponseNotReady:
58
                sleep(0.001)
59
                continue
60
            break
61
        self.prefetched = True
62
        headers = {}
63
        for k, v in r.getheaders():
64
            headers.update({k: v})
65
        self.headers = headers
66
        self.content = r.read()
67
        self.status_code = r.status
68
        self.status = r.reason
69
        self.request.close()
70

    
71
    @property
72
    def text(self):
73
        """
74
        :returns: (str) content
75
        """
76
        self._get_response()
77
        return '%s' % self._content
78

    
79
    @text.setter
80
    def test(self, v):
81
        pass
82

    
83
    @property
84
    def json(self):
85
        """
86
        :returns: (dict) the json-formated content
87

88
        :raises HTTPResponseError: if content is not json formated
89
        """
90
        self._get_response()
91
        try:
92
            return loads(self._content)
93
        except ValueError as err:
94
            HTTPResponseError('Response not formated in JSON - %s' % err)
95

    
96
    @json.setter
97
    def json(self, v):
98
        pass
99

    
100
    def release(self):
101
        """ Release the connection. Should always be called if the response
102
        content hasn't been used.
103
        """
104
        if not self.prefetched:
105
            self.request.close()
106

    
107

    
108
class KamakiHTTPConnection(HTTPConnection):
109

    
110
    def _retrieve_connection_info(self, extra_params={}):
111
        """
112
        :param extra_params: (dict) key:val for url parameters
113

114
        :returns: (scheme, netloc, url?with&params)
115
        """
116
        if self.url:
117
            url = self.url if self.url[-1] == '/' else (self.url + '/')
118
        else:
119
            url = 'http://127.0.0.1'
120
        if self.path:
121
            url += self.path[1:] if self.path[0] == '/' else self.path
122
        params = dict(self.params)
123
        for k, v in extra_params.items():
124
            params[k] = v
125
        for i, (key, val) in enumerate(params.items()):
126
            param_str = '%s%s' % ('?' if i == 0 else '&', key)
127
            if val is not None:
128
                param_str += '=%s' % val
129
            url += param_str
130

    
131
        parsed = urlparse(url)
132
        self.url = url
133
        self.path = parsed.path if parsed.path else '/'
134
        self.path += '?%s' % parsed.query if parsed.query else ''
135
        return (parsed.scheme, parsed.netloc)
136

    
137
    def perform_request(
138
            self,
139
            method=None, data=None, async_headers={}, async_params={}):
140
        """
141
        :param method: (str) http method ('get', 'post', etc.)
142

143
        :param data: (binary object)
144

145
        :param async_headers: (dict) key:val headers that are used only for one
146
            request instance as opposed to self.headers, which remain to be
147
            used by following or parallel requests
148

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

153
        :returns: (KamakiHTTPResponse) a response object
154

155
        :raises HTTPConnectionError: Connection failures
156
        """
157
        (scheme, netloc) = self._retrieve_connection_info(
158
            extra_params=async_params)
159
        headers = dict(self.headers)
160
        for k, v in async_headers.items():
161
            headers[k] = v
162

    
163
        #de-unicode headers to prepare them for http
164
        http_headers = {}
165
        for k, v in headers.items():
166
            http_headers[str(k)] = str(v)
167

    
168
        #get connection from pool
169
        try:
170
            conn = get_http_connection(netloc=netloc, scheme=scheme)
171
        except ValueError as ve:
172
            raise HTTPConnectionError(
173
                'Cannot establish connection to %s %s' % (self.url, ve),
174
                errno=-1)
175
        try:
176
            #Be carefull, all non-body variables should not be unicode
177
            conn.request(
178
                method=str(method.upper()),
179
                url=str(self.path),
180
                headers=http_headers,
181
                body=data)
182
        except IOError as ioe:
183
            raise HTTPConnectionError(
184
                'Cannot connect to %s: %s' % (self.url, ioe.strerror),
185
                errno=ioe.errno)
186
        except Exception as err:
187
            from traceback import format_stack
188
            from kamaki.clients import recvlog
189
            recvlog.debug('\n'.join(['%s' % type(err)] + format_stack()))
190
            conn.close()
191
            raise
192
        return KamakiHTTPResponse(conn)