This guide describes how to use SCGI with Apache 2.x, to serve an application written in Python. The SCGI package also comes with modules to use the protocol with Apache 1.x or lighttpd, and frameworks for applications in other languages are available (including LISP).
A complete setup for an SCGI application involves these four components:
A web server to process incoming http requests--in our case, Apache 2.
The Apache SCGI module to delegate requests over the SCGI protocol.
An SCGI server process based on the Python scgi_server module.
An application to be run in child processes forked off by the server process.
A single SCGI server process serves only one application, but multiple instances of it. Each instance lives in its own child process of the SCGI server. More child processes are created on demand.
To install the module by hand, cd into the apache2 subdirectory of the SCGI source tree, and, as root, make install. On Debian-based systems and Gentoo, however, just install the libapache2-mod-scgi package. RPM users can install apache2-mod_scgi.
Next, set up the webserver to load the module and delegate requests (in this example, for the http path "/dynamic") to a separate server process accepting SCGI requests over a TCP socket. That server will typically run on the same machine as the http server, but it doesn't have to. Here's a snippet of Apache 2 config to delegate requests for the http path /dynamic to local TCP port 4000, which is the default:
# (This actually better set up permanently with the command line # "a2enmod scgi" but shown here for completeness) LoadModule scgi_module /usr/lib/apache2/modules/mod_scgi.so # Set up a location to be served by an SCGI server process SCGIMount /dynamic/ 127.0.0.1:4000
The deprecated way of delegating requests to an SCGI server is as follows:
<Location "/dynamic"> # Enable SCGI delegation SCGIHandler On # Delegate requests in the "/dynamic" path to daemon on local # server, port 4000 SCGIServer 127.0.0.1:4000 </Location>
Note that using SCGIMount instead of SCGIServer allows Apache to compute PATH_INFO as most software expects.
Here's a description of the configuration directives accepted by this Apache module. The information is largely derived from the source, so please excuse (or better yet: help improve) poor descriptions:
Directive |
Arguments |
Example |
Description |
---|---|---|---|
SCGIMount |
http path and IP/port pair |
/dynamic 127.0.0.1:4000 |
Path prefix and address of SCGI server |
SCGIServer |
IP address and port |
127.0.0.1:4000 |
Address and port of an SCGI server |
SCGIHandler |
On or Off |
On |
Enable delegation of requests through SCGI |
SCGIServerTimeout |
Number of seconds |
10 |
Timeout for communication with SCGI server |
To install the Python module for creating SCGI applications, go to the main SCGI source directory and run python setup.py build and then, as root, python setup.py install. Or, on most GNU/Linux systems, just install the python-scgi package.
The hard part is creating that application server process. The SCGI home page provides a server implementation in Python, called scgi_server.py, but this is really just a kind of support module. In order to port an application to use this server, write a main program in Python that imports this module and uses it. There is one, fairly complex, example that makes an application called Quixote run over SCGI.
The main program that you write for yourself creates a handler class describing how requests should be processed. You then tell the SCGI server module to loop forever, handling incoming requests and creating processes running your handler class as needed. It is your own responsibility to ensure that the server process is running.
Once the SCGI server process runs, it can accept requests forwarded by the http server. It will attempt to delegate every request that comes in to an existing child process, invoking its handler. If no child process is free to take the request, an additional child process is spawned (if the configured maximum number of child processes is not exceeded; if it is, the request blocks until a child process becomes free). If a handler "dies" (e.g. exits with an exception), a new child is spawned to replace it.
The server process must import scgi_server, then derive handler classes from scgi_server.SCGIHandler. Just scgi_server.py can also be run standalone, in which case it return pages displaying the details of your browser's request.
Your handler class should be derived from the SCGIHandler class defined in scgi_server.py, and override its produce() function to process a request. This is new in SCGI 1.12. Before that, the function to override was handle_connection() which involved a lot more work.
Besides self, the produce() function takes several arguments:
a dict mapping names of CGI parameters to request details
size (in bytes) of the request body
an input stream providing the request body
an output stream to write a page to.
Alternatively, override SCGIHandler.produce_cgilike() which can read the request body from standard input, write its output to standard output, and receives its request details both as an argument dict and added to its environment variables.
Your output should start out with an http header, so normally you'd want to start out with something like "Content-Type: text/plain\r\n\r\n" followed by page content.
Besides a header containing CGI variables, the request may also contain a body. The length of this body is passed as the CGI parameter CONTENT_LENGTH, but for your convenience is also converted to an integer and passed separately to the produce() function. If your handler needs the request body, it can read this number of bytes from its input socket. Do not rely on EOF; explicitly read the correct number of bytes (or less, if that's what you want).
Your main SCGI server program should create an SCGIServer object (defined in scgi_server.py), passing in your handler class for the handler_class parameter, and then call its serve() method which will loop indefinitely to process requests:
def main(): SCGIServer(handler_class=MyAppHandler).serve() if __name__ == "__main__": main()
You may want to support command-line options to influence various options of the server's operation. The SCGIServer initialization function takes several arguments:
Name |
Meaning |
Default |
---|---|---|
handler_class |
Class (not object!) of handler to invoke for requests |
SCGIHandler |
host |
Local IP address to bind to |
empty string |
port |
TCP port to listen on |
4000 |
max_children |
Maximum number of child processes to spawn |
5 |