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

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