| 1 |
""" |
|---|
| 2 |
FastCGI (or SCGI, or AJP1.3 ...) server that implements the WSGI protocol. |
|---|
| 3 |
|
|---|
| 4 |
Uses the flup python package: http://www.saddi.com/software/flup/ |
|---|
| 5 |
|
|---|
| 6 |
This is a adaptation of the flup package to add FastCGI server support |
|---|
| 7 |
to run Django apps from Web servers that support the FastCGI protocol. |
|---|
| 8 |
This module can be run standalone or from the django-admin / manage.py |
|---|
| 9 |
scripts using the "runfcgi" directive. |
|---|
| 10 |
|
|---|
| 11 |
Run with the extra option "help" for a list of additional options you can |
|---|
| 12 |
pass to this server. |
|---|
| 13 |
""" |
|---|
| 14 |
|
|---|
| 15 |
from django.utils import importlib |
|---|
| 16 |
import sys, os |
|---|
| 17 |
|
|---|
| 18 |
__version__ = "0.1" |
|---|
| 19 |
__all__ = ["runfastcgi"] |
|---|
| 20 |
|
|---|
| 21 |
FASTCGI_HELP = r""" |
|---|
| 22 |
Run this project as a fastcgi (or some other protocol supported |
|---|
| 23 |
by flup) application. To do this, the flup package from |
|---|
| 24 |
http://www.saddi.com/software/flup/ is required. |
|---|
| 25 |
|
|---|
| 26 |
runfcgi [options] [fcgi settings] |
|---|
| 27 |
|
|---|
| 28 |
Optional Fcgi settings: (setting=value) |
|---|
| 29 |
protocol=PROTOCOL fcgi, scgi, ajp, ... (default fcgi) |
|---|
| 30 |
host=HOSTNAME hostname to listen on.. |
|---|
| 31 |
port=PORTNUM port to listen on. |
|---|
| 32 |
socket=FILE UNIX socket to listen on. |
|---|
| 33 |
method=IMPL prefork or threaded (default prefork) |
|---|
| 34 |
maxrequests=NUMBER number of requests a child handles before it is |
|---|
| 35 |
killed and a new child is forked (0 = no limit). |
|---|
| 36 |
maxspare=NUMBER max number of spare processes / threads |
|---|
| 37 |
minspare=NUMBER min number of spare processes / threads. |
|---|
| 38 |
maxchildren=NUMBER hard limit number of processes / threads |
|---|
| 39 |
daemonize=BOOL whether to detach from terminal. |
|---|
| 40 |
pidfile=FILE write the spawned process-id to this file. |
|---|
| 41 |
workdir=DIRECTORY change to this directory when daemonizing. |
|---|
| 42 |
debug=BOOL set to true to enable flup tracebacks |
|---|
| 43 |
outlog=FILE write stdout to this file. |
|---|
| 44 |
errlog=FILE write stderr to this file. |
|---|
| 45 |
umask=UMASK umask to use when daemonizing (default 022). |
|---|
| 46 |
|
|---|
| 47 |
Examples: |
|---|
| 48 |
Run a "standard" fastcgi process on a file-descriptor |
|---|
| 49 |
(for webservers which spawn your processes for you) |
|---|
| 50 |
$ manage.py runfcgi method=threaded |
|---|
| 51 |
|
|---|
| 52 |
Run a scgi server on a TCP host/port |
|---|
| 53 |
$ manage.py runfcgi protocol=scgi method=prefork host=127.0.0.1 port=8025 |
|---|
| 54 |
|
|---|
| 55 |
Run a fastcgi server on a UNIX domain socket (posix platforms only) |
|---|
| 56 |
$ manage.py runfcgi method=prefork socket=/tmp/fcgi.sock |
|---|
| 57 |
|
|---|
| 58 |
Run a fastCGI as a daemon and write the spawned PID in a file |
|---|
| 59 |
$ manage.py runfcgi socket=/tmp/fcgi.sock method=prefork \ |
|---|
| 60 |
daemonize=true pidfile=/var/run/django-fcgi.pid |
|---|
| 61 |
|
|---|
| 62 |
""" |
|---|
| 63 |
|
|---|
| 64 |
FASTCGI_OPTIONS = { |
|---|
| 65 |
'protocol': 'fcgi', |
|---|
| 66 |
'host': None, |
|---|
| 67 |
'port': None, |
|---|
| 68 |
'socket': None, |
|---|
| 69 |
'method': 'fork', |
|---|
| 70 |
'daemonize': None, |
|---|
| 71 |
'workdir': '/', |
|---|
| 72 |
'pidfile': None, |
|---|
| 73 |
'maxspare': 5, |
|---|
| 74 |
'minspare': 2, |
|---|
| 75 |
'maxchildren': 50, |
|---|
| 76 |
'maxrequests': 0, |
|---|
| 77 |
'debug': None, |
|---|
| 78 |
'outlog': None, |
|---|
| 79 |
'errlog': None, |
|---|
| 80 |
'umask': None, |
|---|
| 81 |
} |
|---|
| 82 |
|
|---|
| 83 |
def fastcgi_help(message=None): |
|---|
| 84 |
print FASTCGI_HELP |
|---|
| 85 |
if message: |
|---|
| 86 |
print message |
|---|
| 87 |
return False |
|---|
| 88 |
|
|---|
| 89 |
def runfastcgi(argset=[], **kwargs): |
|---|
| 90 |
options = FASTCGI_OPTIONS.copy() |
|---|
| 91 |
options.update(kwargs) |
|---|
| 92 |
for x in argset: |
|---|
| 93 |
if "=" in x: |
|---|
| 94 |
k, v = x.split('=', 1) |
|---|
| 95 |
else: |
|---|
| 96 |
k, v = x, True |
|---|
| 97 |
options[k.lower()] = v |
|---|
| 98 |
|
|---|
| 99 |
if "help" in options: |
|---|
| 100 |
return fastcgi_help() |
|---|
| 101 |
|
|---|
| 102 |
try: |
|---|
| 103 |
import flup |
|---|
| 104 |
except ImportError, e: |
|---|
| 105 |
print >> sys.stderr, "ERROR: %s" % e |
|---|
| 106 |
print >> sys.stderr, " Unable to load the flup package. In order to run django" |
|---|
| 107 |
print >> sys.stderr, " as a FastCGI application, you will need to get flup from" |
|---|
| 108 |
print >> sys.stderr, " http://www.saddi.com/software/flup/ If you've already" |
|---|
| 109 |
print >> sys.stderr, " installed flup, then make sure you have it in your PYTHONPATH." |
|---|
| 110 |
return False |
|---|
| 111 |
|
|---|
| 112 |
flup_module = 'server.' + options['protocol'] |
|---|
| 113 |
|
|---|
| 114 |
if options['method'] in ('prefork', 'fork'): |
|---|
| 115 |
wsgi_opts = { |
|---|
| 116 |
'maxSpare': int(options["maxspare"]), |
|---|
| 117 |
'minSpare': int(options["minspare"]), |
|---|
| 118 |
'maxChildren': int(options["maxchildren"]), |
|---|
| 119 |
'maxRequests': int(options["maxrequests"]), |
|---|
| 120 |
} |
|---|
| 121 |
flup_module += '_fork' |
|---|
| 122 |
elif options['method'] in ('thread', 'threaded'): |
|---|
| 123 |
wsgi_opts = { |
|---|
| 124 |
'maxSpare': int(options["maxspare"]), |
|---|
| 125 |
'minSpare': int(options["minspare"]), |
|---|
| 126 |
'maxThreads': int(options["maxchildren"]), |
|---|
| 127 |
} |
|---|
| 128 |
else: |
|---|
| 129 |
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.") |
|---|
| 130 |
|
|---|
| 131 |
wsgi_opts['debug'] = options['debug'] is not None |
|---|
| 132 |
|
|---|
| 133 |
try: |
|---|
| 134 |
module = importlib.import_module('.%s' % flup_module, 'flup') |
|---|
| 135 |
WSGIServer = module.WSGIServer |
|---|
| 136 |
except: |
|---|
| 137 |
print "Can't import flup." + flup_module |
|---|
| 138 |
return False |
|---|
| 139 |
|
|---|
| 140 |
# Prep up and go |
|---|
| 141 |
from django.core.handlers.wsgi import WSGIHandler |
|---|
| 142 |
|
|---|
| 143 |
if options["host"] and options["port"] and not options["socket"]: |
|---|
| 144 |
wsgi_opts['bindAddress'] = (options["host"], int(options["port"])) |
|---|
| 145 |
elif options["socket"] and not options["host"] and not options["port"]: |
|---|
| 146 |
wsgi_opts['bindAddress'] = options["socket"] |
|---|
| 147 |
elif not options["socket"] and not options["host"] and not options["port"]: |
|---|
| 148 |
wsgi_opts['bindAddress'] = None |
|---|
| 149 |
else: |
|---|
| 150 |
return fastcgi_help("Invalid combination of host, port, socket.") |
|---|
| 151 |
|
|---|
| 152 |
if options["daemonize"] is None: |
|---|
| 153 |
# Default to daemonizing if we're running on a socket/named pipe. |
|---|
| 154 |
daemonize = (wsgi_opts['bindAddress'] is not None) |
|---|
| 155 |
else: |
|---|
| 156 |
if options["daemonize"].lower() in ('true', 'yes', 't'): |
|---|
| 157 |
daemonize = True |
|---|
| 158 |
elif options["daemonize"].lower() in ('false', 'no', 'f'): |
|---|
| 159 |
daemonize = False |
|---|
| 160 |
else: |
|---|
| 161 |
return fastcgi_help("ERROR: Invalid option for daemonize parameter.") |
|---|
| 162 |
|
|---|
| 163 |
daemon_kwargs = {} |
|---|
| 164 |
if options['outlog']: |
|---|
| 165 |
daemon_kwargs['out_log'] = options['outlog'] |
|---|
| 166 |
if options['errlog']: |
|---|
| 167 |
daemon_kwargs['err_log'] = options['errlog'] |
|---|
| 168 |
if options['umask']: |
|---|
| 169 |
daemon_kwargs['umask'] = int(options['umask']) |
|---|
| 170 |
|
|---|
| 171 |
if daemonize: |
|---|
| 172 |
from django.utils.daemonize import become_daemon |
|---|
| 173 |
become_daemon(our_home_dir=options["workdir"], **daemon_kwargs) |
|---|
| 174 |
|
|---|
| 175 |
if options["pidfile"]: |
|---|
| 176 |
fp = open(options["pidfile"], "w") |
|---|
| 177 |
fp.write("%d\n" % os.getpid()) |
|---|
| 178 |
fp.close() |
|---|
| 179 |
|
|---|
| 180 |
WSGIServer(WSGIHandler(), **wsgi_opts).run() |
|---|
| 181 |
|
|---|
| 182 |
if __name__ == '__main__': |
|---|
| 183 |
runfastcgi(sys.argv[1:]) |
|---|