Package web2py :: Package gluon :: Module globals
[hide private]
[frames] | no frames]

Source Code for Module web2py.gluon.globals

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework 
  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  8   
  9  Contains the classes for the global used variables: 
 10   
 11  - Request 
 12  - Response 
 13  - Session 
 14   
 15  """ 
 16   
 17  from storage import Storage, List 
 18  from compileapp import run_view_in 
 19  from streamer import streamer, stream_file_or_304_or_206, DEFAULT_CHUNK_SIZE 
 20  from xmlrpc import handler 
 21  from contenttype import contenttype 
 22  from html import xmlescape 
 23  from http import HTTP 
 24  from fileutils import up 
 25  from serializers import json, custom_json 
 26  import settings 
 27  from utils import web2py_uuid 
 28  from settings import global_settings 
 29   
 30  import hashlib 
 31  import portalocker 
 32  import cPickle 
 33  import cStringIO 
 34  import datetime 
 35  import re 
 36  import Cookie 
 37  import os 
 38  import sys 
 39  import traceback 
 40   
 41  regex_session_id = re.compile('^([\w\-]+/)?[\w\-\.]+$') 
 42   
 43  __all__ = ['Request', 'Response', 'Session'] 
 44   
 45   
46 -class Request(Storage):
47 48 """ 49 defines the request object and the default values of its members 50 51 - env: environment variables, by gluon.main.wsgibase() 52 - cookies 53 - get_vars 54 - post_vars 55 - vars 56 - folder 57 - application 58 - function 59 - args 60 - extension 61 - now: datetime.datetime.today() 62 - restful() 63 """ 64
65 - def __init__(self):
66 self.wsgi = Storage() # hooks to environ and start_response 67 self.env = Storage() 68 self.cookies = Cookie.SimpleCookie() 69 self.get_vars = Storage() 70 self.post_vars = Storage() 71 self.vars = Storage() 72 self.folder = None 73 self.application = None 74 self.function = None 75 self.args = List() 76 self.extension = None 77 self.now = datetime.datetime.now() 78 self.is_restful = False
79
80 - def compute_uuid(self):
81 self.uuid = '%s/%s.%s.%s' % ( 82 self.application, 83 self.client.replace(':', '_'), 84 self.now.strftime('%Y-%m-%d.%H-%M-%S'), 85 web2py_uuid()) 86 return self.uuid
87
88 - def restful(self):
89 def wrapper(action,self=self): 90 def f(_action=action,_self=self,*a,**b): 91 self.is_restful = True 92 method = _self.env.request_method 93 if len(_self.args) and '.' in _self.args[-1]: 94 _self.args[-1],_self.extension = _self.args[-1].rsplit('.',1) 95 if not method in ['GET','POST','DELETE','PUT']: 96 raise HTTP(400,"invalid method") 97 rest_action = _action().get(method,None) 98 if not rest_action: 99 raise HTTP(400,"method not supported") 100 try: 101 return rest_action(*_self.args,**_self.vars) 102 except TypeError, e: 103 exc_type, exc_value, exc_traceback = sys.exc_info() 104 if len(traceback.extract_tb(exc_traceback))==1: 105 raise HTTP(400,"invalid arguments") 106 else: 107 raise e
108 f.__doc__ = action.__doc__ 109 f.__name__ = action.__name__ 110 return f
111 return wrapper 112 113
114 -class Response(Storage):
115 116 """ 117 defines the response object and the default values of its members 118 response.write( ) can be used to write in the output html 119 """ 120
121 - def __init__(self):
122 self.status = 200 123 self.headers = Storage() 124 self.body = cStringIO.StringIO() 125 self.session_id = None 126 self.cookies = Cookie.SimpleCookie() 127 self.postprocessing = [] 128 self.flash = '' # used by the default view layout 129 self.meta = Storage() # used by web2py_ajax.html 130 self.menu = [] # used by the default view layout 131 self.files = [] # used by web2py_ajax.html 132 self._vars = None 133 self._caller = lambda f: f() 134 self._view_environment = None 135 self._custom_commit = None 136 self._custom_rollback = None
137
138 - def write(self, data, escape=True):
139 if not escape: 140 self.body.write(str(data)) 141 else: 142 self.body.write(xmlescape(data))
143
144 - def render(self, *a, **b):
145 if len(a) > 2: 146 raise SyntaxError, 'Response.render can be called with two arguments, at most' 147 elif len(a) == 2: 148 (view, self._vars) = (a[0], a[1]) 149 elif len(a) == 1 and isinstance(a[0], str): 150 (view, self._vars) = (a[0], {}) 151 elif len(a) == 1 and hasattr(a[0], 'read') and callable(a[0].read): 152 (view, self._vars) = (a[0], {}) 153 elif len(a) == 1 and isinstance(a[0], dict): 154 (view, self._vars) = (None, a[0]) 155 else: 156 (view, self._vars) = (None, {}) 157 self._vars.update(b) 158 self._view_environment.update(self._vars) 159 if view: 160 import cStringIO 161 (obody, oview) = (self.body, self.view) 162 (self.body, self.view) = (cStringIO.StringIO(), view) 163 run_view_in(self._view_environment) 164 page = self.body.getvalue() 165 self.body.close() 166 (self.body, self.view) = (obody, oview) 167 else: 168 run_view_in(self._view_environment) 169 page = self.body.getvalue() 170 return page
171
172 - def stream( 173 self, 174 stream, 175 chunk_size = DEFAULT_CHUNK_SIZE, 176 request=None, 177 ):
178 """ 179 if a controller function:: 180 181 return response.stream(file, 100) 182 183 the file content will be streamed at 100 bytes at the time 184 """ 185 186 if isinstance(stream, (str, unicode)): 187 stream_file_or_304_or_206(stream, 188 chunk_size=chunk_size, 189 request=request, 190 headers=self.headers) 191 192 # ## the following is for backward compatibility 193 194 if hasattr(stream, 'name'): 195 filename = stream.name 196 else: 197 filename = None 198 keys = [item.lower() for item in self.headers] 199 if filename and not 'content-type' in keys: 200 self.headers['Content-Type'] = contenttype(filename) 201 if filename and not 'content-length' in keys: 202 try: 203 self.headers['Content-Length'] = \ 204 os.path.getsize(filename) 205 except OSError: 206 pass 207 if request and request.env.web2py_use_wsgi_file_wrapper: 208 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) 209 else: 210 wrapped = streamer(stream, chunk_size=chunk_size) 211 return wrapped
212
213 - def download(self, request, db, chunk_size = DEFAULT_CHUNK_SIZE, attachment=True):
214 """ 215 example of usage in controller:: 216 217 def download(): 218 return response.download(request, db) 219 220 downloads from http://..../download/filename 221 """ 222 223 import contenttype as c 224 if not request.args: 225 raise HTTP(404) 226 name = request.args[-1] 227 items = re.compile('(?P<table>.*?)\.(?P<field>.*?)\..*')\ 228 .match(name) 229 if not items: 230 raise HTTP(404) 231 (t, f) = (items.group('table'), items.group('field')) 232 field = db[t][f] 233 try: 234 (filename, stream) = field.retrieve(name) 235 except IOError: 236 raise HTTP(404) 237 self.headers['Content-Type'] = c.contenttype(name) 238 if attachment: 239 self.headers['Content-Disposition'] = \ 240 "attachment; filename=%s" % filename 241 return self.stream(stream, chunk_size = chunk_size, request=request)
242
243 - def json(self, data, default=None):
244 return json(data, default = default or custom_json)
245
246 - def xmlrpc(self, request, methods):
247 """ 248 assuming:: 249 250 def add(a, b): 251 return a+b 252 253 if a controller function \"func\":: 254 255 return response.xmlrpc(request, [add]) 256 257 the controller will be able to handle xmlrpc requests for 258 the add function. Example:: 259 260 import xmlrpclib 261 connection = xmlrpclib.ServerProxy('http://hostname/app/contr/func') 262 print connection.add(3, 4) 263 264 """ 265 266 return handler(request, self, methods)
267
268 -class Session(Storage):
269 270 """ 271 defines the session object and the default values of its members (None) 272 """ 273
274 - def connect( 275 self, 276 request, 277 response, 278 db=None, 279 tablename='web2py_session', 280 masterapp=None, 281 migrate=True, 282 separate = None, 283 check_client=False, 284 ):
285 """ 286 separate can be separate=lambda(session_name): session_name[-2:] 287 and it is used to determine a session prefix. 288 separate can be True and it is set to session_name[-2:] 289 """ 290 if separate == True: 291 separate = lambda session_name: session_name[-2:] 292 self._unlock(response) 293 if not masterapp: 294 masterapp = request.application 295 response.session_id_name = 'session_id_%s' % masterapp.lower() 296 297 if not db: 298 if global_settings.db_sessions is True or masterapp in global_settings.db_sessions: 299 return 300 response.session_new = False 301 client = request.client.replace(':', '.') 302 if response.session_id_name in request.cookies: 303 response.session_id = \ 304 request.cookies[response.session_id_name].value 305 if regex_session_id.match(response.session_id): 306 response.session_filename = \ 307 os.path.join(up(request.folder), masterapp, 308 'sessions', response.session_id) 309 else: 310 response.session_id = None 311 if response.session_id: 312 try: 313 response.session_file = \ 314 open(response.session_filename, 'rb+') 315 portalocker.lock(response.session_file, 316 portalocker.LOCK_EX) 317 response.session_locked = True 318 self.update(cPickle.load(response.session_file)) 319 response.session_file.seek(0) 320 oc = response.session_filename.split('/')[-1].split('-')[0] 321 if check_client and client!=oc: 322 raise Exception, "cookie attack" 323 except: 324 self._close(response) 325 response.session_id = None 326 if not response.session_id: 327 uuid = web2py_uuid() 328 response.session_id = '%s-%s' % (client, uuid) 329 if separate: 330 prefix = separate(response.session_id) 331 response.session_id = '%s/%s' % (prefix,response.session_id) 332 response.session_filename = \ 333 os.path.join(up(request.folder), masterapp, 334 'sessions', response.session_id) 335 response.session_new = True 336 else: 337 if global_settings.db_sessions is not True: 338 global_settings.db_sessions.add(masterapp) 339 response.session_db = True 340 if response.session_file: 341 self._close(response) 342 if settings.global_settings.web2py_runtime_gae: 343 # in principle this could work without GAE 344 request.tickets_db = db 345 if masterapp == request.application: 346 table_migrate = migrate 347 else: 348 table_migrate = False 349 tname = tablename + '_' + masterapp 350 table = db.get(tname, None) 351 if table is None: 352 table = db.define_table( 353 tname, 354 db.Field('locked', 'boolean', default=False), 355 db.Field('client_ip', length=64), 356 db.Field('created_datetime', 'datetime', 357 default=request.now), 358 db.Field('modified_datetime', 'datetime'), 359 db.Field('unique_key', length=64), 360 db.Field('session_data', 'blob'), 361 migrate=table_migrate, 362 ) 363 try: 364 key = request.cookies[response.session_id_name].value 365 (record_id, unique_key) = key.split(':') 366 if record_id == '0': 367 raise Exception, 'record_id == 0' 368 rows = db(table.id == record_id).select() 369 if len(rows) == 0 or rows[0].unique_key != unique_key: 370 raise Exception, 'No record' 371 372 # rows[0].update_record(locked=True) 373 374 session_data = cPickle.loads(rows[0].session_data) 375 self.update(session_data) 376 except Exception: 377 record_id = None 378 unique_key = web2py_uuid() 379 session_data = {} 380 response._dbtable_and_field = \ 381 (response.session_id_name, table, record_id, unique_key) 382 response.session_id = '%s:%s' % (record_id, unique_key) 383 response.cookies[response.session_id_name] = response.session_id 384 response.cookies[response.session_id_name]['path'] = '/' 385 self.__hash = hashlib.md5(str(self)).digest() 386 if self.flash: 387 (response.flash, self.flash) = (self.flash, None)
388
389 - def is_new(self):
390 if self._start_timestamp: 391 return False 392 else: 393 self._start_timestamp = datetime.datetime.today() 394 return True
395
396 - def is_expired(self, seconds = 3600):
397 now = datetime.datetime.today() 398 if not self._last_timestamp or \ 399 self._last_timestamp + datetime.timedelta(seconds = seconds) > now: 400 self._last_timestamp = now 401 return False 402 else: 403 return True
404
405 - def secure(self):
406 self._secure = True
407
408 - def forget(self, response=None):
409 self._close(response) 410 self._forget = True
411
412 - def _try_store_in_db(self, request, response):
413 414 # don't save if file-based sessions, no session id, or session being forgotten 415 if not response.session_db or not response.session_id or self._forget: 416 return 417 418 # don't save if no change to session 419 __hash = self.__hash 420 if __hash is not None: 421 del self.__hash 422 if __hash == hashlib.md5(str(self)).digest(): 423 return 424 425 (record_id_name, table, record_id, unique_key) = \ 426 response._dbtable_and_field 427 dd = dict(locked=False, client_ip=request.env.remote_addr, 428 modified_datetime=request.now, 429 session_data=cPickle.dumps(dict(self)), 430 unique_key=unique_key) 431 if record_id: 432 table._db(table.id == record_id).update(**dd) 433 else: 434 record_id = table.insert(**dd) 435 response.cookies[response.session_id_name] = '%s:%s'\ 436 % (record_id, unique_key) 437 response.cookies[response.session_id_name]['path'] = '/'
438
439 - def _try_store_on_disk(self, request, response):
440 441 # don't save if sessions not not file-based 442 if response.session_db: 443 return 444 445 # don't save if no change to session 446 __hash = self.__hash 447 if __hash is not None: 448 del self.__hash 449 if __hash == hashlib.md5(str(self)).digest(): 450 self._close(response) 451 return 452 453 if not response.session_id or self._forget: 454 self._close(response) 455 return 456 457 if response.session_new: 458 # Tests if the session sub-folder exists, if not, create it 459 session_folder = os.path.dirname(response.session_filename) 460 if not os.path.exists(session_folder): 461 os.mkdir(session_folder) 462 response.session_file = open(response.session_filename, 'wb') 463 portalocker.lock(response.session_file, portalocker.LOCK_EX) 464 response.session_locked = True 465 466 if response.session_file: 467 cPickle.dump(dict(self), response.session_file) 468 response.session_file.truncate() 469 self._close(response)
470
471 - def _unlock(self, response):
472 if response and response.session_file and response.session_locked: 473 try: 474 portalocker.unlock(response.session_file) 475 response.session_locked = False 476 except: ### this should never happen but happens in Windows 477 pass
478
479 - def _close(self, response):
480 if response and response.session_file: 481 self._unlock(response) 482 try: 483 response.session_file.close() 484 del response.session_file 485 except: 486 pass
487