--- /dev/null
+[[!meta title="Simple Common Gateway Interface"]]
+
+Basic [[CGI]] starts a new process to handle each request. This is
+simple, but resource hungry. To make things more efficient, a number
+of protocols have developed that keep a CGI server running in the
+background. The frontend webserver then passes requests back to the
+CGI server for processing without having to fire up a new program.
+
+[FastCGI][] is the original CGI server protocol, but here we'll focus
+on it's simpler cousin [SCGI][]. I write most of my scripts in
+[[Python]], so I use the handy [scgi module][module] to write the
+server (`www-apps/scgi` on [[Gentoo]]). There's a great article on
+[Linux Journal][LJ] to walk you through things, but in short, you need
+something like:
+
+ import scgi
+ import scgi.scgi_server
+ import urlparse
+
+ class YourHandler(scgi.scgi_server.SCGIHandler):
+ def produce(self, env, bodysize, input, output):
+ url = env.get('DOCUMENT_URI', None)
+ if url is None: # fall back to the request URI
+ url = env.get('REQUEST_URI', None)
+ data = urlparse.parse_qs(env.get('QUERY_STRING', ''))
+ output.write('Status: 200 OK\n')
+ output.write('Content-type: text/plain; charset=UTF-8\n')
+ output.write('\n')
+ output.write('URI: {}\n'.format(url))
+ output.write('data: {}\n'.format(data))
+
+ if __name__ == '__main__':
+ server = scgi.scgi_server.SCGIServer(
+ handler_class=YourHandler, host='localhost', port=4000)
+ server.serve()
+
+Fire this script up, point your server at `localhost:4000`, and you're
+good to go!
+
+I've written up a little script to test your SCGI server from the
+command line: [[scgi-test.py]]:
+
+ $ scgi-test.py /your/uri/
+
+Configuring your frontend webserver to point to the SCGI server is
+beyond the scope of this post. I give an [[Nginx example|Nginx]] in
+my Nginx post.
+
+
+[FastCGI]: http://en.wikipedia.org/wiki/FastCGI
+[SCGI]: http://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
+[module]: http://python.ca/scgi/
+[LJ]: http://www.linuxjournal.com/article/9310
+
+[[!tag tags/tools]]
+[[!tag tags/web]]
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+
+"""Simple testing for SCGI servers.
+"""
+
+import socket as _socket
+
+
+__version__ = '0.1'
+
+
+def get_socket(args):
+ """Open a socket specified by the command line arguments.
+
+ This could be extended to support Unix sockets.
+ """
+ return _socket.create_connection((args.host, args.port))
+
+def netstring(string):
+ """Convert a string into a netstring.
+
+ >>> netstring('hello world!')
+ '12:hello world!,'
+ """
+ return '{}:{},'.format(len(string), string)
+
+def header(method, uri, data=None):
+ """Return the content string of an SCGI header."""
+ try:
+ content_length = len(data)
+ except TypeError:
+ content_length = 0
+ strings = [
+ 'CONTENT_LENGTH\x00{}\x00'.format(content_length),
+ 'SCGI\x00{}\x00'.format(1),
+ 'REQUEST_METHOD\x00{}\x00'.format(method),
+ 'REQUEST_URI\x00{}\x00'.format(uri),
+ ]
+ return ''.join(strings)
+
+def recvall(socket):
+ """Recieve all data from a socket until it closes."""
+ ret = []
+ while True:
+ r = socket.recv(1024)
+ if not r:
+ break
+ ret.append(r)
+ return ''.join(ret)
+
+def request(socket, data=None, **kwargs):
+ """Send a request and return the response string."""
+ socket.sendall(netstring(header(data=data, **kwargs)))
+ if data:
+ socket.sendall(data)
+ return recvall(socket)
+
+
+if __name__ == '__main__':
+ import argparse
+ import sys
+
+ parser = argparse.ArgumentParser(description=__doc__, version=__version__)
+ parser.add_argument(
+ '--host', default='localhost', help='hostname')
+ parser.add_argument(
+ '-p', '--port', default=4000, type=int, help='port number')
+ parser.add_argument(
+ '-m', '--method', default='GET', help='request method')
+ parser.add_argument(
+ 'uri', help='request URI')
+
+ args = parser.parse_args()
+
+ socket = get_socket(args)
+ response = None
+ try:
+ response = request(socket=socket, method=args.method, uri=args.uri)
+ finally:
+ socket.close()
+ if response:
+ sys.stdout.write(response)