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