Add SCGI post and scgi-test.py script.
authorW. Trevor King <wking@drexel.edu>
Tue, 21 Feb 2012 15:18:27 +0000 (10:18 -0500)
committerW. Trevor King <wking@drexel.edu>
Tue, 21 Feb 2012 15:18:27 +0000 (10:18 -0500)
posts/SCGI.mdwn [new file with mode: 0644]
posts/SCGI/scgi-test.py [new file with mode: 0755]

diff --git a/posts/SCGI.mdwn b/posts/SCGI.mdwn
new file mode 100644 (file)
index 0000000..b4c19b8
--- /dev/null
@@ -0,0 +1,56 @@
+[[!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]]
diff --git a/posts/SCGI/scgi-test.py b/posts/SCGI/scgi-test.py
new file mode 100755 (executable)
index 0000000..ee75387
--- /dev/null
@@ -0,0 +1,98 @@
+#!/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)