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