Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2012-03-09 15:39:47 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-03-09 15:39:47 (GMT)
commitb7533c7bc7ae06ca90f201d3c4701882d8836524 (patch)
tree10bae2d97798f90de48f2e0021756f8e1716e238
parentc25b7f068ea5330bea8da998e98ef65f883293ac (diff)
Add server.fork() to simplify client testing
-rw-r--r--restful_document/__init__.py4
-rw-r--r--restful_document/server.py264
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