durusmail: quixote-users: Asyncore based scgi server
Asyncore based scgi server
2003-09-07
Asyncore based scgi server
Patrik Simons
2003-09-07
I needed to get quixote up and running under windows, and since scgi's
passfd.c doesn't compile under mingw32 I wrote an asyncore based scgi
server.
It's working fine together with scgi-cgi (however, scgi-cgi doesn't compile
unchanged
under mingw32 either).

Patrik

scgi_server.py:

# Copyright 2003 by Patrik Simons, Neotide Ab (patrik.simons@neotide.fi)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
met:
#
#  1. Redistributions of source code must retain the above copyright
notice,
#     this list of conditions and the following disclaimer.
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#  3. The name of the author may not be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Asyncore based SCGI server.

Example use:

import asyncore
from quixote.publish import Publisher
from scgi_server import SCGIServer, QuixoteProducer
app = Publisher('test')
# add app setup here
class TestProducer(QuixoteProducer):
     publisher = app
server = SCGIServer(TestProducer)
asyncore.loop()
"""
import asynchat
import asyncore
import cStringIO
import socket

class SCGIServer(asyncore.dispatcher):
    def __init__(self, producer, here=('',4001)):
        asyncore.dispatcher.__init__(self)
        self.producer = producer
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(here)
        self.listen(5)

    def handle_accept(self):
        sock_addr = self.accept()
        if sock_addr:
            sock, addr = sock_addr
        else:
            return # Failed
        SCGIHandler(sock, self.producer)

class SCGIHandler(asynchat.async_chat):
    def __init__(self, sock, producer):
        asynchat.async_chat.__init__(self, sock)
        self.set_terminator(':')
        self.producer = producer
        self.env = None
        self.buffer = []

    def collect_incoming_data(self, data):
        self.buffer.append(data)

    def found_terminator(self):
        data = ''.join(self.buffer)
        self.buffer = []
        if self.get_terminator() == ':':
            self.set_terminator(int(data)+1) # Headers + comma
            return
        elif not self.env:
            self.env = self.read_env(data)
            length = int(self.env['CONTENT_LENGTH'])
            if length:
                self.set_terminator(length)
                return
            data = ''
        self.push_with_producer(self.producer(self.env, data))
        self.close_when_done()
        self.env = None
        self.set_terminator(':')

    def read_env(self, data):
        headers = data.split('\0')
        headers = headers[:-1] # Skip ending comma
        assert len(headers)%2 == 0, 'malformed scgi headers'
        env = {}
        for i in xrange(0, len(headers), 2):
            env[headers[i]] = headers[i+1]
        return env

class QuixoteProducer:
    publisher = None # Override in subclass

    def __init__(self, env, data):
        self.env = env
        self.data = data
        self.stdin = cStringIO.StringIO(data)
        self.stderr = self.publisher.error_log
        self.has_data = 1

    def more(self):
        if not self.has_data:
            return ''
        stdout = cStringIO.StringIO()
        try:
            self.publisher.publish(self.stdin, stdout, self.stderr,
self.env)
        except:
            pass
            #raise # Change to pass in production
        self.stdin.close()
        data = stdout.getvalue()
        stdout.close()
        self.has_data = 0
        return data

reply