source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/main.py @ 42095c5

mainqndtest v1.1.1
Last change on this file since 42095c5 was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

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