Add snf-common dependency
[kamaki] / kamaki / clients / connection / request.py
1 # Copyright 2011-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 import requests
35 from kamaki.clients.connection import HTTPConnection, HTTPResponse, HTTPConnectionError
36 from kamaki.clients.connection.pool import ObjectPool
37 from urlparse import urlparse
38
39 # Add a convenience status property to the responses
40 def _status(self):
41         return requests.status_codes._codes[self.status_code][0].upper()
42 requests.Response.status = property(_status)
43
44 class HTTPRequestsResponse(HTTPResponse):
45
46         def __init__(self, request=None, prefetched=False):
47                 super(HTTPRequestsResponse, self).__init__(request=request, prefetched=prefetched)
48                 if prefetched:
49                         self = request.response
50
51         def _get_response(self):
52                 if self.prefetched:
53                         return
54                 r = self.request.response
55                 try:
56                         self.headers = r.headers
57                         self.status = r.status
58                         self.status_code = r.status_code
59                         self.content = r.content if hasattr(r, 'content') else None
60                         from json import loads
61                         try:
62                                 self.json = loads(r.content)#None if self._get_content_only else r.json
63                         except ValueError:
64                                 self.json = None
65                         self.text = r.content#None if self._get_content_only else r.text
66                         self.exception = r.exception if hasattr(r, 'exception') else None
67                 except requests.ConnectionError as err:
68                         raise HTTPConnectionError('Connection error', status=651, details=err.message)
69                 except requests.HTTPError as err:
70                         raise HTTPConnectionError('HTTP error', status=652, details=err.message)
71                 except requests.Timeout as err:
72                         raise HTTPConnectionError('Connection Timeout', status=408, details=err.message)
73                 except requests.URLRequired as err:
74                         raise HTTPConnectionError('Invalid URL', status=404, details=err.message)
75                 except requests.RequestException as err:
76                         raise HTTPConnectionError('HTTP Request error', status=700, details=err.message)
77                 self.prefetched=True
78
79         def release(self):
80                 """requests object handles this automatically"""
81                 if hasattr(self, '_pool'):
82                         self._pool.pool_put(self)
83
84 POOL_SIZE=8
85 class HTTPRequestsResponsePool(ObjectPool):
86         def __init__(self, netloc, size=POOL_SIZE):
87                 super(HTTPRequestsResponsePool, self).__init__(size=size)
88                 self.netloc = netloc
89
90         def _pool_cleanup(self, resp):
91                 resp._get_response()
92                 return True
93
94         @classmethod
95         def key(self, full_url):
96                 p = urlparse(full_url)
97                 return '%s:%s:%s'%(p.scheme,p.netloc, p.port)
98
99         def _pool_create(self):
100                 resp = HTTPRequestsResponse()
101                 resp._pool = self
102                 return resp
103
104 class HTTPRequest(HTTPConnection):
105
106         _pools = {}
107
108         #Avoid certificate verification by default
109         verify = False
110
111         def _get_response_object(self):
112                 pool_key = HTTPRequestsResponsePool.key(self.url)
113                 try:
114                         respool = self._pools[pool_key]
115                 except KeyError:
116                         self._pools[pool_key] = HTTPRequestsResponsePool(pool_key)
117                         respool = self._pools[pool_key]
118                 return respool.pool_get()
119
120         def perform_request(self, method=None, url=None, params=None, headers=None, data=None):
121                 """perform a request
122                 Example: method='PUT' url='https://my.server:8080/path/to/service'
123                         params={'update':None, 'format':'json'} headers={'X-Auth-Token':'s0m3t0k3n=='}
124                         data='The data body to put to server'
125                 @return an HTTPResponse which is also stored as self.response
126                 """
127                 if method is not None:
128                         self.method = method
129                 if url is not None:
130                         self.url = url
131                 if params is not None:
132                         self.params = params
133                 if headers is not None:
134                         self.headers = headers
135                 http_headers = {}
136                 for k,v in self.headers.items():
137                         http_headers[str(k)] = str(v)
138
139                 for i,(key, val) in enumerate(self.params.items()):
140                         param_str = ('?' if i == 0 else '&') + unicode(key) 
141                         if val is not None:
142                                 param_str+= '='+unicode(val)
143                         self.url += param_str
144
145                 #use pool before request, so that it will block if pool is full
146                 res = self._get_response_object()
147                 self._response_object = requests.request(str(self.method),
148                         str(self.url), headers=http_headers, data=data,
149                         verify=self.verify, prefetch = False)
150                 res.request = self._response_object.request
151                 return res