Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / connection / kamakicon.py @ 1f417830

History | View | Annotate | Download (6.2 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
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 unicode(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 = ('?' if i == 0 else '&') + unicode(key)
127
            if val is not None:
128
                param_str += '=' + unicode(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(self,
138
        method=None,
139
        data=None,
140
        async_headers={},
141
        async_params={}):
142
        """
143
        :param method: (str) http method ('get', 'post', etc.)
144

145
        :param data: (binary object)
146

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

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

155
        :returns: (KamakiHTTPResponse) a response object
156

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

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

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