Package occi :: Module wsgi
[hide private]
[frames] | no frames]

Source Code for Module occi.wsgi

  1  # coding=utf-8
 
  2  #
 
  3  # Copyright (C) 2010-2012 Platform Computing
 
  4  # Copyright (C) 2012 engjoy UG (haftungsbeschraenkt)
 
  5  #
 
  6  # This library is free software; you can redistribute it and/or
 
  7  # modify it under the terms of the GNU Lesser General Public
 
  8  # License as published by the Free Software Foundation; either
 
  9  # version 2.1 of the License, or (at your option) any later version.
 
 10  #
 
 11  # This library is distributed in the hope that it will be useful,
 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
 14  # Lesser General Public License for more details.
 
 15  #
 
 16  # You should have received a copy of the GNU Lesser General Public
 
 17  # License along with this library; if not, write to the Free Software
 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 
 19  #
 
 20  '''
 
 21  Module which incorporates the WSGI integration.
 
 22  
 
 23  Created on 22.11.2011
 
 24  
 
 25  @author: tmetsch
 
 26  
 
 27  ''' 
 28  
 
 29  # disabling 'Too many local variables' pylint check (Needed here :-/).
 
 30  # pylint: disable=R0914
 
 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  
 
57 -def _parse_headers(environ):
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
101 -def _parse_query(environ):
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
118 -def _set_hostname(environ, registry):
119 ''' 120 Set the hostname of the service. 121 122 environ -- The WSGI environ. 123 registry -- The OCCI registry. 124 ''' 125 # set hostname 126 if 'HTTP_HOST' in environ.keys(): 127 registry.set_hostname('http://' + environ['HTTP_HOST']) 128 else: 129 # WSGI - could be that HTTP_HOST is not available... 130 host = 'http://' + environ.get('SERVER_NAME') + ':' 131 host += environ.get('SERVER_PORT') 132 registry.set_hostname(host)
133 134
135 -class Application(object):
136 ''' 137 An WSGI application for OCCI. 138 ''' 139 140 # disabling 'Too few public methods' pylint check (given by WSGI) 141 # pylint: disable=R0903 142
143 - def __init__(self, registry=None, renderings=None):
144 # set default registry 145 if registry is None: 146 self.registry = NonePersistentRegistry() 147 else: 148 self.registry = registry 149 150 # set default renderings 151 if renderings is None: 152 self.registry.set_renderer('text/occi', 153 TextOcciRendering(self.registry)) 154 self.registry.set_renderer('text/plain', 155 TextPlainRendering(self.registry)) 156 self.registry.set_renderer('text/uri-list', 157 TextUriListRendering(self.registry)) 158 self.registry.set_renderer('text/html', 159 HTMLRendering(self.registry)) 160 self.registry.set_renderer('application/x-www-form-urlencoded', 161 HTMLRendering(self.registry)) 162 self.registry.set_renderer('application/occi+json', 163 JsonRendering(self.registry)) 164 else: 165 for mime_type in renderings.keys(): 166 self.registry.set_renderer(mime_type, renderings[mime_type])
167
168 - def register_backend(self, category, backend):
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 # parse 206 heads = _parse_headers(environ) 207 208 # parse body... 209 body = _parse_body(environ) 210 211 # parse query 212 query = _parse_query(environ) 213 214 _set_hostname(environ, self.registry) 215 216 # find right handler 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 # call handler 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 # send 242 headers['Server'] = VERSION 243 headers['Content-length'] = str(len(body)) 244 245 code = RETURN_CODES[status] 246 247 # headers.items() because we need a list of sets...& unicode handling 248 # for wsgi since it is not supported :-/ 249 response(code, [(str(k), str(v)) for k, v in headers.items()]) 250 return [str(body), ]
251
252 - def __call__(self, environ, response):
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