diff options
author | Aleksey Lim <alsroot@sugarlabs.org> | 2012-03-09 15:39:47 (GMT) |
---|---|---|
committer | Aleksey Lim <alsroot@sugarlabs.org> | 2012-03-09 15:39:47 (GMT) |
commit | b7533c7bc7ae06ca90f201d3c4701882d8836524 (patch) | |
tree | 10bae2d97798f90de48f2e0021756f8e1716e238 | |
parent | c25b7f068ea5330bea8da998e98ef65f883293ac (diff) |
Add server.fork() to simplify client testing
-rw-r--r-- | restful_document/__init__.py | 4 | ||||
-rw-r--r-- | restful_document/server.py | 264 |
2 files changed, 156 insertions, 112 deletions
diff --git a/restful_document/__init__.py b/restful_document/__init__.py index 430170d..ebc944e 100644 --- a/restful_document/__init__.py +++ b/restful_document/__init__.py @@ -23,7 +23,7 @@ from restful_document.router import \ Router from restful_document.server import \ - Server, application + config, application, fork, serve_forever from restful_document.user import \ User @@ -32,4 +32,4 @@ from restful_document.env import \ principal, request, responce, \ HTTPError, BadRequest, Unauthorized, Forbidden, NotFound, \ host, port, debug, foreground, logdir, rundir, trust_users, \ - keyfile, certfile, master + keyfile, certfile, master, auth diff --git a/restful_document/server.py b/restful_document/server.py index 8d7206b..c174a76 100644 --- a/restful_document/server.py +++ b/restful_document/server.py @@ -15,6 +15,7 @@ import os import sys +import time import signal import atexit import logging @@ -33,121 +34,150 @@ from restful_document import env, printf _LOGFILE_FORMAT = '%(asctime)s %(levelname)s %(name)s: %(message)s' +_application_name = 'sugar-network-server' -def application(name, documents): - """Create WSGI application. +def config(name, description, version, homepage, no_exit=False): + """Configure server. + + Function will override default parameters using configure files and + command-line arguments. :param name: id string to use for cases like configure file names - :param documents: - list of document classes, inherited from - `restful_document.Document`, to serve + :param description: + server description string + :param version: + server version + :param homepage: + home page for the server project :returns: - object that can be used as WSGI `application()` function + not processed arguments """ + global _application_name + _application_name = name + + parser = OptionParser( + usage='%prog [OPTIONS] [COMMAND]', + description=description, + add_help_option=False) + parser.print_version = lambda: sys.stdout.write('%s\n' % version) + + parser.add_option('-h', '--help', + help=_('show this help message and exit'), + action='store_true') + parser.add_option('-V', '--version', + help=_('show version number and exit'), + action='version') + util.Option.seek('active-document', ad.env) util.Option.seek('main', env) - util.Option.merge(None, - ['/etc/%s.conf' % name, '~/.config/%s/config' % name]) + util.Option.bind(parser, [ + '/etc/%s.conf' % name, + '~/.config/%s/config' % name, + ]) + + options, args = parser.parse_args() + + if not no_exit and not args and not options.help: + prog = basename(sys.argv[0]) + print 'Usage: %s [OPTIONS] [COMMAND]' % prog + print ' %s -h|--help' % prog + print + print description + print _HELP % homepage + exit(0) + + if options.help: + parser.print_help() + print _HELP % homepage + if not no_exit: + exit(0) + + util.Option.merge(options) + return args - if not env.debug.value: - logging_level = logging.WARNING - elif env.debug.value == 1: - logging_level = logging.INFO - else: - logging_level = logging.DEBUG - logging.basicConfig(level=logging_level, format=_LOGFILE_FORMAT) +def application(documents): + """Create WSGI application. + + :param documents: + list of document classes, inherited from + `restful_document.Document`, to serve + :returns: + object that can be used as WSGI `application()` function + + """ + _setup_logging() if env.master.value: node = ad.Master(documents) else: node = ad.Node(documents) - atexit.register(node.close) - return rd.Router(node) -class Server(object): - """Serve active_document documents from WSGI server.""" - - def __init__(self, name, description, version, homepage): - """Configure server. - - :param name: - id string to use for cases like configure file names - :param description: - server description string - :param version: - server version - :param homepage: - home page for the server project - - """ - self._documents = None - self._name = name - self._description = description - self._version = version - self._homepage = homepage - self._ssl_args = {} - - self._parser = OptionParser( - usage='%prog [OPTIONS] [COMMAND]', - description=self._description, - add_help_option=False) - self._parser.print_version = \ - lambda: sys.stdout.write('%s\n' % self._version) - self._parser.add_option('-h', '--help', - help=_('show this help message and exit'), - action='store_true') - self._parser.add_option('-V', '--version', - help=_('show version number and exit'), - action='version') - - util.Option.seek('active-document', ad.env) - util.Option.seek('main', env) - - util.Option.bind(self._parser, [ - '/etc/%s.conf' % self._name, - '~/.config/%s/config' % self._name, - ]) - self._options, self._args = self._parser.parse_args() - util.Option.merge(self._options) - - def serve_forever(self, documents, **ssl_args): - """Start server. - - :param documents: - list of document classes, inherited from - `restful_document.Document`, to serve - :param ssl_args: - arguments to pass to `ssl.wrap_socket` to enable SSL connections - - """ +def fork(documents): + """Run server in forked process. + + :param documents: + list of document classes, inherited from + `restful_document.Document`, to serve + :returns: + server process pid + + """ + child_pid = os.fork() + if child_pid: + # Let server to start listening port + time.sleep(1) + return child_pid + + _setup_logging() + _forward_stdout() + + node = ad.Master(documents) + httpd = WSGIServer((env.host.value, env.port.value), rd.Router(node)) + + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + finally: + httpd.stop() + node.close() + + # pylint: disable-msg=W0212 + os._exit(0) + + +def serve_forever(args, documents, **ssl_args): + """Process commands to control WSGI server. + + :param args: + list of commands to process + :param documents: + list of document classes, inherited from + `restful_document.Document`, to serve + :param ssl_args: + arguments to pass to `ssl.wrap_socket` to enable SSL connections + + """ + _Server(args, documents, ssl_args) + + +class _Server(object): + + def __init__(self, args, documents, ssl_args): + self._args = args self._documents = documents + self._ssl_args = ssl_args if env.keyfile.value: self._ssl_args['keyfile'] = env.keyfile.value if env.certfile.value: self._ssl_args['certfile'] = env.certfile.value - for arg, value in ssl_args.items(): - self._ssl_args[arg] = value - - if not self._args and not self._options.help: - prog = basename(sys.argv[0]) - print 'Usage: %s [OPTIONS] [COMMAND]' % prog - print ' %s -h|--help' % prog - print - print self._parser.description - print _HELP % self._homepage - exit(0) - - if self._options.help: - self._parser.print_help() - print _HELP % self._homepage - exit(0) command = self._args.pop(0) @@ -167,7 +197,7 @@ class Server(object): _('Unknown command "%s"') % command) exit(getattr(self, '_cmd_' + command)() or 0) except Exception: - printf.exception(_('Aborted %s due to error'), self._name) + printf.exception(_('Aborted %s due to error'), _application_name) exit(1) finally: printf.flush_hints() @@ -184,7 +214,7 @@ class Server(object): pidfile, pid = self._check_for_instance() if pid: logging.warning(_('%s is already run with pid %s'), - self._name, pid) + _application_name, pid) return 1 if env.foreground.value: self._launch() @@ -197,7 +227,7 @@ class Server(object): os.makedirs(env.rundir.value) enforce(os.access(env.rundir.value, os.W_OK), _('No write access to %s'), env.rundir.value) - self._forward_stdout() + _forward_stdout() self._daemonize(pidfile) return 0 @@ -207,27 +237,27 @@ class Server(object): os.kill(pid, signal.SIGTERM) return 0 else: - logging.warning(_('%s is not run'), self._name) + logging.warning(_('%s is not run'), _application_name) return 1 def _cmd_status(self): __, pid = self._check_for_instance() if pid: - printf.info(_('%s started'), self._name) + printf.info(_('%s started'), _application_name) return 0 else: - printf.info(_('%s stopped'), self._name) + printf.info(_('%s stopped'), _application_name) return 1 def _cmd_reload(self): __, pid = self._check_for_instance() if pid: os.kill(pid, signal.SIGHUP) - logging.info(_('Reload %s process'), self._name) + logging.info(_('Reload %s process'), _application_name) def _launch(self): logging.info(_('Start %s on %s:%s'), - self._name, env.host.value, env.port.value) + _application_name, env.host.value, env.port.value) if env.master.value: node = ad.Master(self._documents) @@ -238,12 +268,13 @@ class Server(object): rd.Router(node), **self._ssl_args) def sigterm_cb(signum, frame): - logging.info(_('Got signal %s, will stop %s'), signum, self._name) + logging.info(_('Got signal %s, will stop %s'), + signum, _application_name) httpd.stop() def sighup_cb(signum, frame): - logging.info(_('Reload %s on SIGHUP signal'), self._name) - self._forward_stdout() + logging.info(_('Reload %s on SIGHUP signal'), _application_name) + _forward_stdout() signal.signal(signal.SIGINT, sigterm_cb) signal.signal(signal.SIGTERM, sigterm_cb) @@ -257,7 +288,7 @@ class Server(object): def _check_for_instance(self): pid = None - pidfile = join(env.rundir.value, '%s.pid' % self._name) + pidfile = join(env.rundir.value, '%s.pid' % _application_name) if exists(pidfile): try: pid = int(file(pidfile).read().strip()) @@ -266,15 +297,6 @@ class Server(object): pid = None return pidfile, pid - def _forward_stdout(self): - log_path = abspath(join(env.logdir.value, '%s.log' % self._name)) - sys.stdout.flush() - sys.stderr.flush() - logfile = file(log_path, 'a+') - os.dup2(logfile.fileno(), sys.stdout.fileno()) - os.dup2(logfile.fileno(), sys.stderr.fileno()) - logfile.close() - def _daemonize(self, pid_path): pid_path = abspath(pid_path) @@ -311,6 +333,28 @@ class Server(object): exit(status) +def _setup_logging(): + if not env.debug.value: + logging_level = logging.WARNING + elif env.debug.value == 1: + logging_level = logging.INFO + else: + logging_level = logging.DEBUG + logging.basicConfig(level=logging_level, format=_LOGFILE_FORMAT) + + +def _forward_stdout(): + if not exists(env.logdir.value): + os.makedirs(env.logdir.value) + log_path = abspath(join(env.logdir.value, '%s.log' % _application_name)) + sys.stdout.flush() + sys.stderr.flush() + logfile = file(log_path, 'a+') + os.dup2(logfile.fileno(), sys.stdout.fileno()) + os.dup2(logfile.fileno(), sys.stderr.fileno()) + logfile.close() + + _HELP = """ Commands: start start in daemon mode |