Package gluon :: Module main
[hide private]
[frames] | no frames]

Source Code for Module gluon.main

  1  """ 
  2  This file is part of web2py Web Framework (Copyrighted, 2007) 
  3  Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  4  License: GPL v2 
  5  """ 
  6   
  7  import cgi, cStringIO, Cookie, cPickle, os 
  8  import re, copy, sys, types, time, thread 
  9  import datetime, signal, socket, stat 
 10  import tempfile 
 11  #from wsgiref.simple_server import make_server, demo_app 
 12  from random import random 
 13  from storage import Storage, load_storage, save_storage 
 14  from restricted import RestrictedError 
 15  from languages import translator 
 16  from http import HTTP, redirect 
 17  from globals import Request, Response, Session 
 18  from cache import Cache 
 19  from compileapp import run_models_in, run_controller_in, run_view_in 
 20  from fileutils import listdir, copystream 
 21  from contenttype import contenttype 
 22  from sql import SQLDB, SQLField 
 23  from sqlhtml import SQLFORM, SQLTABLE 
 24  from rewrite import rewrite 
 25  from xmlrpc import handler 
 26  from streamer import streamer 
 27  import html 
 28  import validators 
 29  import myregex 
 30  import wsgiserver 
 31  import portalocker 
 32  ### contrib moduels 
 33  import contrib.simplejson 
 34  import contrib.pyrtf 
 35  import contrib.rss2 
 36  import contrib.feedparser 
 37  import contrib.markdown 
 38  import contrib.memcache 
 39   
 40  __all__=['wsgibase', 'save_password', 'appfactory', 'HttpServer'] 
 41   
 42  ### Security Checks: validate URL and session_id here, accept_language is validated in languages 
 43  # pattern to find valid paths in url /application/controller/... 
 44  regex_url=re.compile('(?:^$)|(?:^(\w+/?){0,3}$)|(?:^(\w+/){3}\w+(/?\.?[\w\-\.]+)*/?$)|(?:^(\w+)/static(/\.?[\w\-\.]+)*/?$)') 
 45  # patter used to validate session ids 
 46  regex_session_id=re.compile('([0-9]+\.)+[0-9]+') 
 47   
 48  error_message='<html><body><h1>Invalid request</h1></body></html>' 
 49  error_message_ticket='<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%s" target="_blank">%s</a></body></html>' 
 50   
 51  working_folder=os.getcwd() 
 52   
53 -def RFC1123_DATETIME(seconds):
54 return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(seconds))
55
56 -def serve_controller(request,response,session):
57 """ 58 this function is used to generate a dynmaic page. 59 It first runs all models, then runs the function in the controller, 60 and then tries to render the output using a view/template. 61 this function must run from the [applciation] folder. 62 A typical examples would be the call to the url 63 /[applicaiton]/[controller]/[function] that would result in a call 64 to [function]() in applications/[application]/[controller].py 65 renedred by applications/[application]/[controller]/[view].html 66 """ 67 ################################################### 68 # build evnironment for controller and view 69 ################################################### 70 environment={} 71 for key in html.__all__: environment[key]=eval('html.%s' % key) 72 for key in validators.__all__: environment[key]=eval('validators.%s' % key) 73 environment['T']=translator(request) 74 environment['HTTP']=HTTP 75 environment['redirect']=redirect 76 environment['request']=request 77 environment['response']=response 78 environment['session']=session 79 environment['cache']=Cache(request) 80 environment['SQLDB']=SQLDB 81 SQLDB._set_thread_folder(os.path.join(request.folder,'databases')) 82 environment['SQLField']=SQLField 83 environment['SQLFORM']=SQLFORM 84 environment['SQLTABLE']=SQLTABLE 85 # set default view, controller can override it 86 response.view='%s/%s.html' % (request.controller,request.function) 87 # also, make sure the flash is passed through 88 if session.flash: response.flash, session.flash=session.flash, None 89 ################################################### 90 # process models, controller and view (if required) 91 ################################################### 92 run_models_in(environment) 93 response._view_environment=copy.copy(environment) 94 run_controller_in(request.controller,request.function,environment) 95 if not type(response.body) in [types.StringType, types.GeneratorType]: 96 for key,value in response._vars.items(): 97 response._view_environment[key]=value 98 run_view_in(response._view_environment) 99 response.body=response.body.getvalue() 100 raise HTTP(200,response.body,**response.headers) 101
102 -def wsgibase(environ, responder):
103 """ 104 this is the gluon wsgi application. the furst function called when a page 105 is requested (static or dynamical). it can be called by paste.httpserver 106 or by apache mod_wsgi. 107 """ 108 ### os.chdir(working_folder) ### to recover if the app does chdir 109 request=Request() 110 response=Response() 111 session=Session() 112 try: 113 try: 114 session_file=None 115 session_new=False 116 ################################################### 117 # parse the environment variables - DONE 118 ################################################### 119 for key, value in environ.items(): 120 request.env[key.lower().replace('.','_')]=value 121 if not request.env.web2py_path: 122 request.env.web2py_path=working_folder 123 ################################################### 124 # valudate the path in url 125 ################################################### 126 if not request.env.path_info and request.env.request_uri: 127 request.env.path_info=request.env.request_uri # for fcgi 128 path=request.env.path_info[1:].replace('\\','/') 129 if not regex_url.match(path): 130 raise HTTP(400,error_message,web2py_error='invalid path') 131 items=path.split('/') 132 ################################################### 133 # serve if a static file 134 ################################################### 135 136 if len(items)>2 and items[1]=='static': 137 static_file=os.path.join(request.env.web2py_path,'applications',items[0],'static','/'.join(items[2:])) 138 if not os.access(static_file,os.R_OK): 139 raise HTTP(400,error_message,web2py_error='invalid application') 140 stat_file=os.stat(static_file) 141 mtime=RFC1123_DATETIME(stat_file[stat.ST_MTIME]) 142 headers={'Content-Type':contenttype(static_file), 143 'Content-Length':stat_file[stat.ST_SIZE], 144 'Last-Modified':mtime} 145 if environ.get('HTTP_IF_MODIFIED_SINCE', '') == mtime: 146 raise HTTP(304) 147 else: 148 raise HTTP(200,streamer(open(static_file,'rb')),**headers) 149 ################################################### 150 # parse application, controller and function 151 ################################################### 152 if len(items) and items[-1]=='': del items[-1] 153 if len(items)==0: redirect('/init/default/index') 154 if len(items)==1: redirect('/%s/default/index' % items[0]) 155 if len(items)==2: redirect('/%s/%s/index' % tuple(items)) 156 if len(items)>3: items,request.args=items[:3],items[3:] 157 if request.args==None: request.args=[] 158 request.application=items[0] 159 request.controller=items[1] 160 request.function=items[2] 161 request.folder=os.path.join(request.env.web2py_path,'applications',request.application)+'/' 162 ################################################### 163 # access the requested application 164 ################################################### 165 if not os.access(request.folder,os.F_OK): 166 if items==['init','default','index']: 167 redirect('/welcome/default/index') 168 raise HTTP(400,error_message,web2py_error='invalid application') 169 ################################################### 170 # get the GET and POST data -DONE 171 ################################################### 172 request.body=tempfile.TemporaryFile() 173 if request.env.content_length: 174 copystream(request.env.wsgi_input,request.body, 175 int(request.env.content_length)) 176 if request.env.request_method in ['POST', 'BOTH']: 177 dpost=cgi.FieldStorage(fp=request.body, 178 environ=environ,keep_blank_values=1) 179 request.body.seek(0) 180 try: keys=dpost.keys() 181 except TypeError: keys=[] 182 for key in keys: 183 dpk=dpost[key] 184 if type(dpk)==types.ListType: 185 request.post_vars[key]=request.vars[key]=[x.value for x in dpk] 186 elif not dpk.filename: #or type(dpk.file)==type(cStringIO.StringIO()): 187 request.post_vars[key]=request.vars[key]=dpk.value 188 else: 189 request.post_vars[key]=request.vars[key]=dpk 190 if request.env.request_method in ['GET', 'BOTH']: 191 dget=cgi.FieldStorage(environ=environ,keep_blank_values=1) 192 for key in dget.keys(): 193 request.get_vars[key]=request.vars[key]=dget[key].value 194 ################################################### 195 # load cookies 196 ################################################### 197 request.cookies=Cookie.SimpleCookie() 198 response.cookies=Cookie.SimpleCookie() 199 if request.env.http_cookie: 200 request.cookies.load(request.env.http_cookie) 201 ################################################### 202 # try load session or create new session file 203 ################################################### 204 session_id_name='session_id_%s'%request.application 205 if request.cookies.has_key(session_id_name): 206 response.session_id=request.cookies[session_id_name].value 207 if regex_session_id.match(response.session_id): 208 session_filename=os.path.join(request.folder,'sessions',response.session_id) 209 else: response.session_id=None 210 if response.session_id: 211 try: 212 session_file=open(session_filename,'rb+') 213 portalocker.lock(session_file,portalocker.LOCK_EX) 214 session=Storage(cPickle.load(session_file)) 215 session_file.seek(0) 216 except: 217 if session_file: portalocker.unlock(session_file) 218 response.session_id=None 219 if not response.session_id: 220 response.session_id=request.env.remote_addr+'.'+str(int(time.time()))+'.'+str(random())[2:] 221 session_filename=os.path.join(request.folder,'sessions',response.session_id) 222 session_new=True 223 response.cookies[session_id_name]=response.session_id 224 response.cookies[session_id_name]['path']="/" 225 response.session_id_name=session_id_name 226 ################################################### 227 # run controller 228 ################################################### 229 if not items[1]=='static': 230 serve_controller(request,response,session) 231 except HTTP, http_response: 232 ################################################### 233 # on sucess, committ database 234 ################################################### 235 SQLDB.close_all_instances(SQLDB.commit) 236 ################################################### 237 # save cookies is session (static files do not have a session) 238 ################################################### 239 if response.session_id: 240 http_response.headers['Set-Cookie']=[str(response.cookies[i])[11:] for i in response.cookies.keys()] 241 if session_new: 242 session_file=open(session_filename,'wb') 243 portalocker.lock(session_file,portalocker.LOCK_EX) 244 cPickle.dump(dict(session),session_file) 245 ################################################### 246 # whatever happens return the intended HTTP response 247 ################################################### 248 if session_file: portalocker.unlock(session_file) 249 return http_response.to(responder) 250 except RestrictedError, e: 251 ################################################### 252 # on application error, rollback database 253 ################################################### 254 SQLDB.close_all_instances(SQLDB.rollback) 255 ticket=e.log(request) 256 #print e.traceback 257 if session_file: portalocker.unlock(session_file) 258 return HTTP(200,error_message_ticket % (ticket,ticket),\ 259 web2py_error='ticket %s'%ticket).to(responder) 260 except BaseException, exception: 261 ################################################### 262 # on application error, rollback database 263 ################################################### 264 ### os.chdir(working_folder) ### to recover if the app does chdir 265 try: SQLDB.close_all_instances(SQLDB.rollback) 266 except: pass 267 e=RestrictedError('Framework','','',locals()) 268 try: ticket=e.log(request) 269 except: 270 ticket='unrecoverable' 271 print '*'*10,'intenral error traceback','*'*10 272 print e.traceback 273 print '*'*49 274 #print e.traceback 275 if session_file: portalocker.unlock(session_file) 276 return HTTP(200,error_message_ticket % (ticket,ticket), 277 web2py_error='ticket %s'%ticket).to(responder) 278 279 wsgibase,html.URL=rewrite(wsgibase,html.URL) 280
281 -def save_password(password,port):
282 """ 283 used by main() to save the password in the parameters.py file. 284 """ 285 if password=='<recycle>': return 286 import gluon.validators 287 crypt=gluon.validators.CRYPT() 288 file=open('parameters_%i.py'%port,'w') 289 if len(password)>0: file.write('password="%s"\n' % crypt(password)[0]) 290 else: file.write('password=None\n') 291 file.close()
292
293 -def appfactory(wsgiapp=wsgibase,logfilename='httpsever.log',web2py_path=working_folder):
294 def app_with_logging(environ, responder): 295 environ['web2py_path']=web2py_path 296 status_headers=[] 297 def responder2(s,h): 298 status_headers.append(s) 299 status_headers.append(h) 300 return responder(s,h)
301 time_in=time.time() 302 ret=wsgiapp(environ,responder2) 303 try: 304 line='%s, %s, %s, %s, %s, %s, %f\n' % (environ['REMOTE_ADDR'], datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), environ['REQUEST_METHOD'],environ['PATH_INFO'].replace(',','%2C'),environ['SERVER_PROTOCOL'],status_headers[0][:3],time.time()-time_in) 305 if logfilename: open(logfilename,'a').write(line) 306 else: sys.stdout.write(line) 307 except: pass 308 return ret 309 return app_with_logging 310
311 -class HttpServer(object):
312 - def __init__(self,ip='127.0.0.1',port=8000,password='', 313 pid_filename='httpserver.pid', 314 log_filename='httpserver.log', 315 ssl_certificate=None, 316 ssl_private_key=None, 317 numthreads=10, 318 server_name=None, 319 request_queue_size=5, 320 timeout=10, 321 shutdown_timeout=5, 322 path=working_folder):
323 """ 324 starts the web server. 325 """ 326 save_password(password,port) 327 self.pid_filename=pid_filename 328 if not server_name: server_name=socket.gethostname() 329 print 'starting web server...' 330 self.server=wsgiserver.CherryPyWSGIServer((ip, port), 331 appfactory(wsgibase,log_filename,web2py_path=path), 332 numthreads=int(numthreads), server_name=server_name, 333 request_queue_size=int(request_queue_size), 334 timeout=int(timeout), 335 shutdown_timeout=int(shutdown_timeout)) 336 if not ssl_certificate or not ssl_private_key: 337 print 'SSL is off' 338 elif not wsgiserver.SSL: 339 print 'Error: OpenSSL libraries available. SSL is OFF' 340 elif not os.access(ssl_certificate,os.R_OK): 341 print 'Error: unable to open SSL certificate. SSL is OFF' 342 elif not os.access(ssl_private_key,os.R_OK): 343 print 'Error: unable to open SSL private key. SSL is OFF' 344 else: 345 self.server.ssl_certificate=ssl_certificate 346 self.server.ssl_private_key=ssl_private_key 347 print 'SSL is ON'
348 - def start(self):
349 try: signal.signal(signal.SIGTERM,lambda a,b,s=self:s.stop()) 350 except: pass 351 open(self.pid_filename,'w').write(str(os.getpid())) 352 self.server.start()
353 - def stop(self):
354 self.server.stop() 355 os.unlink(self.pid_filename)
356