root/flup/server/scgi_fork.py

Revision 59:5e584c75125a, 7.6 kB (checked in by Allan Saddi <allan@saddi.com>, 2 years ago)

Re-commit r2304 since it was found that mod_scgi does set SCRIPT_NAME/
PATH_INFO correctly when using SCGIMount (vs. SCGIHandler/SCGIServer).

Line 
1 # Copyright (c) 2005, 2006 Allan Saddi <allan@saddi.com>
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 # 1. Redistributions of source code must retain the above copyright
8 #    notice, this list of conditions and the following disclaimer.
9 # 2. Redistributions in binary form must reproduce the above copyright
10 #    notice, this list of conditions and the following disclaimer in the
11 #    documentation and/or other materials provided with the distribution.
12 #
13 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 # SUCH DAMAGE.
24 #
25 # $Id$
26
27 """
28 scgi - an SCGI/WSGI gateway.
29
30 For more information about SCGI and mod_scgi for Apache1/Apache2, see
31 <http://www.mems-exchange.org/software/scgi/>.
32
33 For more information about the Web Server Gateway Interface, see
34 <http://www.python.org/peps/pep-0333.html>.
35
36 Example usage:
37
38   #!/usr/bin/env python
39   import sys
40   from myapplication import app # Assume app is your WSGI application object
41   from scgi import WSGIServer
42   ret = WSGIServer(app).run()
43   sys.exit(ret and 42 or 0)
44
45 See the documentation for WSGIServer for more information.
46
47 About the bit of logic at the end:
48 Upon receiving SIGHUP, the python script will exit with status code 42. This
49 can be used by a wrapper script to determine if the python script should be
50 re-run. When a SIGINT or SIGTERM is received, the script exits with status
51 code 0, possibly indicating a normal exit.
52
53 Example wrapper script:
54
55   #!/bin/sh
56   STATUS=42
57   while test $STATUS -eq 42; do
58     python "$@" that_script_above.py
59     STATUS=$?
60   done
61 """
62
63 __author__ = 'Allan Saddi <allan@saddi.com>'
64 __version__ = '$Revision$'
65
66 import logging
67 import socket
68
69 from flup.server.scgi_base import BaseSCGIServer, Connection, NoDefault
70 from flup.server.preforkserver import PreforkServer
71
72 __all__ = ['WSGIServer']
73
74 class WSGIServer(BaseSCGIServer, PreforkServer):
75     """
76     SCGI/WSGI server. For information about SCGI (Simple Common Gateway
77     Interface), see <http://www.mems-exchange.org/software/scgi/>.
78
79     This server is similar to SWAP <http://www.idyll.org/~t/www-tools/wsgi/>,
80     another SCGI/WSGI server.
81
82     It differs from SWAP in that it isn't based on scgi.scgi_server and
83     therefore, it allows me to implement concurrency using threads. (Also,
84     this server was written from scratch and really has no other depedencies.)
85     Which server to use really boils down to whether you want multithreading
86     or forking. (But as an aside, I've found scgi.scgi_server's implementation
87     of preforking to be quite superior. So if your application really doesn't
88     mind running in multiple processes, go use SWAP. ;)
89     """
90     def __init__(self, application, scriptName=NoDefault, environ=None,
91                  bindAddress=('localhost', 4000), umask=None,
92                  allowedServers=None,
93                  loggingLevel=logging.INFO, debug=True, **kw):
94         """
95         scriptName is the initial portion of the URL path that "belongs"
96         to your application. It is used to determine PATH_INFO (which doesn't
97         seem to be passed in). An empty scriptName means your application
98         is mounted at the root of your virtual host.
99
100         environ, which must be a dictionary, can contain any additional
101         environment variables you want to pass to your application.
102
103         bindAddress is the address to bind to, which must be a string or
104         a tuple of length 2. If a tuple, the first element must be a string,
105         which is the host name or IPv4 address of a local interface. The
106         2nd element of the tuple is the port number. If a string, it will
107         be interpreted as a filename and a UNIX socket will be opened.
108
109         If binding to a UNIX socket, umask may be set to specify what
110         the umask is to be changed to before the socket is created in the
111         filesystem. After the socket is created, the previous umask is
112         restored.
113         
114         allowedServers must be None or a list of strings representing the
115         IPv4 addresses of servers allowed to connect. None means accept
116         connections from anywhere.
117
118         loggingLevel sets the logging level of the module-level logger.
119         """
120         BaseSCGIServer.__init__(self, application,
121                                 scriptName=scriptName,
122                                 environ=environ,
123                                 multithreaded=False,
124                                 multiprocess=True,
125                                 bindAddress=bindAddress,
126                                 umask=umask,
127                                 allowedServers=allowedServers,
128                                 loggingLevel=loggingLevel,
129                                 debug=debug)
130         for key in ('multithreaded', 'multiprocess', 'jobClass', 'jobArgs'):
131             if kw.has_key(key):
132                 del kw[key]
133         PreforkServer.__init__(self, jobClass=Connection, jobArgs=(self,), **kw)
134
135     def run(self):
136         """
137         Main loop. Call this after instantiating WSGIServer. SIGHUP, SIGINT,
138         SIGQUIT, SIGTERM cause it to cleanup and return. (If a SIGHUP
139         is caught, this method returns True. Returns False otherwise.)
140         """
141         self.logger.info('%s starting up', self.__class__.__name__)
142
143         try:
144             sock = self._setupSocket()
145         except socket.error, e:
146             self.logger.error('Failed to bind socket (%s), exiting', e[1])
147             return False
148
149         ret = PreforkServer.run(self, sock)
150
151         self._cleanupSocket(sock)
152
153         self.logger.info('%s shutting down%s', self.__class__.__name__,
154                          self._hupReceived and ' (reload requested)' or '')
155
156         return ret
157
158 def factory(global_conf, host=None, port=None, **local):
159     import paste_factory
160     return paste_factory.helper(WSGIServer, global_conf, host, port, **local)
161
162 if __name__ == '__main__':
163     def test_app(environ, start_response):
164         """Probably not the most efficient example."""
165         import cgi
166         start_response('200 OK', [('Content-Type', 'text/html')])
167         yield '<html><head><title>Hello World!</title></head>\n' \
168               '<body>\n' \
169               '<p>Hello World!</p>\n' \
170               '<table border="1">'
171         names = environ.keys()
172         names.sort()
173         for name in names:
174             yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
175                 name, cgi.escape(`environ[name]`))
176
177         form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ,
178                                 keep_blank_values=1)
179         if form.list:
180             yield '<tr><th colspan="2">Form data</th></tr>'
181
182         for field in form.list:
183             yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
184                 field.name, field.value)
185
186         yield '</table>\n' \
187               '</body></html>\n'
188
189     from wsgiref import validate
190     test_app = validate.validator(test_app)
191     WSGIServer(test_app,
192                loggingLevel=logging.DEBUG).run()
Note: See TracBrowser for help on using the browser.