Major refactor of the configuration mechanics.

* All configuration and bootup is handled by the Application objects.

* There is now a strict ordering on the precedence of configuration
settings:

  1. Each option is given a default value of some sort in options.ini

  2. Any detectable framework configuration settings override the hard
     coded defaults for options. Currently, only Paster applications
     have support for this.

  3. Anything that is specified in a Gunicorn configuration file (by
     default gunicorn.conf.py) overrides what was possibly set by a
     framework specific configuration source.

  4. Anything specified on the command line reins supreme. The command
     line is the final authority on a given configuration option.
     Though, not all configuration options are available via command
     line.

* Configuration metadata is pulled from an options.ini. In the future I'll
use this to build the example gunicorn.conf.py and the config.rst file
in docs/site/config.rst.

I haven't tested the differences thoroughly. The next item on my agenda
is to figure out a way to start testing Gunicorn that doesn't make my
eyes bleed.
This commit is contained in:
Paul J. Davis 2010-05-15 00:29:00 -04:00
parent 5268b8fbbb
commit 036f8b50d9
17 changed files with 835 additions and 479 deletions

View File

@ -3,6 +3,7 @@ include LICENSE
include NOTICE
include README.rst
include THANKS
include gunicorn/options.ini
recursive-include tests *
recursive-include examples *
recursive-include doc *

View File

@ -1,94 +0,0 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import os
import sys
from gunicorn import util
class Application(object):
"""\
An application interface for configuring and loading
the various necessities for any given web framework.
"""
def get_config(self):
return {}
def load(self):
raise NotImplementedError
class WSGIApplication(Application):
def __init__(self, app_uri):
self.app_uri = app_uri
def load(self):
return util.import_app(self.app_uri)
class DjangoApplication(Application):
def __init__(self, settings_modname, project_path):
self.project_path = project_path
self.settings_modname = settings_modname
# update sys.path
sys.path.insert(0, project_path)
sys.path.append(os.path.join(project_path, os.pardir))
def load(self):
import django.core.handlers.wsgi
os.environ['DJANGO_SETTINGS_MODULE'] = self.settings_modname
return django.core.handlers.wsgi.WSGIHandler()
class PasterApplication(Application):
def __init__(self, cfgurl, relpath, global_opts):
self.cfgurl = cfgurl
self.relpath = relpath
self.global_opts = global_opts
def local_conf(self):
from paste.deploy import loadwsgi
ctx = loadwsgi.loadcontext(loadwsgi.SERVER, self.cfgurl,
relative_to=self.relpath)
def mk_bind():
host = ctx.local_conf.get('host')
port = ctx.local_conf.get('port')
if host and port:
return '%s:%s' % (host, port)
elif host:
return host
ret = {}
vars = {
'bind': mk_bind,
'workers': lambda: ctx.local_conf.get('workers', 1),
'umask': lambda: int(ctx.local_conf.get('umask', UMASK)),
'group': lambda: ctx.local_conf.get('group'),
'user': lambda: ctx.local_conf.get('user')
}
for vn in vars:
if self.global_ops.get(vn):
val = vars[vn]()
if val:
ret[vn] = val
keys = ctx.local_conf.items()
keys = filter(self.global_opts.get, keys)
keys = filter(ret.has_key, keys)
ret.update((k, ctx.local_conf[k]) for k in keys)
if not self.global_opts.get("debug"):
ret['debug'] = (ctx.global_conf.get('debug') == "true")
ret['default_proc_name'] = ctx.global_conf.get('__file__')
return ret
def load(self):
from paste.deploy import loadapp
return loadapp(self.cfgurl, relative_to=self.relpath)

0
gunicorn/app/__init__.py Normal file
View File

100
gunicorn/app/base.py Normal file
View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import logging
import optparse
import os
from gunicorn import __version__
from gunicorn import util
from gunicorn.arbiter import Arbiter
from gunicorn.config import Config
from gunicorn.debug import spew
class Application(object):
"""\
An application interface for configuring and loading
the various necessities for any given web framework.
"""
def __init__(self, usage):
self.log = logging.getLogger(__name__)
self.cfg = Config(usage)
parser = self.cfg.parser()
opts, args = parser.parse_args()
cfg = self.init(parser, opts, args)
# Load up the any app specific configuration
if cfg:
for k, v in cfg:
self.cfg.set(k.lower(), v)
# Load up the config file if its found.
if opts.config and os.path.exists(opts.config):
cfg = globals().copy()
try:
execfile(opts.config, cfg, cfg)
except Exception, e:
print "Failed to read config file: %s" % opts.config
traceback.print_exc()
sys.exit(1)
for k, v in cfg.iteritems():
self.cfg.set(k.lower(), v)
# Lastly, update the configuration with any command line
# settings.
for k, v in opts.__dict__.iteritems():
if v is None or self.cfg.modified(k.lower()):
continue
self.cfg.set(k.lower(), v)
self.configure_logging()
def init(self, parser, opts, args):
raise NotImplementedError
def load(self):
raise NotImplementedError
def run(self):
if self.cfg.spew:
debug.spew()
if self.cfg.daemon:
util.daemonize()
else:
os.setpgrp()
Arbiter(self).run()
def configure_logging(self):
"""\
Set the log level and choose the destination for log output.
"""
logger = logging.getLogger('gunicorn')
handlers = []
if self.cfg.logfile != "-":
handlers.append(logging.FileHandler(self.cfg.logfile))
else:
handlers.append(logging.StreamHandler())
levels = {
"critical": logging.CRITICAL,
"error": logging.ERROR,
"warning": logging.WARNING,
"info": logging.INFO,
"debug": logging.DEBUG
}
loglevel = levels.get(self.cfg.loglevel.lower(), logging.INFO)
logger.setLevel(loglevel)
format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
datefmt = r"%Y-%m-%d %H:%M:%S"
for h in handlers:
h.setFormatter(logging.Formatter(format, datefmt))
logger.addHandler(h)

46
gunicorn/app/djangoapp.py Normal file
View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import os
import sys
import django.core.handlers.wsgi
from gunicorn import util
from gunicorn.app.base import Application
class DjangoApplication(Application):
def init(parser, opts, args):
self.project_path = os.getcwd()
if args:
settings_path = os.path.abspath(os.path.normpath(args[0]))
if not os.path.exists(settings_path):
self.no_settings(settings_path)
else:
self.project_path = os.path.dirname(settings_path)
else:
settings_path = os.path.join(project_path, "settings.py")
if not os.path.exists(settings_path):
self.no_settings(settings_path)
project_name = os.path.split(project_path)[-1]
settings_name, ext = os.path.splitext(os.path.basename(settings_path))
settings_modname = "%s.%s" % (project_name, settings_name)
self.cfg.default_proc_name = settings_modname
sys.path.insert(0, self.project_path)
sys.path.append(os.path.join(project_path, os.pardir))
def no_settings(self, path):
error = "Settings file '%s' not found in current folder.\n" % path
sys.stderr.write(error)
sys.stderr.flush()
sys.exit(1)
def load(self):
os.environ['DJANGO_SETTINGS_MODULE'] = self.settings_modname
return django.core.handlers.wsgi.WSGIHandler()

78
gunicorn/app/pasterapp.py Normal file
View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import os
import pkg_resources
import sys
from paste.deploy import loadapp, loadwsgi
SERVER = loadwsgi.SERVER
class PasterApplication(Application):
def init(self, parser, opts, args):
if len(args) != 1:
parser.error("No application name specified.")
cfgfname = os.path.normpath(os.path.join(os.getcwd(), args[0]))
cfgfname = os.path.abspath(cfgfname)
if not os.path.exists(cfgfname):
parser.error("Config file not found.")
self.cfgurl = 'config:%s' % cfgfname
self.relpath = os.path.dirname(cfgfname)
sys.path.insert(0, self.relpath)
pkg_resources.working_set.add_entry(self.relpath)
return self.app_config()
def app_config(self):
cx = loadwsgi.loadcontext(SERVER, self.cfgurl, relative_to=self.relpath)
gc, lc = cx.global_conf, cx.local_conf
cfg = {}
host, port = lc.get('host'), lc.get('port')
if host and port:
cfg['bind'] = '%s:%s' % (host, port)
elif host:
cfg['bind'] = host
cfg['workers'] = int(lc.get('workers', 1))
cfg['umask'] = int(lc.get('umask', 0))
cfg['default_proc_name'] = gc.get('__file__')
cfg.update(dict((k,v) for (k,v) in lc.items() if k not in cfg))
return cfg
def load(self):
return loadapp(self.cfgurl, relative_to=self.relpath)
class PasterServerApplication(Application):
def __init__(self, app, *args, **kwargs):
self.log = logging.getLogger(__name__)
self.cfg = Config()
self.app = app
cfg = {}
host, port = kwargs.get('host'), kwargs.get('port')
if host and port:
cfg['bind'] = '%s:%s' % (host, port)
elif host:
cfg['bind'] = host
if gcfg:
for k, v in list(gcfg.items()):
if k.lower() in self.cfg.settings:
self.cfg.set(k.lower(), v)
self.cfg.default_proc_name = kwargs.__file__
self.configure_logging(cfg)
def load(self):
return self.app

31
gunicorn/app/wsgiapp.py Normal file
View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import os
import sys
import traceback
from gunicorn import util
from gunicorn.app.base import Application
class WSGIApplication(Application):
def init(self, parser, opts, args):
if len(args) != 1:
parser.error("No application module specified.")
self.cfg.set("default_proc_name", args[0])
self.app_uri = args[0]
sys.path.insert(0, os.getcwd())
try:
self.load()
except:
print "Failed to import application: %s" % self.app_uri
traceback.print_exc()
sys.exit(1)
def load(self):
return util.import_app(self.app_uri)

View File

@ -44,24 +44,19 @@ class Arbiter(object):
if name[:3] == "SIG" and name[3] != "_"
)
def __init__(self, cfg, app):
self.cfg = cfg
self.app = app
def __init__(self, app):
self.cfg = app.cfg
self.app = app.load()
self.log = logging.getLogger(__name__)
self.address = cfg.address
self.num_workers = cfg.workers
self.debug = cfg.debug
self.timeout = cfg.timeout
self.proc_name = cfg.proc_name
try:
self.worker_class = cfg.worker_class
except ImportError, e:
self.log.error("%s" % e)
sys.exit(1)
self.address = self.cfg.address
self.num_workers = self.cfg.workers
self.debug = self.cfg.debug
self.timeout = self.cfg.timeout
self.proc_name = self.cfg.proc_name
self.worker_class = self.cfg.worker_class
self.pidfile = None
self.worker_age = 0
self.reexec_pid = 0
@ -301,7 +296,7 @@ class Arbiter(object):
os.environ['GUNICORN_FD'] = str(self.LISTENER.fileno())
os.chdir(self.START_CTX['cwd'])
self.cfg.before_exec(self)
self.cfg.pre_exec(self)
os.execvpe(self.START_CTX[0], self.START_CTX['args'], os.environ)
def murder_workers(self):
@ -366,7 +361,7 @@ class Arbiter(object):
self.worker_age += 1
worker = self.worker_class(self.worker_age, self.pid, self.LISTENER,
self.app, self.timeout/2.0, self.cfg)
self.cfg.before_fork(self, worker)
self.cfg.pre_fork(self, worker)
pid = os.fork()
if pid != 0:
self.WORKERS[pid] = worker
@ -378,7 +373,7 @@ class Arbiter(object):
util._setproctitle("worker [%s]" % self.proc_name)
self.log.debug("Booting worker: %s (age: %s)" % (
worker_pid, self.worker_age))
self.cfg.after_fork(self, worker)
self.cfg.post_fork(self, worker)
worker.init_process()
sys.exit(0)
except SystemExit:

View File

@ -3,99 +3,153 @@
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import ConfigParser
import grp
import inspect
import optparse
import os
import pwd
import sys
import types
from gunicorn import __version__
from gunicorn import util
class Setting(object):
def __init__(self, name, opts):
self.name = name
self.section = opts["section"]
self.order = int(opts.get("order", 0))
self.cli = opts["cli"].split()
self.type = opts["type"].strip()
self.arity = opts.get('arity', None)
if self.arity:
self.arity = int(self.arity)
self.meta = opts.get('meta', "").strip() or None
self.action = opts.get('action', "store").strip()
self.default = opts.get('default').strip() or None
self.desc = opts["desc"]
self.short = self.desc.splitlines()[0].strip()
# Special case the callable types.
self.value = None
if self.default and self.type != 'callable':
self.set(self.default, modified=False)
# A flag that tells us if this setting
# has been altered
self.modified = False
def add_option(self, parser):
if not len(self.cli):
return
args = tuple(self.cli)
opttypes = {
"pos_int": "int",
"bool": None
}
kwargs = {
"dest": self.name,
"metavar": self.meta or None,
"action": self.action,
"type": opttypes.get(self.type, "string"),
"default": None,
"help": "%s [%s]" % (self.short, self.default)
}
parser.add_option(*args, **kwargs)
def get(self):
return self.value
def set(self, val, modified=True):
validator = getattr(self, "set_%s" % self.type)
self.value = validator(val)
self.modified = modified
def set_bool(self, val):
if isinstance(val, types.BooleanType):
return val
if val.lower().strip() == "true":
return True
elif val.lower().strip() == "false":
return False
else:
raise ValueError("Invalid boolean: %s" % val)
def set_pos_int(self, val):
if not isinstance(val, (types.IntType, types.LongType)):
val = int(val, 0)
if val < 0:
raise ValueError("Value must be positive: %s" % val)
return val
def set_string(self, val):
return val.strip()
def set_callable(self, val):
if not callable(val):
raise TypeError("Value is not callable: %s" % val)
arity = len(inspect.getargspec(val)[0])
if arity != self.arity:
raise TypeError("Value must have an arity of: %s" % self.arity)
return val
class Config(object):
DEFAULT_CONFIG_FILE = 'gunicorn.conf.py'
DEFAULTS = dict(
backlog=2048,
bind='127.0.0.1:8000',
daemon=False,
debug=False,
default_proc_name = os.getcwd(),
group=None,
keepalive=2,
logfile='-',
loglevel='info',
pidfile=None,
proc_name = None,
spew=False,
timeout=30,
tmp_upload_dir=None,
umask="0",
user=None,
workers=1,
worker_connections=1000,
worker_class="egg:gunicorn#sync",
after_fork=lambda server, worker: server.log.info(
"Worker spawned (pid: %s)" % worker.pid),
def __init__(self, usage):
self.settings = {}
self.usage = usage
path = os.path.join(os.path.dirname(__file__), "options.ini")
opts = ConfigParser.SafeConfigParser()
if not len(opts.read(path)):
raise RuntimeError("Options configuration file is missing!")
before_fork=lambda server, worker: True,
for sect in opts.sections():
self.settings[sect] = Setting(sect, dict(opts.items(sect)))
before_exec=lambda server: server.log.info("Forked child, reexecuting")
)
def __init__(self, opts, path=None):
self.cfg = {}
self.opts = opts
if path is None:
path = os.path.join(os.getcwd(), self.DEFAULT_CONFIG_FILE)
self.path = path
self.load()
def update(self, opts):
opts = dict((k, v) for (k, v) in opts.iteritems() if v is not None)
self.opts.update(opts)
self.cfg.update(opts)
def load(self):
self.cfg = self.DEFAULTS.copy()
if os.path.exists(self.path):
try:
execfile(self.path, globals(), self.cfg)
except Exception, e:
sys.exit("Could not read config file: %r\n %s" % (self.path, e))
self.cfg.pop("__builtins__", None)
opts = dict((k, v) for (k, v) in opts.iteritems() if v is not None)
self.cfg.update(opts)
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
pass
return self.cfg[key]
# Special case hook functions
self.settings['pre_fork'].set(def_pre_fork, modified=False)
self.settings['post_fork'].set(def_post_fork, modified=False)
self.settings['pre_exec'].set(def_pre_exec, modified=False)
def __getattr__(self, key):
try:
super(Config, self).__getattribute__(key)
except AttributeError:
if key in self.cfg:
return self.cfg[key]
raise
def __contains__(self, key):
return (key in self.cfg)
def __iter__(self):
return self.cfg.iteritems()
def __getattr__(self, name):
if name not in self.settings:
raise AttributeError("No configuration setting for: %s" % name)
return self.settings[name].get()
def __setattr__(self, name, value):
if name != "settings" and name in self.settings:
raise AttributeError("Invalid access!")
super(Config, self).__setattr__(name, value)
def set(self, name, value):
if name not in self.settings:
raise AttributeError("No configuration setting for: %s" % name)
self.settings[name].set(value)
def get(self, key, default=None):
return self.cfg.get(key, default)
def parser(self):
kwargs = {
"usage": self.usage,
"version": __version__,
"formatter": HelpFormatter()
}
parser = optparse.OptionParser(**kwargs)
keys = self.settings.keys()
def sorter(k):
return (self.settings[k].section, self.settings[k].order)
keys.sort(key=sorter)
for k in keys:
self.settings[k].add_option(parser)
return parser
def was_modified(self, name):
return self.settings[name].modified
@property
def worker_class(self):
uri = self.cfg.get('worker_class', None) or 'egg:gunicorn#sync'
uri = self.settings['worker_class'].get()
worker_class = util.load_worker_class(uri)
if hasattr(worker_class, "setup"):
worker_class.setup()
@ -103,72 +157,69 @@ class Config(object):
@property
def workers(self):
if not self.cfg.get('workers'):
raise RuntimeError("invalid workers number")
workers = int(self.cfg["workers"])
if not workers:
raise RuntimeError("number of workers < 1")
if self.cfg['debug'] == True:
workers = 1
return workers
if self.settings['debug'].get():
return 1
return self.settings['workers'].get()
@property
def address(self):
if not self.cfg['bind']:
raise RuntimeError("Listener address is not set")
return util.parse_address(util.to_bytestring(self.cfg['bind']))
@property
def umask(self):
if not self.cfg.get('umask'):
return 0
umask = self.cfg['umask']
if isinstance(umask, basestring):
return int(umask, 0)
return umask
bind = self.settings['bind'].get()
return util.parse_address(util.to_bytestring(bind))
@property
def uid(self):
if not self.cfg.get('user'):
return os.geteuid()
user = self.settings['user'].get()
user = self.cfg.get('user')
if user.isdigit() or isinstance(user, int):
uid = int(user)
if not user:
return os.geteuid()
elif user.isdigit() or isinstance(user, int):
return int(user)
else:
uid = pwd.getpwnam(user).pw_uid
return uid
return pwd.getpwnam(user).pw_uid
@property
def gid(self):
if not self.cfg.get('group'):
group = self.settings['group'].get()
if not group:
return os.getegid()
group = self.cfg.get('group')
if group.isdigit() or isinstance(group, int):
gid = int(group)
elif group.isdigit() or isinstance(user, int):
return int(group)
else:
gid = grp.getgrnam(group).gr_gid
return gid
return grp.getgrnam(group).gr_gid
@property
def proc_name(self):
if not self.cfg.get('proc_name'):
return self.cfg.get('default_proc_name')
return self.cfg.get('proc_name')
def after_fork(self, *args):
return self._hook("after_fork", *args)
def before_fork(self, *args):
return self._hook("before_fork", *args)
def before_exec(self, *args):
return self._hook("before_exec", *args)
pn = self.settings['proc_name'].get()
if pn:
return pn
else:
return self.settings['default_proc_name']
@property
def pre_fork(self):
return self.settings['pre_fork'].get()
@property
def post_fork(self):
return self.settings['post_fork'].get()
@property
def pre_exec(self):
return self.settings['pre_exec'].get()
def def_pre_fork(server, worker):
pass
def def_post_fork(server, worker):
server.log.info("Worker spawned (pid: %s)" % worker.pid)
def def_pre_exec(server):
server.log.info("Forked child, reexecuting.")
class HelpFormatter(optparse.IndentedHelpFormatter):
pass
def _hook(self, hookname, *args):
hook = self.cfg.get(hookname)
if not hook:
return
if not callable(hook):
raise RuntimeError("%r hook isn't a callable" % hookname)
return hook(*args)

View File

@ -45,9 +45,8 @@ class Request(object):
"SERVER_SOFTWARE": "gunicorn/%s" % __version__
}
def __init__(self, socket, client_address, server_address, conf):
self.debug = conf['debug']
self.conf = conf
def __init__(self, cfg, socket, client_address, server_address):
self.cfg = cfg
self.socket = socket
self.client_address = client_address
@ -85,18 +84,17 @@ class Request(object):
self.socket.send("HTTP/1.1 100 Continue\r\n\r\n")
if not self.parser.content_len and not self.parser.is_chunked:
wsgi_input = TeeInput(self.socket, self.parser, StringIO(),
self.conf)
wsgi_input = TeeInput(self.cfg, self.socket, self.parser, StringIO())
content_length = "0"
else:
wsgi_input = TeeInput(self.socket, self.parser, buf2, self.conf)
wsgi_input = TeeInput(self.cfg, self.socket, self.parser, buf2)
content_length = str(wsgi_input.len)
# This value should evaluate true if an equivalent application
# object may be simultaneously invoked by another process, and
# should evaluate false otherwise. In debug mode we fall to one
# worker so we comply to pylons and other paster app.
wsgi_multiprocess = (self.debug == False)
wsgi_multiprocess = self.cfg.workers > 1
# authors should be aware that REMOTE_HOST and REMOTE_ADDR
# may not qualify the remote addr:

View File

@ -26,8 +26,8 @@ class TeeInput(object):
CHUNK_SIZE = util.CHUNK_SIZE
def __init__(self, socket, parser, buf, conf):
self.conf = conf
def __init__(self, cfg, socket, parser, buf):
self.cfg = cfg
self.buf = StringIO()
self.parser = parser
self._sock = socket
@ -39,7 +39,7 @@ class TeeInput(object):
elif self._len and self._len < util.MAX_BODY:
self.tmp = StringIO()
else:
self.tmp = tempfile.TemporaryFile(dir=self.conf['tmp_upload_dir'])
self.tmp = tempfile.TemporaryFile(dir=self.cfg['tmp_upload_dir'])
if len(buf.getvalue()) > 0:
chunk, self.buf = parser.filter_body(buf)

View File

@ -2,176 +2,32 @@
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import logging
import optparse as op
import os
import pkg_resources
import sys
from gunicorn.arbiter import Arbiter
from gunicorn.config import Config
from gunicorn.debug import spew
from gunicorn import util, __version__
LOG_LEVELS = {
"critical": logging.CRITICAL,
"error": logging.ERROR,
"warning": logging.WARNING,
"info": logging.INFO,
"debug": logging.DEBUG
}
UMASK = "0"
def options():
""" build command lines options passed to OptParse object """
return [
op.make_option('-c', '--config', dest='config', type='string',
help='Config file. [%default]'),
op.make_option('-b', '--bind', dest='bind',
help='Adress to listen on. Ex. 127.0.0.1:8000 or unix:/tmp/gunicorn.sock'),
op.make_option('-w', '--workers', dest='workers',
help='Number of workers to spawn. [1]'),
op.make_option('-k', '--worker-class', dest='worker_class',
help="The type of request processing to use "+
"[egg:gunicorn#sync]"),
op.make_option('-p','--pid', dest='pidfile',
help='set the background PID FILE'),
op.make_option('-D', '--daemon', dest='daemon', action="store_true",
help='Run daemonized in the background.'),
op.make_option('-m', '--umask', dest="umask", type='string',
help="Define umask of daemon process"),
op.make_option('-u', '--user', dest="user",
help="Change worker user"),
op.make_option('-g', '--group', dest="group",
help="Change worker group"),
op.make_option('-n', '--name', dest='proc_name',
help="Process name"),
op.make_option('--log-level', dest='loglevel',
help='Log level below which to silence messages. [info]'),
op.make_option('--log-file', dest='logfile',
help='Log to a file. - equals stdout. [-]'),
op.make_option('-d', '--debug', dest='debug', action="store_true",
default=False, help='Debug mode. only 1 worker.'),
op.make_option('--spew', dest='spew', action="store_true",
default=False, help="Install a trace hook")
]
def main(usage, get_app):
"""\
Used by the various runners to setup options and
launch the arbiter.
"""
vrs = "%prog " + __version__
parser = op.OptionParser(usage=usage, option_list=options(), version=vrs)
opts, args = parser.parse_args()
cfg = Config(opts.__dict__, opts.config)
app = get_app(parser, opts, args)
cfg.update(app.get_config())
if cfg.spew:
spew()
if cfg.daemon:
daemonize()
else:
os.setpgrp()
configure_logging(cfg)
Arbiter(cfg, app.load()).run()
def run():
"""\
The ``gunicorn`` command line runner for launcing Gunicorn with
generic WSGI applications.
"""
sys.path.insert(0, os.getcwd())
def get_app(parser, opts, args):
if len(args) != 1:
parser.error("No application module specified.")
opts.default_proc_name = args[0]
application = app.WSGIApplication(args[0])
try:
application.load()
except Exception, e:
parser.error("Failed to import application module:\n %s" % e)
return application
main("%prog [OPTIONS] APP_MODULE", get_app)
"""
from gunicorn.app.wsgiapp import WSGIApplication
WSGIApplication("%prog [OPTIONS] APP_MODULE").run()
def run_django():
"""\
The ``gunicorn_django`` command line runner for launching Django
applications.
"""
def settings_notfound(path):
error = "Settings file '%s' not found in current folder.\n" % path
sys.stderr.write(error)
sys.stderr.flush()
sys.exit(1)
def get_app(parser, opts, args):
import django.core.handlers.wsgi
project_path = os.getcwd()
if args:
settings_path = os.path.abspath(os.path.normpath(args[0]))
if not os.path.exists(settings_path):
settings_notfound(settings_path)
else:
project_path = os.path.dirname(settings_path)
else:
settings_path = os.path.join(project_path, "settings.py")
if not os.path.exists(settings_path):
settings_notfound(settings_path)
# set environ
project_name = os.path.split(project_path)[-1]
settings_name, ext = os.path.splitext(os.path.basename(settings_path))
settings_modname = "%s.%s" % (project_name, settings_name)
opts.default_proc_name = settings_modname
# django wsgi app
return app.DjangoApplication(settings_modname, project_path)
main("%prog [OPTIONS] [SETTINGS_PATH]", get_app)
from gunicorn.app.djangoapp import DjangoApplication
DjangoApplication("%prog [OPTIONS] [SETTINGS_PATH]").run()
def run_paster():
"""\
The ``gunicorn_paster`` command for launcing Paster compatible
apllications like Pylons or Turbogears2
"""
from paste.deploy import loadwsgi
from gunicorn.app.pasterapp import PasterApplication
PasterApplication("%prog [OPTIONS] pasteconfig.ini").run()
def get_app(parser, opts, args):
if len(args) != 1:
parser.error("No application name specified.")
cfgfname = os.path.normpath(os.path.join(os.getcwd(), args[0]))
cfgfname = os.path.abspath(cfgfname)
if not os.path.exists(cfgfname):
parser.error("Config file not found.")
cfgurl = 'config:%s' % cfgfname
relpath = os.path.dirname(cfgfname)
# load module in sys path
sys.path.insert(0, relpath)
# add to eggs
pkg_resources.working_set.add_entry(relpath)
return app.PasterApplication(cfgurl, relpath, opts.__dict__)
main("%prog [OPTIONS] pasteconfig.ini", get_app)
def paste_server(app, gcfg=None, host="127.0.0.1", port=None, *args, **kwargs):
def paste_server(*args, **kwargs):
"""\
A paster server.
@ -183,77 +39,9 @@ def paste_server(app, gcfg=None, host="127.0.0.1", port=None, *args, **kwargs):
port = 5000
"""
opts = kwargs.copy()
if port and not host.startswith("unix:"):
bind = "%s:%s" % (host, port)
else:
bind = host
opts['bind'] = bind
from gunicorn.app.pasterapp import PasterServerApplication
PasterServerApplication(app, *args, **kwargs).run()
if gcfg:
for key, value in list(gcfg.items()):
if value and value is not None:
if key == "debug":
value = (value == "true")
opts[key] = value
opts['default_proc_name'] = opts['__file__']
cfg = Config(opts)
if cfg.spew:
spew()
if cfg.daemon:
daemonize()
else:
os.setpgrp()
configure_logging(cfg)
Arbiter(cfg, app).run()
def daemonize():
"""\
Standard daemonization of a process. Code is basd on the
ActiveState recipe at:
http://code.activestate.com/recipes/278731/
"""
if not 'GUNICORN_FD' in os.environ:
if os.fork() == 0:
os.setsid()
if os.fork() != 0:
os.umask(0)
else:
os._exit(0)
else:
os._exit(0)
maxfd = util.get_maxfd()
# Iterate through and close all file descriptors.
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR, fd wasn't open to begin with (ignored)
pass
os.open(util.REDIRECT_TO, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)
def configure_logging(opts):
"""\
Set the log level and choose the destination for log output.
"""
handlers = []
if opts['logfile'] != "-":
handlers.append(logging.FileHandler(opts['logfile']))
else:
handlers.append(logging.StreamHandler())
loglevel = LOG_LEVELS.get(opts['loglevel'].lower(), logging.INFO)
logger = logging.getLogger('gunicorn')
logger.setLevel(loglevel)
format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
datefmt = r"%Y-%m-%d %H:%M:%S"
for h in handlers:
h.setFormatter(logging.Formatter(format, datefmt))
logger.addHandler(h)

334
gunicorn/options.ini Normal file
View File

@ -0,0 +1,334 @@
# This file is read by config.py to set up the default
# information for all server settings. It is here so
# that we can use the data to generate all the various
# docs related to configuration information.
#
# Config file
#
[config]
section = Config
cli = -c --config
meta = FILE
type = string
default = gunicorn.conf.py
desc = The path to a Gunicorn config file.
By default Gunicorn will try to read a file named 'gunicorn.conf.py' in the
current directory.
Only has an effect when specified on the command line or as part of an
application specific configuration.
#
# Server Socket
#
[bind]
section = Server Socket
order = 0
cli = -b --bind
meta = ADDRESS
type = string
default = 127.0.0.1:8000
desc = The socket to bind.
[backlog]
section = Server Socket
order = 1
cli = --backlog
meta = INT
type = pos_int
default = 2048
desc = The maximum number of pending connections.
This refers to the number of clients that can be waiting to be served.
Exceeding this number results in the client getting an error when attempting
to connect. It should only affect servers under significant load.
Must be a positive integer. Generally set in the 64-2048 range.
#
# Worker processes
#
[workers]
section = Worker Processes
order = 1
cli = -w --workers
meta = INT
type = pos_int
default = 1
desc = The number of worker process for handling requests.
A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll want to
vary this a bit to find the best for your particular application's work
load.
[worker_class]
section = Worker Processes
order = 2
cli = -k --worker-class
meta = STRING
type = string
default = egg:gunicorn#sync
desc = The type of workers to use.
The default async class should handle most 'normal' types of work loads.
You'll want to read http://gunicorn/deployment.hml for information on when
you might want to choose one of the other worker classes.
An string referring to a 'gunicorn.workers' entry point or a MODULE:CLASS
pair where CLASS is a subclass of gunicorn.workers.base.Worker. The default
provided values are:
egg:gunicorn#sync
egg:gunicorn#eventlet - Requires eventlet >= 0.9.7
egg:gunicorn#gevent - Requires gevent >= 0.12.2 (?)
egg:gunicorn#tornado - Requires tornado >= 0.2
[worker_connections]
section = Worker Processes
order = 3
cli = --worker-connections
meta = INT
type = pos_int
default = 1000
desc = The maximum number of simultaneous clients.
This setting only affects the Eventlet and Gevent worker types.
A positive integer generally set to around 1000.
[timeout]
section = Worker Processes
order = 4
cli = -t --timeout
meta = INT
type = pos_int
default = 30
desc = Workers silent for more than this many seconds are killed and restarted.
Generally set to thirty seconds. Only set this noticeably higher if you're
sure of the repercussions for sync workers. For the non sync workers it just
means that the worker process is still communicating and is not tied to the
length of time required to handle a single request.
[keepalive]
section = Worker Processes
order = 5
cli = --keep-alive
meta = INT
type = pos_int
default = 2
desc = The number of seconds to wait for requests on a Keep-Alive connection.
A positive integer. Generally set in the 1-5 seconds range.
#
# Debugging
#
[debug]
section = Debugging
order = 1
cli = -d --debug
type = bool
action = store_true
default = False
desc = Turn on debugging in the server.
This limits the number of worker processes to 1 and changes some error
handling that's sent to clients.
[spew]
section = Debugging
order = 2
cli = --spew
type = bool
action = store_true
default = False
desc = Install a trace function that spews every line executed by the server.
This is the nuclear option.
#
# Server mechanics
#
[daemon]
section = Server Mechanics
order = 1
cli = -D --daemon
action = store_true
type = bool
default = False
desc = Daemonize the Gunicorn process.
Detaches the server from the controlling terminal and enters the background.
[pidfile]
section = Server Mechanics
order = 2
cli = -p --pid
meta = FILE
type = string
default =
desc = A filename to use for the PID file.
If not set, no PID file will be written.
[user]
section = Server Mechanics
order = 3
cli = -u --user
type = string
default =
desc = Switch worker processes to run as this user.
A valid user id (as an integer) or the name of a user that can be retrieved
with a call to pwd.getpwnam(value) or None to not change the worker process
user.
[group]
section = Server Mechanics
order = 4
cli = -g --group
type = string
default =
desc = Switch worker process to run as this group.
A valid group id (as an integer) or the name of a user that can be retrieved
with a call to pwd.getgrnam(value) or None to change the worker processes
group.
[umask]
section = Server Mechanics
order = 5
cli = -m --umask
meta = INT
type = pos_int
default = 0
desc = A bit mask for the file mode on files written by Gunicorn.
Note that this affects unix socket permissions.
A valid value for the os.umask(mode) call or a string compatible with
int(value, 0) (0 means Python guesses the base, so values like "0", "0xFF",
"0022" are valid for decimal, hex, and octal representations)
[tmp_upload_dir]
section = Server Mechanics
order = 6
cli =
meta = DIR
type = string
default =
desc = Directory to store temporary request data as they are read.
This may disappear in the near future.
This path should be writable by the process permissions set for Gunicorn
workers. If not specified, Gunicorn will choose a system generated temporary
directory.
#
# Logging
#
[logfile]
section = Logging
order = 1
cli = --log-file
meta = FILE
type = string
default = -
desc = The log file to write to.
"-" means log to stdout.
[loglevel]
section = Logging
order = 2
cli = --log-level
meta = LEVEL
type = string
default = info
desc = The granularity of log output
Valid level names are:
debug
info
warning
error
critical
#
# Process naming
#
[proc_name]
section = Process Naming
order = 1
cli = -n --name
meta = STRING
type = string
default = gunicorn
desc = A base to use with setproctitle for process naming.
This affects things like 'ps' and 'top'. If you're going to be running more
than one instance of Gunicorn you'll probably want to set a name to tell
them apart. This requires that you install the setproctitle module.
It defaults to 'gunicorn'.
[default_proc_name]
section = Process Naming
order = 2
cli =
type = string
default = gunicorn
desc = Internal setting that is adjusted for each type of application.
#
# Server hooks
#
[pre_fork]
section = Server Hooks
order = 1
cli =
type = callable
arity = 2
default =
desc = Called just before a worker is forked.
The callable needs to accept two instance variables for the Arbiter and new
Worker.
[post_fork]
section = Server Hooks
order = 2
cli =
type = callable
arity = 2
default =
desc = Called just after a worker has been forked.
The callable needs to accept two instance variables for the Arbiter and new
Worker.
[pre_exec]
section = Server Hooks
order = 3
cli =
type = callable
arity = 1
default =
desc = Called just before a new master process is forked.
The callable needs to accept a single instance variable for the Arbiter.
Cannot be specified from the command line.

View File

@ -36,7 +36,7 @@ class BaseSocket(object):
if not bound:
self.bind(sock)
sock.setblocking(0)
sock.listen(self.conf['backlog'])
sock.listen(self.conf.backlog)
return sock

View File

@ -208,4 +208,33 @@ def to_bytestring(s):
return s
def is_hoppish(header):
return header.lower().strip() in hop_headers
return header.lower().strip() in hop_headers
def daemonize():
"""\
Standard daemonization of a process. Code is basd on the
ActiveState recipe at:
http://code.activestate.com/recipes/278731/
"""
if not 'GUNICORN_FD' in os.environ:
if os.fork() == 0:
os.setsid()
if os.fork() != 0:
os.umask(0)
else:
os._exit(0)
else:
os._exit(0)
maxfd = util.get_maxfd()
# Iterate through and close all file descriptors.
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR, fd wasn't open to begin with (ignored)
pass
os.open(util.REDIRECT_TO, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)

View File

@ -21,8 +21,7 @@ class AsyncWorker(Worker):
self.worker_connections = self.cfg.worker_connections
def keepalive_request(self, client, addr):
return http.KeepAliveRequest(client, addr, self.address,
self.cfg)
return http.KeepAliveRequest(self.cfg, client, addr, self.address)
def handle(self, client, addr):
try:

View File

@ -89,7 +89,7 @@ class SyncWorker(Worker):
util.close(client)
def handle_request(self, client, addr):
req = http.Request(client, addr, self.address, self.cfg)
req = http.Request(self.cfg, client, addr, self.address)
try:
environ = req.read()
if not environ or not req.parser.status_line: