1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 '''
21 Module which incorporates the WSGI integration.
22
23 Created on 22.11.2011
24
25 @author: tmetsch
26
27 '''
28
29
30
31
32 from occi import VERSION
33 from occi.backend import KindBackend, MixinBackend, ActionBackend
34 from occi.exceptions import HTTPError
35 from occi.handlers import QUERY_STRING
36 from occi.handlers import QueryHandler, CollectionHandler, ResourceHandler, \
37 CATEGORY, LINK, ATTRIBUTE, LOCATION, ACCEPT, CONTENT_TYPE
38 from occi.protocol.html_rendering import HTMLRendering
39 from occi.protocol.json_rendering import JsonRendering
40 from occi.protocol.occi_rendering import TextOcciRendering, \
41 TextPlainRendering, TextUriListRendering
42 from occi.registry import NonePersistentRegistry
43 import StringIO
44 import logging
45
46 RETURN_CODES = {201: '201 Created',
47 200: '200 OK',
48 400: '400 Bad Request',
49 403: '403 Forbidden',
50 404: '404 Not Found',
51 405: '405 Method Not Allowed',
52 406: '406 Not Acceptable',
53 500: '500 Internal Server Error',
54 501: '501 Not implemented'}
55
56
58 '''
59 Will parse the HTTP Headers and only return those who are needed for
60 the OCCI service.
61
62 Also translates the WSGI notion of the Header field names to those used
63 by OCCI.
64
65 environ -- The WSGI environ
66 '''
67 headers = {}
68
69 if 'HTTP_CATEGORY'in environ.keys():
70 headers[CATEGORY] = environ['HTTP_CATEGORY']
71 if 'HTTP_LINK'in environ.keys():
72 headers[LINK] = environ['HTTP_LINK']
73 if 'HTTP_X_OCCI_ATTRIBUTE'in environ.keys():
74 headers[ATTRIBUTE] = environ['HTTP_X_OCCI_ATTRIBUTE']
75 if 'HTTP_X_OCCI_LOCATION'in environ.keys():
76 headers[LOCATION] = environ['HTTP_X_OCCI_LOCATION']
77 if 'HTTP_ACCEPT' in environ.keys():
78 headers[ACCEPT] = environ.get('HTTP_ACCEPT')
79 if 'CONTENT_TYPE' in environ.keys():
80 headers[CONTENT_TYPE] = environ.get('CONTENT_TYPE')
81 if 'QUERY_STRING' in environ.keys():
82 headers[QUERY_STRING] = environ.get('QUERY_STRING')
83
84 return headers
85
86
87 -def _parse_body(environ):
88 '''
89 Parse the body from the WSGI environ.
90
91 environ -- The WSGI environ.
92 '''
93 try:
94 length = int(environ.get('CONTENT_LENGTH', '0'))
95 body = StringIO.StringIO(environ['wsgi.input'].read(length))
96 return body.getvalue()
97 except (KeyError, ValueError):
98 return ''
99
100
102 '''
103 Parse the query from the WSGI environ.
104
105 environ -- The WSGI environ.
106 '''
107 tmp = environ.get('QUERY_STRING')
108 if tmp is not None:
109 try:
110 query = (tmp.split('=')[0], tmp.split('=')[1])
111 except IndexError:
112 query = ()
113 else:
114 query = ()
115 return query
116
117
119 '''
120 Set the hostname of the service.
121
122 environ -- The WSGI environ.
123 registry -- The OCCI registry.
124 '''
125
126 if 'HTTP_HOST' in environ.keys():
127 registry.set_hostname('http://' + environ['HTTP_HOST'])
128 else:
129
130 host = 'http://' + environ.get('SERVER_NAME') + ':'
131 host += environ.get('SERVER_PORT')
132 registry.set_hostname(host)
133
134
136 '''
137 An WSGI application for OCCI.
138 '''
139
140
141
142
143 - def __init__(self, registry=None, renderings=None):
167
169 '''
170 Register a backend.
171
172 Verifies that correct 'parent' backends are used.
173
174 category -- The category the backend defines.
175 backend -- The backend which handles the given category.
176 '''
177 allow = False
178 if repr(category) == 'kind' and isinstance(backend, KindBackend):
179 allow = True
180 elif repr(category) == 'mixin' and isinstance(backend, MixinBackend):
181 allow = True
182 elif repr(category) == 'action' and isinstance(backend, ActionBackend):
183 allow = True
184
185 if allow:
186 self.registry.set_backend(category, backend, None)
187 else:
188 raise AttributeError('Backends handling kinds need to derive'
189 ' from KindBackend; Backends handling'
190 ' actions need to derive from'
191 ' ActionBackend and backends handling'
192 ' mixins need to derive from MixinBackend.')
193
194 - def _call_occi(self, environ, response, **kwargs):
195 '''
196 Starts the overall OCCI part of the service. Needs to be called by the
197 __call__ function defined by an WSGI app.
198
199 environ -- The WSGI environ.
200 response -- The WESGI response.
201 kwargs -- keyworded arguments which will be forwarded to the backends.
202 '''
203 extras = kwargs.copy()
204
205
206 heads = _parse_headers(environ)
207
208
209 body = _parse_body(environ)
210
211
212 query = _parse_query(environ)
213
214 _set_hostname(environ, self.registry)
215
216
217 if environ['PATH_INFO'] == '/-/':
218 handler = QueryHandler(self.registry, heads, body, query, extras)
219 elif environ['PATH_INFO'] == '/.well-known/org/ogf/occi/-/':
220 handler = QueryHandler(self.registry, heads, body, query, extras)
221 elif environ['PATH_INFO'].endswith('/'):
222 handler = CollectionHandler(self.registry, heads, body, query,
223 extras)
224 else:
225 handler = ResourceHandler(self.registry, heads, body, query,
226 extras)
227
228
229 mtd = environ['REQUEST_METHOD']
230 try:
231 key = environ['PATH_INFO']
232 status, headers, body = handler.handle(mtd, key)
233 del handler
234 except HTTPError as err:
235 status = err.code
236 headers = {CONTENT_TYPE: 'text/plain',
237 'Content-Length': len(err.message)}
238 body = err.message
239 logging.error(body)
240
241
242 headers['Server'] = VERSION
243 headers['Content-length'] = str(len(body))
244
245 code = RETURN_CODES[status]
246
247
248
249 response(code, [(str(k), str(v)) for k, v in headers.items()])
250 return [str(body), ]
251
253 '''
254 Will be called as defined by WSGI.
255
256 environ -- The environ.
257 response -- The response.
258 '''
259 return self._call_occi(environ, response)
260