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