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

Source Code for Module Dropbox.web2py.gluon.main

  1  #!/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: 
 10   
 11  - wsgibase: the gluon wsgi application 
 12   
 13  """ 
 14   
 15  import gc 
 16  import cgi 
 17  import cStringIO 
 18  import Cookie 
 19  import os 
 20  import re 
 21  import copy 
 22  import sys 
 23  import time 
 24  import datetime 
 25  import signal 
 26  import socket 
 27  import tempfile 
 28  import random 
 29  import string 
 30  import urllib2 
 31  from thread import allocate_lock 
 32   
 33  from fileutils import abspath, write_file, parse_version, copystream 
 34  from settings import global_settings 
 35  from admin import add_path_first, create_missing_folders, create_missing_app_folders 
 36  from globals import current 
 37   
 38  #  Remarks: 
 39  #  calling script has inserted path to script directory into sys.path 
 40  #  applications_parent (path to applications/, site-packages/ etc) 
 41  #  defaults to that directory set sys.path to 
 42  #  ("", gluon_parent/site-packages, gluon_parent, ...) 
 43  # 
 44  #  this is wrong: 
 45  #  web2py_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 
 46  #  because we do not want the path to this file which may be Library.zip 
 47  #  gluon_parent is the directory containing gluon, web2py.py, logging.conf 
 48  #  and the handlers. 
 49  #  applications_parent (web2py_path) is the directory containing applications/ 
 50  #  and routes.py 
 51  #  The two are identical unless web2py_path is changed via the web2py.py -f folder option 
 52  #  main.web2py_path is the same as applications_parent (for backward compatibility) 
 53   
 54  web2py_path = global_settings.applications_parent  # backward compatibility 
 55   
 56  create_missing_folders() 
 57   
 58  # set up logging for subsequent imports 
 59  import logging 
 60  import logging.config 
 61   
 62  # This needed to prevent exception on Python 2.5: 
 63  # NameError: name 'gluon' is not defined 
 64  # See http://bugs.python.org/issue1436 
 65  import gluon.messageboxhandler 
 66  logging.gluon = gluon 
 67   
 68  exists = os.path.exists 
 69  pjoin = os.path.join 
 70   
 71  logpath = abspath("logging.conf") 
 72  if exists(logpath): 
 73      logging.config.fileConfig(abspath("logging.conf")) 
 74  else: 
 75      logging.basicConfig() 
 76  logger = logging.getLogger("web2py") 
 77   
 78  from restricted import RestrictedError 
 79  from http import HTTP, redirect 
 80  from globals import Request, Response, Session 
 81  from compileapp import build_environment, run_models_in, \ 
 82      run_controller_in, run_view_in 
 83  from contenttype import contenttype 
 84  from dal import BaseAdapter 
 85  from settings import global_settings 
 86  from validators import CRYPT 
 87  from cache import CacheInRam 
 88  from html import URL, xmlescape 
 89  from utils import is_valid_ip_address 
 90  from rewrite import load, url_in, THREAD_LOCAL as rwthread, \ 
 91      try_rewrite_on_error, fixup_missing_path_info 
 92  import newcron 
 93   
 94  __all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer'] 
 95   
 96  requests = 0    # gc timer 
 97   
 98  # Security Checks: validate URL and session_id here, 
 99  # accept_language is validated in languages 
100   
101  # pattern used to validate client address 
102  regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?')  # ## to account for IPV6 
103   
104  #try: 
105  if 1: 
106      version_info = open(pjoin(global_settings.gluon_parent, 'VERSION'), 'r') 
107      raw_version_string = version_info.read().strip() 
108      version_info.close() 
109      global_settings.web2py_version = parse_version(raw_version_string) 
110  #except: 
111  #    raise RuntimeError("Cannot determine web2py version") 
112   
113  web2py_version = global_settings.web2py_version 
114   
115  try: 
116      import rocket 
117  except: 
118      if not global_settings.web2py_runtime_gae: 
119          logger.warn('unable to import Rocket') 
120   
121  load() 
122   
123  HTTPS_SCHEMES = set(('https', 'HTTPS')) 
124   
125   
126 -def get_client(env):
127 """ 128 guess the client address from the environment variables 129 130 first tries 'http_x_forwarded_for', secondly 'remote_addr' 131 if all fails, assume '127.0.0.1' or '::1' (running locally) 132 """ 133 g = regex_client.search(env.get('http_x_forwarded_for', '')) 134 if g: 135 client = (g.group() or '').split(',')[0] 136 else: 137 g = regex_client.search(env.get('remote_addr', '')) 138 if g: 139 client = g.group() 140 elif env.http_host.startswith('['): # IPv6 141 client = '::1' 142 else: 143 client = '127.0.0.1' # IPv4 144 if not is_valid_ip_address(client): 145 raise HTTP(400, "Bad Request (request.client=%s)" % client) 146 return client
147 148
149 -def copystream_progress(request, chunk_size=10 ** 5):
150 """ 151 copies request.env.wsgi_input into request.body 152 and stores progress upload status in cache_ram 153 X-Progress-ID:length and X-Progress-ID:uploaded 154 """ 155 env = request.env 156 if not env.content_length: 157 return cStringIO.StringIO() 158 source = env.wsgi_input 159 try: 160 size = int(env.content_length) 161 except ValueError: 162 raise HTTP(400, "Invalid Content-Length header") 163 dest = tempfile.TemporaryFile() 164 if not 'X-Progress-ID' in request.vars: 165 copystream(source, dest, size, chunk_size) 166 return dest 167 cache_key = 'X-Progress-ID:' + request.vars['X-Progress-ID'] 168 cache_ram = CacheInRam(request) # same as cache.ram because meta_storage 169 cache_ram(cache_key + ':length', lambda: size, 0) 170 cache_ram(cache_key + ':uploaded', lambda: 0, 0) 171 while size > 0: 172 if size < chunk_size: 173 data = source.read(size) 174 cache_ram.increment(cache_key + ':uploaded', size) 175 else: 176 data = source.read(chunk_size) 177 cache_ram.increment(cache_key + ':uploaded', chunk_size) 178 length = len(data) 179 if length > size: 180 (data, length) = (data[:size], size) 181 size -= length 182 if length == 0: 183 break 184 dest.write(data) 185 if length < chunk_size: 186 break 187 dest.seek(0) 188 cache_ram(cache_key + ':length', None) 189 cache_ram(cache_key + ':uploaded', None) 190 return dest
191 192
193 -def serve_controller(request, response, session):
194 """ 195 this function is used to generate a dynamic page. 196 It first runs all models, then runs the function in the controller, 197 and then tries to render the output using a view/template. 198 this function must run from the [application] folder. 199 A typical example would be the call to the url 200 /[application]/[controller]/[function] that would result in a call 201 to [function]() in applications/[application]/[controller].py 202 rendered by applications/[application]/views/[controller]/[function].html 203 """ 204 205 # ################################################## 206 # build environment for controller and view 207 # ################################################## 208 209 environment = build_environment(request, response, session) 210 211 # set default view, controller can override it 212 213 response.view = '%s/%s.%s' % (request.controller, 214 request.function, 215 request.extension) 216 217 # also, make sure the flash is passed through 218 # ################################################## 219 # process models, controller and view (if required) 220 # ################################################## 221 222 run_models_in(environment) 223 response._view_environment = copy.copy(environment) 224 page = run_controller_in(request.controller, request.function, environment) 225 if isinstance(page, dict): 226 response._vars = page 227 response._view_environment.update(page) 228 run_view_in(response._view_environment) 229 page = response.body.getvalue() 230 # logic to garbage collect after exec, not always, once every 100 requests 231 global requests 232 requests = ('requests' in globals()) and (requests + 1) % 100 or 0 233 if not requests: 234 gc.collect() 235 # end garbage collection logic 236 237 # ################################################## 238 # set default headers it not set 239 # ################################################## 240 241 default_headers = [ 242 ('Content-Type', contenttype('.' + request.extension)), 243 ('Cache-Control', 244 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'), 245 ('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT', 246 time.gmtime())), 247 ('Pragma', 'no-cache')] 248 for key, value in default_headers: 249 response.headers.setdefault(key, value) 250 251 raise HTTP(response.status, page, **response.headers)
252 253
254 -def start_response_aux(status, headers, exc_info, response=None):
255 """ 256 in controller you can use:: 257 258 - request.wsgi.environ 259 - request.wsgi.start_response 260 261 to call third party WSGI applications 262 """ 263 response.status = str(status).split(' ', 1)[0] 264 response.headers = dict(headers) 265 return lambda *args, **kargs: response.write(escape=False, *args, **kargs)
266 267
268 -def middleware_aux(request, response, *middleware_apps):
269 """ 270 In you controller use:: 271 272 @request.wsgi.middleware(middleware1, middleware2, ...) 273 274 to decorate actions with WSGI middleware. actions must return strings. 275 uses a simulated environment so it may have weird behavior in some cases 276 """ 277 def middleware(f): 278 def app(environ, start_response): 279 data = f() 280 start_response(response.status, response.headers.items()) 281 if isinstance(data, list): 282 return data 283 return [data]
284 for item in middleware_apps: 285 app = item(app) 286 287 def caller(app): 288 wsgi = request.wsgi 289 return app(wsgi.environ, wsgi.start_response) 290 return lambda caller=caller, app=app: caller(app) 291 return middleware 292 293
294 -def environ_aux(environ, request):
295 new_environ = copy.copy(environ) 296 new_environ['wsgi.input'] = request.body 297 new_environ['wsgi.version'] = 1 298 return new_environ
299 300
301 -def parse_get_post_vars(request, environ):
302 303 # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars 304 env = request.env 305 dget = cgi.parse_qsl(env.query_string or '', keep_blank_values=1) 306 for (key, value) in dget: 307 if key in request.get_vars: 308 if isinstance(request.get_vars[key], list): 309 request.get_vars[key] += [value] 310 else: 311 request.get_vars[key] = [request.get_vars[key]] + [value] 312 else: 313 request.get_vars[key] = value 314 request.vars[key] = request.get_vars[key] 315 316 # parse POST variables on POST, PUT, BOTH only in post_vars 317 try: 318 request.body = body = copystream_progress(request) 319 except IOError: 320 raise HTTP(400, "Bad Request - HTTP body is incomplete") 321 if (body and env.request_method in ('POST', 'PUT', 'BOTH')): 322 dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1) 323 # The same detection used by FieldStorage to detect multipart POSTs 324 is_multipart = dpost.type[:10] == 'multipart/' 325 body.seek(0) 326 isle25 = sys.version_info[1] <= 5 327 328 def listify(a): 329 return (not isinstance(a, list) and [a]) or a
330 try: 331 keys = sorted(dpost) 332 except TypeError: 333 keys = [] 334 for key in keys: 335 if key is None: 336 continue # not sure why cgi.FieldStorage returns None key 337 dpk = dpost[key] 338 # if en element is not a file replace it with its value else leave it alone 339 if isinstance(dpk, list): 340 value = [] 341 for _dpk in dpk: 342 if not _dpk.filename: 343 value.append(_dpk.value) 344 else: 345 value.append(_dpk) 346 elif not dpk.filename: 347 value = dpk.value 348 else: 349 value = dpk 350 pvalue = listify(value) 351 if key in request.vars: 352 gvalue = listify(request.vars[key]) 353 if isle25: 354 value = pvalue + gvalue 355 elif is_multipart: 356 pvalue = pvalue[len(gvalue):] 357 else: 358 pvalue = pvalue[:-len(gvalue)] 359 request.vars[key] = value 360 if len(pvalue): 361 request.post_vars[key] = (len(pvalue) > 362 1 and pvalue) or pvalue[0] 363 364
365 -def wsgibase(environ, responder):
366 """ 367 this is the gluon wsgi application. the first function called when a page 368 is requested (static or dynamic). it can be called by paste.httpserver 369 or by apache mod_wsgi. 370 371 - fills request with info 372 - the environment variables, replacing '.' with '_' 373 - adds web2py path and version info 374 - compensates for fcgi missing path_info and query_string 375 - validates the path in url 376 377 The url path must be either: 378 379 1. for static pages: 380 381 - /<application>/static/<file> 382 383 2. for dynamic pages: 384 385 - /<application>[/<controller>[/<function>[/<sub>]]][.<extension>] 386 - (sub may go several levels deep, currently 3 levels are supported: 387 sub1/sub2/sub3) 388 389 The naming conventions are: 390 391 - application, controller, function and extension may only contain 392 [a-zA-Z0-9_] 393 - file and sub may also contain '-', '=', '.' and '/' 394 """ 395 396 current.__dict__.clear() 397 request = Request() 398 response = Response() 399 session = Session() 400 env = request.env 401 env.web2py_path = global_settings.applications_parent 402 env.web2py_version = web2py_version 403 env.update(global_settings) 404 static_file = False 405 try: 406 try: 407 try: 408 # ################################################## 409 # handle fcgi missing path_info and query_string 410 # select rewrite parameters 411 # rewrite incoming URL 412 # parse rewritten header variables 413 # parse rewritten URL 414 # serve file if static 415 # ################################################## 416 417 fixup_missing_path_info(environ) 418 (static_file, version, environ) = url_in(request, environ) 419 response.status = env.web2py_status_code or response.status 420 421 if static_file: 422 if environ.get('QUERY_STRING', '').startswith( 423 'attachment'): 424 response.headers['Content-Disposition'] \ 425 = 'attachment' 426 if version: 427 response.headers['Cache-Control'] = 'max-age=315360000' 428 response.headers[ 429 'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT' 430 response.stream(static_file, request=request) 431 432 # ################################################## 433 # fill in request items 434 # ################################################## 435 app = request.application # must go after url_in! 436 437 if not global_settings.local_hosts: 438 local_hosts = set(['127.0.0.1', '::ffff:127.0.0.1', '::1']) 439 if not global_settings.web2py_runtime_gae: 440 try: 441 fqdn = socket.getfqdn() 442 local_hosts.add(socket.gethostname()) 443 local_hosts.add(fqdn) 444 local_hosts.update([ 445 ip[4][0] for ip in socket.getaddrinfo( 446 fqdn, 0)]) 447 if env.server_name: 448 local_hosts.add(env.server_name) 449 local_hosts.update([ 450 ip[4][0] for ip in socket.getaddrinfo( 451 env.server_name, 0)]) 452 except (socket.gaierror, TypeError): 453 pass 454 global_settings.local_hosts = list(local_hosts) 455 else: 456 local_hosts = global_settings.local_hosts 457 client = get_client(env) 458 x_req_with = str(env.http_x_requested_with).lower() 459 460 request.update( 461 client = client, 462 folder = abspath('applications', app) + os.sep, 463 ajax = x_req_with == 'xmlhttprequest', 464 cid = env.http_web2py_component_element, 465 is_local = env.remote_addr in local_hosts, 466 is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \ 467 request.env.http_x_forwarded_proto in HTTPS_SCHEMES \ 468 or env.https == 'on') 469 request.compute_uuid() # requires client 470 request.url = environ['PATH_INFO'] 471 472 # ################################################## 473 # access the requested application 474 # ################################################## 475 476 if not exists(request.folder): 477 if app == rwthread.routes.default_application \ 478 and app != 'welcome': 479 redirect(URL('welcome', 'default', 'index')) 480 elif rwthread.routes.error_handler: 481 _handler = rwthread.routes.error_handler 482 redirect(URL(_handler['application'], 483 _handler['controller'], 484 _handler['function'], 485 args=app)) 486 else: 487 raise HTTP(404, rwthread.routes.error_message 488 % 'invalid request', 489 web2py_error='invalid application') 490 elif not request.is_local and \ 491 exists(pjoin(request.folder, 'DISABLED')): 492 raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>") 493 494 # ################################################## 495 # build missing folders 496 # ################################################## 497 498 create_missing_app_folders(request) 499 500 # ################################################## 501 # get the GET and POST data 502 # ################################################## 503 504 parse_get_post_vars(request, environ) 505 506 # ################################################## 507 # expose wsgi hooks for convenience 508 # ################################################## 509 510 request.wsgi.environ = environ_aux(environ, request) 511 request.wsgi.start_response = \ 512 lambda status='200', headers=[], \ 513 exec_info=None, response=response: \ 514 start_response_aux(status, headers, exec_info, response) 515 request.wsgi.middleware = \ 516 lambda *a: middleware_aux(request, response, *a) 517 518 # ################################################## 519 # load cookies 520 # ################################################## 521 522 if env.http_cookie: 523 try: 524 request.cookies.load(env.http_cookie) 525 except Cookie.CookieError, e: 526 pass # invalid cookies 527 528 # ################################################## 529 # try load session or create new session file 530 # ################################################## 531 532 if not env.web2py_disable_session: 533 session.connect(request, response) 534 535 # ################################################## 536 # run controller 537 # ################################################## 538 539 if global_settings.debugging and app != "admin": 540 import gluon.debug 541 # activate the debugger 542 gluon.debug.dbg.do_debug(mainpyfile=request.folder) 543 544 serve_controller(request, response, session) 545 546 except HTTP, http_response: 547 548 if static_file: 549 return http_response.to(responder, env=env) 550 551 if request.body: 552 request.body.close() 553 554 # ################################################## 555 # on success, try store session in database 556 # ################################################## 557 session._try_store_in_db(request, response) 558 559 # ################################################## 560 # on success, commit database 561 # ################################################## 562 563 if response.do_not_commit is True: 564 BaseAdapter.close_all_instances(None) 565 # elif response._custom_commit: 566 # response._custom_commit() 567 elif response.custom_commit: 568 BaseAdapter.close_all_instances(response.custom_commit) 569 else: 570 BaseAdapter.close_all_instances('commit') 571 572 # ################################################## 573 # if session not in db try store session on filesystem 574 # this must be done after trying to commit database! 575 # ################################################## 576 577 session._try_store_in_cookie_or_file(request, response) 578 579 if request.cid: 580 if response.flash: 581 http_response.headers['web2py-component-flash'] = \ 582 urllib2.quote(xmlescape(response.flash)\ 583 .replace('\n','')) 584 if response.js: 585 http_response.headers['web2py-component-command'] = \ 586 urllib2.quote(response.js.replace('\n','')) 587 588 # ################################################## 589 # store cookies in headers 590 # ################################################## 591 592 rcookies = response.cookies 593 if session._forget and response.session_id_name in rcookies: 594 del rcookies[response.session_id_name] 595 elif session._secure: 596 rcookies[response.session_id_name]['secure'] = True 597 http_response.cookies2headers(rcookies) 598 ticket = None 599 600 except RestrictedError, e: 601 602 if request.body: 603 request.body.close() 604 605 # ################################################## 606 # on application error, rollback database 607 # ################################################## 608 609 ticket = e.log(request) or 'unknown' 610 if response._custom_rollback: 611 response._custom_rollback() 612 else: 613 BaseAdapter.close_all_instances('rollback') 614 615 http_response = \ 616 HTTP(500, rwthread.routes.error_message_ticket % 617 dict(ticket=ticket), 618 web2py_error='ticket %s' % ticket) 619 620 except: 621 622 if request.body: 623 request.body.close() 624 625 # ################################################## 626 # on application error, rollback database 627 # ################################################## 628 629 try: 630 if response._custom_rollback: 631 response._custom_rollback() 632 else: 633 BaseAdapter.close_all_instances('rollback') 634 except: 635 pass 636 e = RestrictedError('Framework', '', '', locals()) 637 ticket = e.log(request) or 'unrecoverable' 638 http_response = \ 639 HTTP(500, rwthread.routes.error_message_ticket 640 % dict(ticket=ticket), 641 web2py_error='ticket %s' % ticket) 642 643 finally: 644 if response and hasattr(response, 'session_file') \ 645 and response.session_file: 646 response.session_file.close() 647 648 session._unlock(response) 649 http_response, new_environ = try_rewrite_on_error( 650 http_response, request, environ, ticket) 651 if not http_response: 652 return wsgibase(new_environ, responder) 653 if global_settings.web2py_crontype == 'soft': 654 newcron.softcron(global_settings.applications_parent).start() 655 return http_response.to(responder, env=env)
656 657
658 -def save_password(password, port):
659 """ 660 used by main() to save the password in the parameters_port.py file. 661 """ 662 663 password_file = abspath('parameters_%i.py' % port) 664 if password == '<random>': 665 # make up a new password 666 chars = string.letters + string.digits 667 password = ''.join([random.choice(chars) for i in range(8)]) 668 cpassword = CRYPT()(password)[0] 669 print '******************* IMPORTANT!!! ************************' 670 print 'your admin password is "%s"' % password 671 print '*********************************************************' 672 elif password == '<recycle>': 673 # reuse the current password if any 674 if exists(password_file): 675 return 676 else: 677 password = '' 678 elif password.startswith('<pam_user:'): 679 # use the pam password for specified user 680 cpassword = password[1:-1] 681 else: 682 # use provided password 683 cpassword = CRYPT()(password)[0] 684 fp = open(password_file, 'w') 685 if password: 686 fp.write('password="%s"\n' % cpassword) 687 else: 688 fp.write('password=None\n') 689 fp.close()
690 691
692 -def appfactory(wsgiapp=wsgibase, 693 logfilename='httpserver.log', 694 profilerfilename='profiler.log'):
695 """ 696 generates a wsgi application that does logging and profiling and calls 697 wsgibase 698 699 .. function:: gluon.main.appfactory( 700 [wsgiapp=wsgibase 701 [, logfilename='httpserver.log' 702 [, profilerfilename='profiler.log']]]) 703 704 """ 705 if profilerfilename and exists(profilerfilename): 706 os.unlink(profilerfilename) 707 locker = allocate_lock() 708 709 def app_with_logging(environ, responder): 710 """ 711 a wsgi app that does logging and profiling and calls wsgibase 712 """ 713 status_headers = [] 714 715 def responder2(s, h): 716 """ 717 wsgi responder app 718 """ 719 status_headers.append(s) 720 status_headers.append(h) 721 return responder(s, h)
722 723 time_in = time.time() 724 ret = [0] 725 if not profilerfilename: 726 ret[0] = wsgiapp(environ, responder2) 727 else: 728 import cProfile 729 import pstats 730 logger.warn('profiler is on. this makes web2py slower and serial') 731 732 locker.acquire() 733 cProfile.runctx('ret[0] = wsgiapp(environ, responder2)', 734 globals(), locals(), profilerfilename + '.tmp') 735 stat = pstats.Stats(profilerfilename + '.tmp') 736 stat.stream = cStringIO.StringIO() 737 stat.strip_dirs().sort_stats("time").print_stats(80) 738 profile_out = stat.stream.getvalue() 739 profile_file = open(profilerfilename, 'a') 740 profile_file.write('%s\n%s\n%s\n%s\n\n' % 741 ('=' * 60, environ['PATH_INFO'], '=' * 60, profile_out)) 742 profile_file.close() 743 locker.release() 744 try: 745 line = '%s, %s, %s, %s, %s, %s, %f\n' % ( 746 environ['REMOTE_ADDR'], 747 datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), 748 environ['REQUEST_METHOD'], 749 environ['PATH_INFO'].replace(',', '%2C'), 750 environ['SERVER_PROTOCOL'], 751 (status_headers[0])[:3], 752 time.time() - time_in, 753 ) 754 if not logfilename: 755 sys.stdout.write(line) 756 elif isinstance(logfilename, str): 757 write_file(logfilename, line, 'a') 758 else: 759 logfilename.write(line) 760 except: 761 pass 762 return ret[0] 763 764 return app_with_logging 765 766
767 -class HttpServer(object):
768 """ 769 the web2py web server (Rocket) 770 """ 771
772 - def __init__( 773 self, 774 ip='127.0.0.1', 775 port=8000, 776 password='', 777 pid_filename='httpserver.pid', 778 log_filename='httpserver.log', 779 profiler_filename=None, 780 ssl_certificate=None, 781 ssl_private_key=None, 782 ssl_ca_certificate=None, 783 min_threads=None, 784 max_threads=None, 785 server_name=None, 786 request_queue_size=5, 787 timeout=10, 788 socket_timeout=1, 789 shutdown_timeout=None, # Rocket does not use a shutdown timeout 790 path=None, 791 interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string 792 ):
793 """ 794 starts the web server. 795 """ 796 797 if interfaces: 798 # if interfaces is specified, it must be tested for rocket parameter correctness 799 # not necessarily completely tested (e.g. content of tuples or ip-format) 800 import types 801 if isinstance(interfaces, types.ListType): 802 for i in interfaces: 803 if not isinstance(i, types.TupleType): 804 raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" 805 else: 806 raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/" 807 808 if path: 809 # if a path is specified change the global variables so that web2py 810 # runs from there instead of cwd or os.environ['web2py_path'] 811 global web2py_path 812 path = os.path.normpath(path) 813 web2py_path = path 814 global_settings.applications_parent = path 815 os.chdir(path) 816 [add_path_first(p) for p in (path, abspath('site-packages'), "")] 817 if exists("logging.conf"): 818 logging.config.fileConfig("logging.conf") 819 820 save_password(password, port) 821 self.pid_filename = pid_filename 822 if not server_name: 823 server_name = socket.gethostname() 824 logger.info('starting web server...') 825 rocket.SERVER_NAME = server_name 826 rocket.SOCKET_TIMEOUT = socket_timeout 827 sock_list = [ip, port] 828 if not ssl_certificate or not ssl_private_key: 829 logger.info('SSL is off') 830 elif not rocket.ssl: 831 logger.warning('Python "ssl" module unavailable. SSL is OFF') 832 elif not exists(ssl_certificate): 833 logger.warning('unable to open SSL certificate. SSL is OFF') 834 elif not exists(ssl_private_key): 835 logger.warning('unable to open SSL private key. SSL is OFF') 836 else: 837 sock_list.extend([ssl_private_key, ssl_certificate]) 838 if ssl_ca_certificate: 839 sock_list.append(ssl_ca_certificate) 840 841 logger.info('SSL is ON') 842 app_info = {'wsgi_app': appfactory(wsgibase, 843 log_filename, 844 profiler_filename)} 845 846 self.server = rocket.Rocket(interfaces or tuple(sock_list), 847 method='wsgi', 848 app_info=app_info, 849 min_threads=min_threads, 850 max_threads=max_threads, 851 queue_size=int(request_queue_size), 852 timeout=int(timeout), 853 handle_signals=False, 854 )
855
856 - def start(self):
857 """ 858 start the web server 859 """ 860 try: 861 signal.signal(signal.SIGTERM, lambda a, b, s=self: s.stop()) 862 signal.signal(signal.SIGINT, lambda a, b, s=self: s.stop()) 863 except: 864 pass 865 write_file(self.pid_filename, str(os.getpid())) 866 self.server.start()
867
868 - def stop(self, stoplogging=False):
869 """ 870 stop cron and the web server 871 """ 872 newcron.stopcron() 873 self.server.stop(stoplogging) 874 try: 875 os.unlink(self.pid_filename) 876 except: 877 pass
878