MoinMoin/web/flup_frontend.py
author Thomas Waldmann <tw AT waldmann-edv DOT de>
Mon, 18 Jan 2010 23:05:58 +0100
changeset 5461 9d8e7ce3c3a2
parent 5378 b95d34820bc8
permissions -rw-r--r--
move sys.argv fix to better place
     1 # -*- coding: iso-8859-1 -*-
     2 """
     3     MoinMoin - Flup based WSGI adapters
     4 
     5     This module provides adapters between popular gateway interfaces
     6     like CGI, FastCGI, SCGI and AJP to the MoinMoin WSGI application.
     7     They are based on the adapters in the flup package and upon the
     8     MoinMoin.frontend.ServerFrontEnd to provide configuration via
     9     command line switches.
    10 
    11     Typically they are simply run from the CGI-scripts like this:
    12 
    13     > from MoinMoin.web.flup_frontend import CGIFrontEnd
    14     > CGIFrontEnd().run()
    15 
    16     They automatically parse the options given on the commandline and
    17     behave accordingly. Flup makes it possible to serve FCGI, SCGI and
    18     AJP from a bound network or unix socket, in different flavours of
    19     multiprocessing/multithreading.
    20 
    21     @copyright: 2008 MoinMoin:FlorianKrupicka,
    22                 2009 MoinMoin:ThomasWaldmann
    23     @license: GNU GPL, see COPYING for details.
    24 """
    25 
    26 import os, sys
    27 
    28 try:
    29     import flup.server.fcgi
    30     have_flup = True
    31     try:
    32         import flup.server.fcgi_single
    33         have_singlepatch = True
    34     except ImportError:
    35         have_singlepatch = False
    36 except ImportError:
    37     have_flup = False
    38 
    39 from MoinMoin.web.frontend import ServerFrontEnd, FrontEnd, FrontEndNotAvailable
    40 
    41 from MoinMoin import log
    42 logging = log.getLogger(__name__)
    43 
    44 if have_flup:
    45     class FlupFrontEnd(ServerFrontEnd):
    46         def add_options(self):
    47             super(FlupFrontEnd, self).add_options()
    48             parser = self.parser
    49             parser.add_option("--min-spare", dest="min_spare", type="int", metavar='MIN',
    50                               help=("Minimum spare threads/processes (when "
    51                                     "using threaded or forking servers)."))
    52             parser.add_option("--max-spare", dest="max_spare", type="int", metavar='MAX',
    53                               help=("Maximum spare threads/processes (when "
    54                                     "using threaded or forking servers)."))
    55             parser.add_option("--max-childs", dest="max_childs", type="int", metavar='CHILDS',
    56                               help=("Hard upper limit on threads/processes "
    57                                     "(when using threaded or forking servers)."))
    58             parser.add_option("-t", "--type", dest="server_type", metavar='TYPE',
    59                               help=("Type of server to use, e.g. single/threaded"
    60                                     "/forking. Defaults to 'single' when not "
    61                                     "bound to a socket and to 'threaded' when it is"))
    62 
    63         def run_server(self, application, options):
    64             server_type = options.server_type
    65 
    66             if not server_type:
    67                 if 'single' in self.server_types:
    68                     server_type = (options.port and 'threaded') or 'single'
    69                 else:
    70                     server_type = 'threaded'
    71 
    72             if server_type not in self.server_types:
    73                 raise TypeError("Unknown server type '%s'" % options.server_type)
    74 
    75             multi = server_type in ('threaded', 'forking')
    76 
    77             mod = self.server_types[server_type]
    78             mod = __import__(mod, globals(), locals(), ['WSGIServer'])
    79             WSGIServerWrapped = mod.WSGIServer
    80 
    81             class WSGIServer(WSGIServerWrapped):
    82                 # note: base class uses debug=False as default. as we use string values,
    83                 # better explicitely pass in "off", "web" or "external".
    84                 def error(self, req):
    85                     """ Override the default handler, so it implements debug=web/external/off. """
    86                     if self.debug == 'external':
    87                         raise
    88                     elif self.debug == 'web':
    89                         import cgitb
    90                         req.stdout.write('Content-Type: text/html\r\n\r\n' +
    91                                          cgitb.html(sys.exc_info()))
    92                     else: # 'off'
    93                         errorpage = """<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
    94 <html><head>
    95 <title>Unhandled Exception</title>
    96 </head><body>
    97 <h1>Unhandled Exception</h1>
    98 <p>An unhandled exception was thrown by the application.</p>
    99 </body></html>
   100 """
   101                         req.stdout.write('Content-Type: text/html\r\n\r\n' + errorpage)
   102 
   103 
   104             kwargs = {}
   105 
   106             kwargs['debug'] = options.debug or os.environ.get('MOIN_DEBUGGER', 'off')
   107 
   108             if options.port:
   109                 kwargs['bindAddress'] = (options.interface, options.port)
   110             elif options.interface and (
   111                  options.interface.startswith('/') or options.interface.startswith('./')):
   112                 kwargs['bindAddress'] = options.interface
   113 
   114             if options.min_spare and multi:
   115                 kwargs['minSpare'] = options.min_spare
   116             if options.max_spare and multi:
   117                 kwargs['maxSpare'] = options.max_spare
   118             if options.max_childs and multi:
   119                 if server_type == 'threaded':
   120                     kwargs['maxThreads'] = options.max_childs
   121                 else:
   122                     kwargs['maxChildren'] = options.max_childs
   123             logging.debug("WSGIServer(%r, %r)" % (application, kwargs))
   124             return WSGIServer(application, **kwargs).run()
   125 
   126     class CGIFrontEnd(FlupFrontEnd):
   127         server_types = {'threaded': 'flup.server.fcgi',
   128                         'forking': 'flup.server.fcgi_fork'}
   129         if have_singlepatch:
   130             server_types['single'] = 'flup.server.fcgi_single'
   131 
   132         def run(self, args=None):
   133             if 'GATEWAY_INTERFACE' in os.environ:
   134                 sys.argv = []
   135             super(CGIFrontEnd, self).run(args)
   136 
   137     class SCGIFrontEnd(FlupFrontEnd):
   138         server_types = {'threaded': 'flup.server.scgi',
   139                         'forking': 'flup.server.scgi_fork'}
   140 
   141     class AJPFrontEnd(FlupFrontEnd):
   142         server_types = {'threaded': 'flup.server.ajp',
   143                         'forking': 'flup.server.ajp_fork'}
   144 else:
   145     class CGIFrontEnd(FrontEnd):
   146         """ Simple WSGI CGI Adapter for fallback if flup is not installed. """
   147         def __init__(self):
   148             logging.warning("No flup-package installed, only basic CGI "
   149                             "support is available.")
   150             super(CGIFrontEnd, self).__init__()
   151 
   152         def run(self, args=None):
   153             if 'GATEWAY_INTERFACE' in os.environ:
   154                 sys.argv = []
   155             super(CGIFrontEnd, self).run(args)
   156 
   157         def run_server(self, application, options):
   158             from MoinMoin.web._fallback_cgi import WSGIServer
   159             return WSGIServer(application).run()
   160 
   161     _ERROR = """
   162 The flup package is not installed on your system. To make use of FCGI,
   163 SCGI or AJP adapters, you have to install it first. The MoinMoin source
   164 distribution provides a flup package in the contrib/flup-server
   165 directory. It is also patched to support non-threaded & non-forking
   166 behaviour. See contrib/flup-server/NOTES.moin for more information.
   167 """
   168     def SCGIFrontEnd():
   169         raise FrontEndNotAvailable(_ERROR)
   170     def AJPFrontEnd():
   171         raise FrontEndNotAvailable(_ERROR)