SVN: r25476 - in trunk/quixote: . demo server

Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Wed, 27 Oct 2004 17:56:10 -0400
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Author: nascheme
Date: 2004-10-27 17:54:59 -0400 (Wed, 27 Oct 2004)
New Revision: 25476

Added:
   trunk/quixote/server/_fcgi.py
   trunk/quixote/server/cgi_server.py
   trunk/quixote/server/fastcgi_server.py
   trunk/quixote/server/medusa_server.py
   trunk/quixote/server/scgi_server.py
   trunk/quixote/server/twisted_server.py
Removed:
   trunk/quixote/demo/demo.cgi
   trunk/quixote/fcgi.py
   trunk/quixote/server/medusa_http.py
   trunk/quixote/server/twisted_http.py
Modified:
   trunk/quixote/demo/__init__.py
   trunk/quixote/demo/integer_ui.py
   trunk/quixote/demo/pages.ptl
   trunk/quixote/http_response.py
   trunk/quixote/publish.py
   trunk/quixote/server/__init__.py
Log:
Use a common pattern for interfacing with HTTP servers.  Simplify
and cleanup the Medusa and Twisted code.  Add a SCGI server module
(it makes more sense to be part of Quixote rather than in the "scgi"
package).

The main method of the Publisher is now process_request().  It takes
a request object and returns a response object.  All the server modules
use it and it's their job to create requests and deal with the
responses.

Add a 'content_type' attribute to HTTPResponse.  Refactor to make it
harder to screw up the character set of the response.


Modified: trunk/quixote/demo/__init__.py
===================================================================
--- trunk/quixote/demo/__init__.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/demo/__init__.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -5,14 +5,16 @@
 
 import sys
 import os
-from quixote import get_response
+from quixote import get_response, enable_ptl
+from quixote.publish import Publisher
 from quixote.directory import Directory, Resolving
+from quixote.errors import PublishError
+from quixote.util import StaticDirectory, StaticFile
+enable_ptl()
 from quixote.demo.pages import _q_index, _q_exception_handler, dumpreq
 from quixote.demo.integer_ui import IntegerUI
 from quixote.demo.session import SessionUI
 from quixote.demo import forms
-from quixote.errors import PublishError
-from quixote.util import StaticDirectory, StaticFile
 
 class DemoUI(Resolving, Directory):
 
@@ -52,3 +54,7 @@
     _curdir = os.path.dirname(forms.__file__)
     srcdir = StaticDirectory(_curdir, list_directory=1)
     q_ico = StaticFile(os.path.join(_curdir, 'q.ico'))
+
+
+def create_publisher():
+    return Publisher(DemoUI(), display_exceptions='plain')

Deleted: trunk/quixote/demo/demo.cgi
===================================================================
--- trunk/quixote/demo/demo.cgi	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/demo/demo.cgi	2004-10-27 21:54:59 UTC (rev 25476)
@@ -1,16 +0,0 @@
-#!/www/python/bin/python
-
-# Example driver script for the Quixote demo: publishes the contents of
-# the quixote.demo package.
-
-from quixote import enable_ptl, Publisher
-
-# Install the import hook that enables PTL modules.
-enable_ptl()
-
-# Create a Publisher instance
-from quixote.demo import DemoUI
-app = Publisher(DemoUI(), display_exceptions='plain')
-
-# Enter the publishing main loop
-app.publish_cgi()

Modified: trunk/quixote/demo/integer_ui.py
===================================================================
--- trunk/quixote/demo/integer_ui.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/demo/integer_ui.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -24,7 +24,7 @@
         if self.n > 10000:
             sys.stderr.write("warning: possible denial-of-service attack "
                              "(request for factorial(%d))\n" % self.n)
-        get_response().set_header("content-type", "text/plain")
+        get_response().set_content_type("text/plain")
         return "%d! = %d\n" % (self.n, fact(self.n))
 
     def _q_index(self):

Modified: trunk/quixote/demo/pages.ptl
===================================================================
--- trunk/quixote/demo/pages.ptl	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/demo/pages.ptl	2004-10-27 21:54:59 UTC (rev 25476)
@@ -11,7 +11,6 @@
     print "debug message from the index page"
     package_name = str('.').join(__name__.split(str('.'))[:-1])
     module_name = __name__
-    module_file = __file__
     """
     <html>
     <head><title>Quixote Demo</title></head>
@@ -22,7 +21,7 @@
     <code>%(package_name)s</code> package.  This index function is
     actually a PTL template, <code>_q_index()</code>, in the
     <code>%(module_name)s</code> PTL module.  Look in
-    <a href="srcdir/pages.ptl">%(module_file)s</a> to
+    <a href="srcdir/pages.ptl">demo/pages.ptl</a> to
     see the source code for this PTL template.)
     </p>
 

Deleted: trunk/quixote/fcgi.py
===================================================================
--- trunk/quixote/fcgi.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/fcgi.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -1,462 +0,0 @@
-#!/usr/local/bin/python1.5
-#------------------------------------------------------------------------
-#               Copyright (c) 1998 by Total Control Software
-#                         All Rights Reserved
-#------------------------------------------------------------------------
-#
-# Module Name:  fcgi.py
-#
-# Description:  Handles communication with the FastCGI module of the
-#               web server without using the FastCGI developers kit, but
-#               will also work in a non-FastCGI environment, (straight CGI.)
-#               This module was originally fetched from someplace on the
-#               Net (I don't remember where and I can't find it now...) and
-#               has been significantly modified to fix several bugs, be more
-#               readable, more robust at handling large CGI data and return
-#               document sizes, and also to fit the model that we had previously
-#               used for FastCGI.
-#
-#     WARNING:  If you don't know what you are doing, don't tinker with this
-#               module!
-#
-# Creation Date:    1/30/98 2:59:04PM
-#
-# License:      This is free software.  You may use this software for any
-#               purpose including modification/redistribution, so long as
-#               this header remains intact and that you do not claim any
-#               rights of ownership or authorship of this software.  This
-#               software has been tested, but no warranty is expressed or
-#               implied.
-#
-#------------------------------------------------------------------------
-
-__revision__ = "$Id$"
-
-
-import  os, sys, string, socket, errno, struct
-from    cStringIO   import StringIO
-import  cgi
-
-#---------------------------------------------------------------------------
-
-# Set various FastCGI constants
-# Maximum number of requests that can be handled
-FCGI_MAX_REQS=1
-FCGI_MAX_CONNS = 1
-
-# Supported version of the FastCGI protocol
-FCGI_VERSION_1 = 1
-
-# Boolean: can this application multiplex connections?
-FCGI_MPXS_CONNS=0
-
-# Record types
-FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST   = 3
-FCGI_PARAMS        = 4 ; FCGI_STDIN         = 5 ; FCGI_STDOUT        = 6
-FCGI_STDERR        = 7 ; FCGI_DATA          = 8 ; FCGI_GET_VALUES    = 9
-FCGI_GET_VALUES_RESULT = 10
-FCGI_UNKNOWN_TYPE = 11
-FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
-
-# Types of management records
-ManagementTypes = [FCGI_GET_VALUES]
-
-FCGI_NULL_REQUEST_ID = 0
-
-# Masks for flags component of FCGI_BEGIN_REQUEST
-FCGI_KEEP_CONN = 1
-
-# Values for role component of FCGI_BEGIN_REQUEST
-FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3
-
-# Values for protocolStatus component of FCGI_END_REQUEST
-FCGI_REQUEST_COMPLETE = 0               # Request completed nicely
-FCGI_CANT_MPX_CONN    = 1               # This app can't multiplex
-FCGI_OVERLOADED       = 2               # New request rejected; too busy
-FCGI_UNKNOWN_ROLE     = 3               # Role value not known
-
-
-error = 'fcgi.error'
-
-
-#---------------------------------------------------------------------------
-
-# The following function is used during debugging; it isn't called
-# anywhere at the moment
-
-def error(msg):
-    "Append a string to /tmp/err"
-    errf = open('/tmp/err', 'a+')
-    errf.write(msg+'\n')
-    errf.close()
-
-#---------------------------------------------------------------------------
-
-class record:
-    "Class representing FastCGI records"
-    def __init__(self):
-        self.version = FCGI_VERSION_1
-        self.recType = FCGI_UNKNOWN_TYPE
-        self.reqId   = FCGI_NULL_REQUEST_ID
-        self.content = ""
-
-    #----------------------------------------
-    def readRecord(self, sock, unpack=struct.unpack):
-        (self.version, self.recType, self.reqId, contentLength,
-         paddingLength) = unpack(">BBHHBx", sock.recv(8))
-
-        content = ""
-        while len(content) < contentLength:
-            content = content + sock.recv(contentLength - len(content))
-        self.content = content
-
-        if paddingLength != 0:
-            padding = sock.recv(paddingLength)
-
-        # Parse the content information
-        if self.recType == FCGI_BEGIN_REQUEST:
-            (self.role, self.flags) = unpack(">HB", content[:3])
-
-        elif self.recType == FCGI_UNKNOWN_TYPE:
-            self.unknownType = ord(content[0])
-
-        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
-            self.values = {}
-            pos = 0
-            while pos < len(content):
-                name, value, pos = readPair(content, pos)
-                self.values[name] = value
-
-        elif self.recType == FCGI_END_REQUEST:
-            (self.appStatus, self.protocolStatus) = unpack(">IB", content[0:5])
-
-    #----------------------------------------
-    def writeRecord(self, sock, pack=struct.pack):
-        content = self.content
-        if self.recType == FCGI_BEGIN_REQUEST:
-            content = pack(">HBxxxxx", self.role, self.flags)
-
-        elif self.recType == FCGI_UNKNOWN_TYPE:
-            content = pack(">Bxxxxxx", self.unknownType)
-
-        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
-            content = ""
-            for i in self.values.keys():
-                content = content + writePair(i, self.values[i])
-
-        elif self.recType == FCGI_END_REQUEST:
-            content = pack(">IBxxx", self.appStatus, self.protocolStatus)
-
-        cLen = len(content)
-        eLen = (cLen + 7) & (0xFFFF - 7)    # align to an 8-byte boundary
-        padLen = eLen - cLen
-
-        hdr = pack(">BBHHBx", self.version, self.recType, self.reqId, cLen,
-                   padLen)
-
-        ##debug.write('Sending fcgi record: %s\n' % repr(content[:50]) )
-        sock.send(hdr + content + padLen*'\000')
-
-#---------------------------------------------------------------------------
-
-_lowbits = ~(1L << 31) # everything but the 31st bit
-
-def readPair(s, pos):
-    nameLen = ord(s[pos]) ; pos = pos+1
-    if nameLen & 128:
-        pos = pos + 3
-        nameLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
-    valueLen = ord(s[pos]) ; pos = pos+1
-    if valueLen & 128:
-        pos = pos + 3
-        valueLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
-    return ( s[pos:pos+nameLen], s[pos+nameLen:pos+nameLen+valueLen],
-             pos+nameLen+valueLen )
-
-#---------------------------------------------------------------------------
-
-_highbit = (1L << 31)
-
-def writePair(name, value):
-    l = len(name)
-    if l < 128:
-        s = chr(l)
-    else:
-        s = struct.pack(">I", l | _highbit)
-    l = len(value)
-    if l < 128:
-        s = s + chr(l)
-    else:
-        s = s + struct.pack(">I", l | _highbit)
-    return s + name + value
-
-#---------------------------------------------------------------------------
-
-def HandleManTypes(r, conn):
-    if r.recType == FCGI_GET_VALUES:
-        r.recType = FCGI_GET_VALUES_RESULT
-        v = {}
-        vars = {'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
-                'FCGI_MAX_REQS'  : FCGI_MAX_REQS,
-                'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
-        for i in r.values.keys():
-            if vars.has_key(i): v[i] = vars[i]
-        r.values = vars
-        r.writeRecord(conn)
-
-#---------------------------------------------------------------------------
-#---------------------------------------------------------------------------
-
-
-_isFCGI = 1         # assume it is until we find out for sure
-
-def isFCGI():
-    return _isFCGI
-
-
-
-#---------------------------------------------------------------------------
-
-
-_init = None
-_sock = None
-
-class FCGI:
-    def __init__(self):
-        self.haveFinished = 0
-        if _init == None:
-            _startup()
-        if not _isFCGI:
-            self.haveFinished = 1
-            self.inp = sys.__stdin__
-            self.out = sys.__stdout__
-            self.err = sys.__stderr__
-            self.env = os.environ
-            return
-
-        if os.environ.has_key('FCGI_WEB_SERVER_ADDRS'):
-            good_addrs = string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
-            good_addrs = map(string.strip, good_addrs)        # Remove whitespace
-        else:
-            good_addrs = None
-
-        self.conn, addr = _sock.accept()
-        stdin, data = "", ""
-        self.env = {}
-        self.requestId = 0
-        remaining = 1
-
-        # Check if the connection is from a legal address
-        if good_addrs != None and addr not in good_addrs:
-            raise error, 'Connection from invalid server!'
-
-        while remaining:
-            r = record()
-            r.readRecord(self.conn)
-
-            if r.recType in ManagementTypes:
-                HandleManTypes(r, self.conn)
-
-            elif r.reqId == 0:
-                # Oh, poopy.  It's a management record of an unknown
-                # type.  Signal the error.
-                r2 = record()
-                r2.recType = FCGI_UNKNOWN_TYPE
-                r2.unknownType = r.recType
-                r2.writeRecord(self.conn)
-                continue                # Charge onwards
-
-            # Ignore requests that aren't active
-            elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
-                continue
-
-            # If we're already doing a request, ignore further BEGIN_REQUESTs
-            elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
-                continue
-
-            # Begin a new request
-            if r.recType == FCGI_BEGIN_REQUEST:
-                self.requestId = r.reqId
-                if r.role == FCGI_AUTHORIZER:   remaining = 1
-                elif r.role == FCGI_RESPONDER:  remaining = 2
-                elif r.role == FCGI_FILTER:     remaining = 3
-
-            elif r.recType == FCGI_PARAMS:
-                if r.content == "":
-                    remaining = remaining-1
-                else:
-                    for i in r.values.keys():
-                        self.env[i] = r.values[i]
-
-            elif r.recType == FCGI_STDIN:
-                if r.content == "":
-                    remaining = remaining-1
-                else:
-                    stdin = stdin+r.content
-
-            elif r.recType == FCGI_DATA:
-                if r.content == "":
-                    remaining = remaining-1
-                else:
-                    data = data+r.content
-        # end of while remaining:
-
-        self.inp = StringIO(stdin)
-        self.err = StringIO()
-        self.out = StringIO()
-        self.data = StringIO(data)
-
-    def __del__(self):
-        self.Finish()
-
-    def Finish(self, status=0):
-        if not self.haveFinished:
-            self.haveFinished = 1
-
-            self.err.seek(0,0)
-            self.out.seek(0,0)
-
-            ##global debug
-            ##debug = open("/tmp/quixote-debug.log", "a+")
-            ##debug.write("fcgi.FCGI.Finish():\n")
-
-            r = record()
-            r.recType = FCGI_STDERR
-            r.reqId = self.requestId
-            data = self.err.read()
-            ##debug.write("  sending stderr (%s)\n" % `self.err`)
-            ##debug.write("  data = %s\n" % `data`)
-            while data:
-                chunk, data = self.getNextChunk(data)
-                ##debug.write("  chunk, data = %s, %s\n" % (`chunk`, `data`))
-                r.content = chunk
-                r.writeRecord(self.conn)
-            r.content = ""
-            r.writeRecord(self.conn)      # Terminate stream
-
-            r.recType = FCGI_STDOUT
-            data = self.out.read()
-            ##debug.write("  sending stdout (%s)\n" % `self.out`)
-            ##debug.write("  data = %s\n" % `data`)
-            while data:
-                chunk, data = self.getNextChunk(data)
-                r.content = chunk
-                r.writeRecord(self.conn)
-            r.content = ""
-            r.writeRecord(self.conn)      # Terminate stream
-
-            r = record()
-            r.recType = FCGI_END_REQUEST
-            r.reqId = self.requestId
-            r.appStatus = status
-            r.protocolStatus = FCGI_REQUEST_COMPLETE
-            r.writeRecord(self.conn)
-            self.conn.close()
-
-            #debug.close()
-
-
-    def getFieldStorage(self):
-        method = 'GET'
-        if self.env.has_key('REQUEST_METHOD'):
-            method = string.upper(self.env['REQUEST_METHOD'])
-        if method == 'GET':
-            return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
-        else:
-            return cgi.FieldStorage(fp=self.inp,
-                                    environ=self.env,
-                                    keep_blank_values=1)
-
-    def getNextChunk(self, data):
-        chunk = data[:8192]
-        data = data[8192:]
-        return chunk, data
-
-
-Accept = FCGI       # alias for backwards compatibility
-#---------------------------------------------------------------------------
-
-def _startup():
-    global _isFCGI, _init, _sock
-    # This function won't work on Windows at all.
-    if sys.platform[:3] == 'win':
-        _isFCGI = 0
-        return
-
-    _init = 1
-    try:
-        s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
-                          socket.SOCK_STREAM)
-        s.getpeername()
-    except socket.error, (err, errmsg):
-        if err != errno.ENOTCONN:       # must be a non-fastCGI environment
-            _isFCGI = 0
-            return
-
-    _sock = s
-
-
-#---------------------------------------------------------------------------
-
-def _test():
-    counter = 0
-    try:
-        while isFCGI():
-            req = Accept()
-            counter = counter+1
-
-            try:
-                fs = req.getFieldStorage()
-                size = string.atoi(fs['size'].value)
-                doc = ['*' * size]
-            except:
-                doc = ['<HTML><HEAD>'
-                       '<TITLE>FCGI TestApp</TITLE>'
-                       '</HEAD>\n<BODY>\n']
-                doc.append('<H2>FCGI TestApp</H2><P>')
-                doc.append('<b>request count</b> = %d<br>' % counter)
-                doc.append('<b>pid</b> = %s<br>' % os.getpid())
-                if req.env.has_key('CONTENT_LENGTH'):
-                    cl = string.atoi(req.env['CONTENT_LENGTH'])
-                    doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
-                    keys = fs.keys()
-                    keys.sort()
-                    for k in keys:
-                        val = fs[k]
-                        if type(val) == type([]):
-                            doc.append('    <b>%-15s :</b>  %s\n'
-                                       % (k, val))
-                        else:
-                            doc.append('    <b>%-15s :</b>  %s\n'
-                                       % (k, val.value))
-                    doc.append('</pre>')
-
-
-                doc.append('<P><HR><P><pre>')
-                keys = req.env.keys()
-                keys.sort()
-                for k in keys:
-                    doc.append('<b>%-20s :</b>  %s\n' % (k, req.env[k]))
-                doc.append('\n</pre><P><HR>\n')
-                doc.append('</BODY></HTML>\n')
-
-
-            doc = string.join(doc, '')
-            req.out.write('Content-length: %s\r\n'
-                        'Content-type: text/html\r\n'
-                        'Cache-Control: no-cache\r\n'
-                        '\r\n'
-                            % len(doc))
-            req.out.write(doc)
-
-            req.Finish()
-    except:
-        import traceback
-        f = open('traceback', 'w')
-        traceback.print_exc( file = f )
-#        f.write('%s' % doc)
-
-if __name__ == '__main__':
-    #import pdb
-    #pdb.run('_test()')
-    _test()

Modified: trunk/quixote/http_response.py
===================================================================
--- trunk/quixote/http_response.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/http_response.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -7,6 +7,7 @@
 
 import time
 from rfc822 import formatdate
+from quixote.html import stringify
 
 status_reasons = {
     100: 'Continue',
@@ -68,8 +69,11 @@
     after all).
 
     Instance attributes:
+      content_type : string
+        the MIME content type of the response (does not include extra params
+        like charset)
       charset : string
-        the default character encoding of the the response
+        the character encoding of the the response
       status_code : int
         HTTP response status code (integer between 100 and 599)
       reason_phrase : string
@@ -80,12 +84,9 @@
         by 'set_header()' goes here.  Does not include "Status" or
         "Set-Cookie" headers (unless someone uses set_header() to set
         them, but that would be foolish).
-      body : string
-        the response body, None by default.  If the body is never
-        set (ie. left as None), the response will not include
-        "Content-type" or "Content-length" headers.  These headers
-        are set as soon as the body is set (with set_body()), even
-        if the body is an empty string.
+      body : str | Stream
+        the response body, None by default.  Note that if the body is not a
+        stream then it is already encoded using 'charset'.
       buffered : bool
         if false, response data will be flushed as soon as it is
         written (the default is true).  This is most useful for
@@ -113,6 +114,7 @@
         """
         Creates a new HTTP response.
         """
+        self.content_type = 'text/html'
         self.charset = charset
         self.set_status(status)
         self.headers = {}
@@ -127,6 +129,15 @@
         self.buffered = True
         self.javascript_code = None
 
+    def set_content_type(self, content_type, charset='iso-8859-1'):
+        """(content_type : string, charset : string = 'iso-8859-1')
+
+        Set the content type of the response to the MIME type specified by
+        'content_type'.  Also sets the charset, defaulting to 'iso-8859-1'.
+        """
+        self.charset = charset
+        self.content_type = content_type
+
     def set_charset(self, charset):
         self.charset = charset.lower()
 
@@ -180,35 +191,23 @@
         else:
             self.cache = seconds + 60*(minutes + 60*(hours + 24*days))
 
-    def set_content_type(self, ctype):
-        """set_content_type(ctype : string)
-
-        Set the "Content-type" header to the MIME type specified in ctype.
-        Shortcut for set_header("Content-type", ctype).
+    def _encode_chunk(self, chunk):
+        """(chunk : str | unicode) -> str
         """
-        self.headers["content-type"] = ctype
-
+        if self.charset == 'iso-8859-1' and isinstance(chunk, str):
+            return chunk # non-ASCII chars are okay
+        else:
+            return chunk.encode(self.charset)
+            
     def set_body(self, body):
-        """set_body(body : any)
+        """(body : any)
 
-        Sets the return body equal to the argument "body". Also updates the
-        "Content-length" header if the length is of the body is known.  If
-        the "Content-type" header has not yet been set, it is set to
-        "text/html".
+        Sets the response body equal to the argument "body".
         """
         if isinstance(body, Stream):
             self.body = body
-            if body.length is not None:
-                self.set_header('content-length', body.length)
         else:
-            if self.charset == 'iso-8859-1':
-                self.body = str(body)
-            else:
-                self.body = unicode(body).encode(self.charset)
-            self.set_header('content-length', len(self.body))
-        if not self.headers.has_key('content-type'):
-            self.set_header('content-type',
-                            'text/html; charset=%s' % self.charset)
+            self.body = self._encode_chunk(stringify(body))
 
     def expire_cookie(self, name, **attrs):
         """
@@ -282,6 +281,14 @@
         self.set_content_type('text/plain')
         return "Your browser should have redirected you to %s" % location
 
+    def get_content_length(self):
+        if self.body is None:
+            return None
+        elif isinstance(self.body, Stream):
+            return self.body.length
+        else:
+            return len(self.body)
+
     def _gen_cookie_headers(self):
         """_gen_cookie_headers() -> [string]
 
@@ -326,13 +333,13 @@
 
         # Date header
         now = time.time()
-        if not self.headers.has_key("date"):
+        if "date" not in self.headers:
             headers.append(("Date", formatdate(now)))
 
         # Cache directives
         if self.cache is None:
             pass # don't mess with the expires header
-        elif not self.headers.has_key("expires"):
+        elif "expires" not in self.headers:
             if self.cache > 0:
                 expire_date = formatdate(now + self.cache)
             else:
@@ -340,13 +347,35 @@
                                    # with some clients
             headers.append(("Expires", expire_date))
 
+        # Content-type
+        if "content-type" not in self.headers:
+            headers.append(('Content-Type',
+                            '%s; charset=%s' % (self.content_type,
+                                                self.charset)))
+
+        # Content-Length
+        if "content-length" not in self.headers:
+            length = self.get_content_length()
+            if length is not None:
+                headers.append(('Content-Length', length))
+
         return headers
 
+    def generate_body_chunks(self):
+        """Return a sequence of body chunks, encoded using 'charset'.
+        """
+        if self.body is None:
+            pass
+        elif isinstance(self.body, Stream):
+            for chunk in self.body:
+                yield self._encode_chunk(chunk)
+        else:
+            yield self.body # already encoded
 
-    def write(self, file):
-        """write(file : file)
+    def write(self, output):
+        """write(output : file)
 
-        Write the HTTP response headers and body to 'file'.  This is not
+        Write the HTTP response headers and body to 'output'.  This is not
         a complete HTTP response, as it doesn't start with a response
         status line as specified by RFC 2616.  It does, however, start
         with a "Status" header as described by the CGI spec.  It
@@ -370,22 +399,18 @@
         # "non-parsed header" mode, where the CGI script is responsible
         # for generating a complete HTTP response with no help from the
         # server.
-        flush_output = not self.buffered and hasattr(file, 'flush')
+        flush_output = not self.buffered and hasattr(output, 'flush')
         for name, value in self.generate_headers():
-            file.write("%s: %s\r\n" % (name, value))
-        file.write("\r\n")
-        if self.body is not None:
-            if isinstance(self.body, Stream):
-                for chunk in self.body:
-                    if isinstance(chunk, unicode):
-                        chunk = chunk.encode(self.charset)
-                    file.write(chunk)
-                    if flush_output:
-                        file.flush()
-            else:
-                file.write(self.body)
+            output.write("%s: %s\r\n" % (name, value))
+        output.write("\r\n")
         if flush_output:
-            file.flush()
+            output.flush()
+        for chunk in self.generate_body_chunks():
+            output.write(chunk)
+            if flush_output:
+                output.flush()
+        if flush_output:
+            output.flush()
 
 
 class Stream:

Modified: trunk/quixote/publish.py
===================================================================
--- trunk/quixote/publish.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/publish.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -7,8 +7,8 @@
 
 __revision__ = "$Id$"
 
-import sys, os, traceback, cStringIO
-import time, types, re, warnings
+import sys, traceback, cStringIO
+import time, re
 import struct
 import urlparse
 import cgitb
@@ -20,7 +20,6 @@
 from quixote.directory import Directory
 from quixote.errors import PublishError, TrailingSlashError, \
      format_publish_error
-from quixote.html import htmltext
 from quixote import util
 from quixote.config import Config
 from quixote.http_request import HTTPRequest
@@ -248,14 +247,14 @@
 
     _SLASH_PAT = re.compile("//*")
 
-    def try_publish(self, request, path):
-        """try_publish(request : HTTPRequest, path : string) -> string
+    def try_publish(self, request):
+        """(request : HTTPRequest) -> object
 
-        The master method that does all the work for a single request.  Uses
-        traverse_url() to get a callable object.  The object is called and
-        the output is returned.  Exceptions are handled by the caller.
+        The master method that does all the work for a single request.
+        Exceptions are handled by the caller.
         """
         self.start_request()
+        path = request.get_environ('PATH_INFO', '/')
         # split path into components
         if '//' in path:
             path = self._SLASH_PAT.sub("/", path)
@@ -304,8 +303,8 @@
                 output = self.compress_output(request, str(output))
         return output
 
-    def process_request(self, request, env):
-        """process_request(request : HTTPRequest, env : dict) : string
+    def process_request(self, request):
+        """(request : HTTPRequest) -> HTTPResponse
 
         Process a single request, given an HTTPRequest object.  The
         try_publish() method will be called to do the work and
@@ -315,7 +314,7 @@
         start_time = time.time()
         try:
             self.parse_request(request)
-            output = self.try_publish(request, env.get('PATH_INFO', ''))
+            output = self.try_publish(request)
         except PublishError, exc:
             if (self.config.fix_trailing_slash and
                 isinstance(exc, TrailingSlashError) and
@@ -333,60 +332,12 @@
             output = self.finish_failed_request()
         output = self.filter_output(request, output)
         self.logger.log_request(request, start_time)
-        return output
-
-    def publish(self, stdin, stdout, stderr, env):
-        """publish(stdin : file, stdout : file, stderr : file, env : dict)
-
-        Create an HTTPRequest object from the environment and from
-        standard input, process it, and write the response to standard
-        output.
-        """
-        request = HTTPRequest(stdin, env)
-        output = self.process_request(request, env)
-
-        # Output results from Response object
         if output:
             request.response.set_body(output)
-        try:
-            request.response.write(stdout)
-        except IOError, exc:
-            self.log('IOError caught while writing request (%s)' % exc)
         self._clear_request()
+        return request.response
 
 
-    def publish_cgi(self):
-        """publish_cgi()
-
-        Entry point from CGI scripts; it will execute the publish function
-        once and return.
-        """
-        if sys.platform == "win32":
-            # on Windows, stdin and stdout are in text mode by default
-            import msvcrt
-            msvcrt.setmode(sys.__stdin__.fileno(), os.O_BINARY)
-            msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)
-        self.publish(sys.__stdin__, sys.__stdout__, sys.__stderr__, os.environ)
-
-    def publish_fcgi(self):
-        """publish_fcgi()
-
-        Entry point from FCGI scripts; it will repeatedly do the publish()
-        function until there are no more requests.  This should also work
-        for CGI scripts but it is not as portable as publish_cgi().
-        """
-        from quixote import fcgi
-        while fcgi.isFCGI() and not self.exit_now:
-            f = fcgi.FCGI()
-            self.publish(f.inp, f.out, f.err, f.env)
-            f.Finish()
-            if self.config.run_once:
-                break
-
-
-# class Publisher
-
-
 class SessionPublisher(Publisher):
 
     def __init__(self, root_directory, session_mgr=None, **kwargs):

Modified: trunk/quixote/server/__init__.py
===================================================================
--- trunk/quixote/server/__init__.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/__init__.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -1,10 +1,5 @@
-"""quixote.server
+"""$URL$
+$Id$
 
-This package is for HTTP servers, built using one or another
-framework, that publish a Quixote application.  These servers can make
-it easy to run a small application without having to install and
-configure a full-blown Web server such as Apache.
-
+This package is for Quixote/HTTP server glue.
 """
-
-__revision__ = "$Id$"

Copied: trunk/quixote/server/_fcgi.py (from rev 25457, trunk/quixote/fcgi.py)
===================================================================
--- trunk/quixote/fcgi.py	2004-10-26 17:43:03 UTC (rev 25457)
+++ trunk/quixote/server/_fcgi.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,461 @@
+#------------------------------------------------------------------------
+#               Copyright (c) 1998 by Total Control Software
+#                         All Rights Reserved
+#------------------------------------------------------------------------
+#
+# Module Name:  fcgi.py
+#
+# Description:  Handles communication with the FastCGI module of the
+#               web server without using the FastCGI developers kit, but
+#               will also work in a non-FastCGI environment, (straight CGI.)
+#               This module was originally fetched from someplace on the
+#               Net (I don't remember where and I can't find it now...) and
+#               has been significantly modified to fix several bugs, be more
+#               readable, more robust at handling large CGI data and return
+#               document sizes, and also to fit the model that we had previously
+#               used for FastCGI.
+#
+#     WARNING:  If you don't know what you are doing, don't tinker with this
+#               module!
+#
+# Creation Date:    1/30/98 2:59:04PM
+#
+# License:      This is free software.  You may use this software for any
+#               purpose including modification/redistribution, so long as
+#               this header remains intact and that you do not claim any
+#               rights of ownership or authorship of this software.  This
+#               software has been tested, but no warranty is expressed or
+#               implied.
+#
+#------------------------------------------------------------------------
+
+__revision__ = "$Id$"
+
+
+import  os, sys, string, socket, errno, struct
+from    cStringIO   import StringIO
+import  cgi
+
+#---------------------------------------------------------------------------
+
+# Set various FastCGI constants
+# Maximum number of requests that can be handled
+FCGI_MAX_REQS=1
+FCGI_MAX_CONNS = 1
+
+# Supported version of the FastCGI protocol
+FCGI_VERSION_1 = 1
+
+# Boolean: can this application multiplex connections?
+FCGI_MPXS_CONNS=0
+
+# Record types
+FCGI_BEGIN_REQUEST = 1 ; FCGI_ABORT_REQUEST = 2 ; FCGI_END_REQUEST   = 3
+FCGI_PARAMS        = 4 ; FCGI_STDIN         = 5 ; FCGI_STDOUT        = 6
+FCGI_STDERR        = 7 ; FCGI_DATA          = 8 ; FCGI_GET_VALUES    = 9
+FCGI_GET_VALUES_RESULT = 10
+FCGI_UNKNOWN_TYPE = 11
+FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
+
+# Types of management records
+ManagementTypes = [FCGI_GET_VALUES]
+
+FCGI_NULL_REQUEST_ID = 0
+
+# Masks for flags component of FCGI_BEGIN_REQUEST
+FCGI_KEEP_CONN = 1
+
+# Values for role component of FCGI_BEGIN_REQUEST
+FCGI_RESPONDER = 1 ; FCGI_AUTHORIZER = 2 ; FCGI_FILTER = 3
+
+# Values for protocolStatus component of FCGI_END_REQUEST
+FCGI_REQUEST_COMPLETE = 0               # Request completed nicely
+FCGI_CANT_MPX_CONN    = 1               # This app can't multiplex
+FCGI_OVERLOADED       = 2               # New request rejected; too busy
+FCGI_UNKNOWN_ROLE     = 3               # Role value not known
+
+
+error = 'fcgi.error'
+
+
+#---------------------------------------------------------------------------
+
+# The following function is used during debugging; it isn't called
+# anywhere at the moment
+
+def error(msg):
+    "Append a string to /tmp/err"
+    errf = open('/tmp/err', 'a+')
+    errf.write(msg+'\n')
+    errf.close()
+
+#---------------------------------------------------------------------------
+
+class record:
+    "Class representing FastCGI records"
+    def __init__(self):
+        self.version = FCGI_VERSION_1
+        self.recType = FCGI_UNKNOWN_TYPE
+        self.reqId   = FCGI_NULL_REQUEST_ID
+        self.content = ""
+
+    #----------------------------------------
+    def readRecord(self, sock, unpack=struct.unpack):
+        (self.version, self.recType, self.reqId, contentLength,
+         paddingLength) = unpack(">BBHHBx", sock.recv(8))
+
+        content = ""
+        while len(content) < contentLength:
+            content = content + sock.recv(contentLength - len(content))
+        self.content = content
+
+        if paddingLength != 0:
+            padding = sock.recv(paddingLength)
+
+        # Parse the content information
+        if self.recType == FCGI_BEGIN_REQUEST:
+            (self.role, self.flags) = unpack(">HB", content[:3])
+
+        elif self.recType == FCGI_UNKNOWN_TYPE:
+            self.unknownType = ord(content[0])
+
+        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+            self.values = {}
+            pos = 0
+            while pos < len(content):
+                name, value, pos = readPair(content, pos)
+                self.values[name] = value
+
+        elif self.recType == FCGI_END_REQUEST:
+            (self.appStatus, self.protocolStatus) = unpack(">IB", content[0:5])
+
+    #----------------------------------------
+    def writeRecord(self, sock, pack=struct.pack):
+        content = self.content
+        if self.recType == FCGI_BEGIN_REQUEST:
+            content = pack(">HBxxxxx", self.role, self.flags)
+
+        elif self.recType == FCGI_UNKNOWN_TYPE:
+            content = pack(">Bxxxxxx", self.unknownType)
+
+        elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS:
+            content = ""
+            for i in self.values.keys():
+                content = content + writePair(i, self.values[i])
+
+        elif self.recType == FCGI_END_REQUEST:
+            content = pack(">IBxxx", self.appStatus, self.protocolStatus)
+
+        cLen = len(content)
+        eLen = (cLen + 7) & (0xFFFF - 7)    # align to an 8-byte boundary
+        padLen = eLen - cLen
+
+        hdr = pack(">BBHHBx", self.version, self.recType, self.reqId, cLen,
+                   padLen)
+
+        ##debug.write('Sending fcgi record: %s\n' % repr(content[:50]) )
+        sock.send(hdr + content + padLen*'\000')
+
+#---------------------------------------------------------------------------
+
+_lowbits = ~(1L << 31) # everything but the 31st bit
+
+def readPair(s, pos):
+    nameLen = ord(s[pos]) ; pos = pos+1
+    if nameLen & 128:
+        pos = pos + 3
+        nameLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+    valueLen = ord(s[pos]) ; pos = pos+1
+    if valueLen & 128:
+        pos = pos + 3
+        valueLen = int(struct.unpack(">I", s[pos-4:pos])[0] & _lowbits)
+    return ( s[pos:pos+nameLen], s[pos+nameLen:pos+nameLen+valueLen],
+             pos+nameLen+valueLen )
+
+#---------------------------------------------------------------------------
+
+_highbit = (1L << 31)
+
+def writePair(name, value):
+    l = len(name)
+    if l < 128:
+        s = chr(l)
+    else:
+        s = struct.pack(">I", l | _highbit)
+    l = len(value)
+    if l < 128:
+        s = s + chr(l)
+    else:
+        s = s + struct.pack(">I", l | _highbit)
+    return s + name + value
+
+#---------------------------------------------------------------------------
+
+def HandleManTypes(r, conn):
+    if r.recType == FCGI_GET_VALUES:
+        r.recType = FCGI_GET_VALUES_RESULT
+        v = {}
+        vars = {'FCGI_MAX_CONNS' : FCGI_MAX_CONNS,
+                'FCGI_MAX_REQS'  : FCGI_MAX_REQS,
+                'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS}
+        for i in r.values.keys():
+            if vars.has_key(i): v[i] = vars[i]
+        r.values = vars
+        r.writeRecord(conn)
+
+#---------------------------------------------------------------------------
+#---------------------------------------------------------------------------
+
+
+_isFCGI = 1         # assume it is until we find out for sure
+
+def isFCGI():
+    return _isFCGI
+
+
+
+#---------------------------------------------------------------------------
+
+
+_init = None
+_sock = None
+
+class FCGI:
+    def __init__(self):
+        self.haveFinished = 0
+        if _init == None:
+            _startup()
+        if not _isFCGI:
+            self.haveFinished = 1
+            self.inp = sys.__stdin__
+            self.out = sys.__stdout__
+            self.err = sys.__stderr__
+            self.env = os.environ
+            return
+
+        if os.environ.has_key('FCGI_WEB_SERVER_ADDRS'):
+            good_addrs = string.split(os.environ['FCGI_WEB_SERVER_ADDRS'], ',')
+            good_addrs = map(string.strip, good_addrs)        # Remove whitespace
+        else:
+            good_addrs = None
+
+        self.conn, addr = _sock.accept()
+        stdin, data = "", ""
+        self.env = {}
+        self.requestId = 0
+        remaining = 1
+
+        # Check if the connection is from a legal address
+        if good_addrs != None and addr not in good_addrs:
+            raise error, 'Connection from invalid server!'
+
+        while remaining:
+            r = record()
+            r.readRecord(self.conn)
+
+            if r.recType in ManagementTypes:
+                HandleManTypes(r, self.conn)
+
+            elif r.reqId == 0:
+                # Oh, poopy.  It's a management record of an unknown
+                # type.  Signal the error.
+                r2 = record()
+                r2.recType = FCGI_UNKNOWN_TYPE
+                r2.unknownType = r.recType
+                r2.writeRecord(self.conn)
+                continue                # Charge onwards
+
+            # Ignore requests that aren't active
+            elif r.reqId != self.requestId and r.recType != FCGI_BEGIN_REQUEST:
+                continue
+
+            # If we're already doing a request, ignore further BEGIN_REQUESTs
+            elif r.recType == FCGI_BEGIN_REQUEST and self.requestId != 0:
+                continue
+
+            # Begin a new request
+            if r.recType == FCGI_BEGIN_REQUEST:
+                self.requestId = r.reqId
+                if r.role == FCGI_AUTHORIZER:   remaining = 1
+                elif r.role == FCGI_RESPONDER:  remaining = 2
+                elif r.role == FCGI_FILTER:     remaining = 3
+
+            elif r.recType == FCGI_PARAMS:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    for i in r.values.keys():
+                        self.env[i] = r.values[i]
+
+            elif r.recType == FCGI_STDIN:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    stdin = stdin+r.content
+
+            elif r.recType == FCGI_DATA:
+                if r.content == "":
+                    remaining = remaining-1
+                else:
+                    data = data+r.content
+        # end of while remaining:
+
+        self.inp = StringIO(stdin)
+        self.err = StringIO()
+        self.out = StringIO()
+        self.data = StringIO(data)
+
+    def __del__(self):
+        self.Finish()
+
+    def Finish(self, status=0):
+        if not self.haveFinished:
+            self.haveFinished = 1
+
+            self.err.seek(0,0)
+            self.out.seek(0,0)
+
+            ##global debug
+            ##debug = open("/tmp/quixote-debug.log", "a+")
+            ##debug.write("fcgi.FCGI.Finish():\n")
+
+            r = record()
+            r.recType = FCGI_STDERR
+            r.reqId = self.requestId
+            data = self.err.read()
+            ##debug.write("  sending stderr (%s)\n" % `self.err`)
+            ##debug.write("  data = %s\n" % `data`)
+            while data:
+                chunk, data = self.getNextChunk(data)
+                ##debug.write("  chunk, data = %s, %s\n" % (`chunk`, `data`))
+                r.content = chunk
+                r.writeRecord(self.conn)
+            r.content = ""
+            r.writeRecord(self.conn)      # Terminate stream
+
+            r.recType = FCGI_STDOUT
+            data = self.out.read()
+            ##debug.write("  sending stdout (%s)\n" % `self.out`)
+            ##debug.write("  data = %s\n" % `data`)
+            while data:
+                chunk, data = self.getNextChunk(data)
+                r.content = chunk
+                r.writeRecord(self.conn)
+            r.content = ""
+            r.writeRecord(self.conn)      # Terminate stream
+
+            r = record()
+            r.recType = FCGI_END_REQUEST
+            r.reqId = self.requestId
+            r.appStatus = status
+            r.protocolStatus = FCGI_REQUEST_COMPLETE
+            r.writeRecord(self.conn)
+            self.conn.close()
+
+            #debug.close()
+
+
+    def getFieldStorage(self):
+        method = 'GET'
+        if self.env.has_key('REQUEST_METHOD'):
+            method = string.upper(self.env['REQUEST_METHOD'])
+        if method == 'GET':
+            return cgi.FieldStorage(environ=self.env, keep_blank_values=1)
+        else:
+            return cgi.FieldStorage(fp=self.inp,
+                                    environ=self.env,
+                                    keep_blank_values=1)
+
+    def getNextChunk(self, data):
+        chunk = data[:8192]
+        data = data[8192:]
+        return chunk, data
+
+
+Accept = FCGI       # alias for backwards compatibility
+#---------------------------------------------------------------------------
+
+def _startup():
+    global _isFCGI, _init, _sock
+    # This function won't work on Windows at all.
+    if sys.platform[:3] == 'win':
+        _isFCGI = 0
+        return
+
+    _init = 1
+    try:
+        s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET,
+                          socket.SOCK_STREAM)
+        s.getpeername()
+    except socket.error, (err, errmsg):
+        if err != errno.ENOTCONN:       # must be a non-fastCGI environment
+            _isFCGI = 0
+            return
+
+    _sock = s
+
+
+#---------------------------------------------------------------------------
+
+def _test():
+    counter = 0
+    try:
+        while isFCGI():
+            req = Accept()
+            counter = counter+1
+
+            try:
+                fs = req.getFieldStorage()
+                size = string.atoi(fs['size'].value)
+                doc = ['*' * size]
+            except:
+                doc = ['<HTML><HEAD>'
+                       '<TITLE>FCGI TestApp</TITLE>'
+                       '</HEAD>\n<BODY>\n']
+                doc.append('<H2>FCGI TestApp</H2><P>')
+                doc.append('<b>request count</b> = %d<br>' % counter)
+                doc.append('<b>pid</b> = %s<br>' % os.getpid())
+                if req.env.has_key('CONTENT_LENGTH'):
+                    cl = string.atoi(req.env['CONTENT_LENGTH'])
+                    doc.append('<br><b>POST data (%s):</b><br><pre>' % cl)
+                    keys = fs.keys()
+                    keys.sort()
+                    for k in keys:
+                        val = fs[k]
+                        if type(val) == type([]):
+                            doc.append('    <b>%-15s :</b>  %s\n'
+                                       % (k, val))
+                        else:
+                            doc.append('    <b>%-15s :</b>  %s\n'
+                                       % (k, val.value))
+                    doc.append('</pre>')
+
+
+                doc.append('<P><HR><P><pre>')
+                keys = req.env.keys()
+                keys.sort()
+                for k in keys:
+                    doc.append('<b>%-20s :</b>  %s\n' % (k, req.env[k]))
+                doc.append('\n</pre><P><HR>\n')
+                doc.append('</BODY></HTML>\n')
+
+
+            doc = string.join(doc, '')
+            req.out.write('Content-length: %s\r\n'
+                        'Content-type: text/html\r\n'
+                        'Cache-Control: no-cache\r\n'
+                        '\r\n'
+                            % len(doc))
+            req.out.write(doc)
+
+            req.Finish()
+    except:
+        import traceback
+        f = open('traceback', 'w')
+        traceback.print_exc( file = f )
+#        f.write('%s' % doc)
+
+if __name__ == '__main__':
+    #import pdb
+    #pdb.run('_test()')
+    _test()

Added: trunk/quixote/server/cgi_server.py
===================================================================
--- trunk/quixote/server/cgi_server.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/cgi_server.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+"""$URL$
+$Id$
+"""
+
+import sys
+import os
+from quixote.http_request import HTTPRequest
+
+def run(create_publisher):
+    if sys.platform == "win32":
+        # on Windows, stdin and stdout are in text mode by default
+        import msvcrt
+        msvcrt.setmode(sys.__stdin__.fileno(), os.O_BINARY)
+        msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)
+    publisher = create_publisher()
+    request = HTTPRequest(sys.__stdin__, os.environ)
+    response = publisher.process_request(request)
+    try:
+        response.write(sys.__stdout__)
+    except IOError, err:
+        publisher.log("IOError while sending response ignored: %s" % err)
+
+
+if __name__ == '__main__':
+    from quixote.demo import create_publisher
+    run(create_publisher)


Property changes on: trunk/quixote/server/cgi_server.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + HeadURL Id

Added: trunk/quixote/server/fastcgi_server.py
===================================================================
--- trunk/quixote/server/fastcgi_server.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/fastcgi_server.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+"""$URL$
+$Id$
+
+Server for Quixote applications that use FastCGI.  It should work
+for CGI too but the cgi_server module is preferred as it is more
+portable.
+"""
+
+from quixote.server import _fcgi
+from quixote.http_request import HTTPRequest
+
+def run(create_publisher):
+    publisher = create_publisher()
+    while _fcgi.isFCGI():
+        f = _fcgi.FCGI()
+        request = HTTPRequest(f.inp, f.env)
+        response = publisher.process_request(request)
+        try:
+            response.write(f.out)
+        except IOError, err:
+            publisher.log("IOError while sending response ignored: %s" % err)
+        f.Finish()
+
+
+if __name__ == '__main__':
+    from quixote.demo import create_publisher
+    run(create_publisher)


Property changes on: trunk/quixote/server/fastcgi_server.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + HeadURL Id

Deleted: trunk/quixote/server/medusa_http.py
===================================================================
--- trunk/quixote/server/medusa_http.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/medusa_http.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -1,143 +0,0 @@
-#!/usr/bin/env python
-
-"""quixote.server.medusa_http
-
-An HTTP handler for Medusa that publishes a Quixote application.
-"""
-
-__revision__ = "$Id$"
-
-# A simple HTTP server, using Medusa, that publishes a Quixote application.
-
-import sys
-import asyncore, rfc822, socket, urllib
-from StringIO import StringIO
-from medusa import http_server, xmlrpc_handler
-from quixote.http_request import HTTPRequest
-from quixote.http_response import Stream
-from quixote.publish import Publisher
-
-
-class StreamProducer:
-    def __init__(self, stream):
-        self.iterator = iter(stream)
-
-    def more(self):
-        try:
-            return self.iterator.next()
-        except StopIteration:
-            return ''
-
-
-class QuixoteHandler:
-    def __init__(self, publisher, server_name, server):
-        """QuixoteHandler(publisher:Publisher, server_name:string,
-                        server:medusa.http_server.http_server)
-
-        Publish the specified Quixote publisher.  'server_name' will
-        be passed as the SERVER_NAME environment variable.
-        """
-        self.publisher = publisher
-        self.server_name = server_name
-        self.server = server
-
-    def match(self, request):
-        # Always match, since this is the only handler there is.
-        return 1
-
-    def handle_request(self, request):
-        msg = rfc822.Message(StringIO('\n'.join(request.header)))
-        length = int(msg.get('Content-Length', '0'))
-        if length:
-            request.collector = xmlrpc_handler.collector(self, request)
-        else:
-            self.continue_request('', request)
-
-    def continue_request(self, data, request):
-        msg = rfc822.Message(StringIO('\n'.join(request.header)))
-        remote_addr, remote_port = request.channel.addr
-        if '#' in request.uri:
-            # MSIE is buggy and sometimes includes fragments in URLs
-            [request.uri, fragment] = request.uri.split('#', 1)
-        if '?' in request.uri:
-            [path, query_string] = request.uri.split('?', 1)
-        else:
-            path = request.uri
-            query_string = ''
-
-        path = urllib.unquote(path)
-        server_port = str(self.server.port)
-        http_host = msg.get("Host")
-        if http_host:
-            if ":" in http_host:
-                server_name, server_port = http_host.split(":", 1)
-            else:
-                server_name = http_host
-        else:
-            server_name = (self.server.ip or
-                           socket.gethostbyaddr(socket.gethostname())[0])
-
-        environ = {'REQUEST_METHOD': request.command,
-                   'ACCEPT_ENCODING': msg.get('Accept-encoding', ''),
-                   'CONTENT_TYPE': msg.get('Content-type', ''),
-                   'CONTENT_LENGTH': len(data),
-                   "GATEWAY_INTERFACE": "CGI/1.1",
-                   'PATH_INFO': path,
-                   'QUERY_STRING': query_string,
-                   'REMOTE_ADDR': remote_addr,
-                   'REMOTE_PORT': str(remote_port),
-                   'REQUEST_URI': request.uri,
-                   'SCRIPT_NAME': '',
-                   "SCRIPT_FILENAME": '',
-                   'SERVER_NAME': server_name,
-                   'SERVER_PORT': server_port,
-                   'SERVER_PROTOCOL': 'HTTP/1.1',
-                   'SERVER_SOFTWARE': self.server_name,
-                   }
-        for title, header in msg.items():
-            envname = 'HTTP_' + title.replace('-', '_').upper()
-            environ[envname] = header
-
-        stdin = StringIO(data)
-        qreq = HTTPRequest(stdin, environ)
-        output = self.publisher.process_request(qreq, environ)
-
-        qresponse = qreq.response
-        if output:
-            qresponse.set_body(output)
-
-        # Copy headers from Quixote's HTTP response
-        for name, value in qresponse.generate_headers():
-            # XXX Medusa's HTTP request is buggy, and only allows unique
-            # headers.
-            request[name] = value
-
-        request.response(qresponse.status_code)
-
-        # XXX should we set a default Last-Modified time?
-        if qresponse.body is not None:
-            if isinstance(qresponse.body, Stream):
-                request.push(StreamProducer(qresponse.body))
-            else:
-                request.push(qresponse.body)
-
-        request.done()
-
-def main():
-    from quixote import enable_ptl
-    enable_ptl()
-    from quixote.demo import DemoUI
-
-    if len(sys.argv) == 2:
-        port = int(sys.argv[1])
-    else:
-        port = 8080
-    print 'Now serving the Quixote demo on port %d' % port
-    server = http_server.http_server('', port)
-    publisher = Publisher(DemoUI(), display_exceptions='plain')
-    dh = QuixoteHandler(publisher, 'Quixote/demo', server)
-    server.install_handler(dh)
-    asyncore.loop()
-
-if __name__ == '__main__':
-    main()

Copied: trunk/quixote/server/medusa_server.py (from rev 25457, trunk/quixote/server/medusa_http.py)
===================================================================
--- trunk/quixote/server/medusa_http.py	2004-10-26 17:43:03 UTC (rev 25457)
+++ trunk/quixote/server/medusa_server.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+"""$URL$
+$Id$
+
+An HTTP handler for Medusa that publishes a Quixote application.
+"""
+
+import asyncore, rfc822, socket, urllib
+from StringIO import StringIO
+from medusa import http_server, xmlrpc_handler
+from quixote.http_request import HTTPRequest
+
+
+class StreamProducer:
+    def __init__(self, chunks):
+        self.chunks = chunks # a generator
+
+    def more(self):
+        try:
+            return self.chunks.next()
+        except StopIteration:
+            return ''
+
+
+class QuixoteHandler:
+    def __init__(self, publisher, server):
+        self.publisher = publisher
+        self.server = server
+
+    def match(self, request):
+        # Always match, since this is the only handler there is.
+        return True
+
+    def handle_request(self, request):
+        msg = rfc822.Message(StringIO('\n'.join(request.header)))
+        length = int(msg.get('Content-Length', '0'))
+        if length:
+            request.collector = xmlrpc_handler.collector(self, request)
+        else:
+            self.continue_request('', request)
+
+    def continue_request(self, data, request):
+        msg = rfc822.Message(StringIO('\n'.join(request.header)))
+        remote_addr, remote_port = request.channel.addr
+        if '#' in request.uri:
+            # MSIE is buggy and sometimes includes fragments in URLs
+            [request.uri, fragment] = request.uri.split('#', 1)
+        if '?' in request.uri:
+            [path, query_string] = request.uri.split('?', 1)
+        else:
+            path = request.uri
+            query_string = ''
+
+        path = urllib.unquote(path)
+        server_port = str(self.server.port)
+        http_host = msg.get("Host")
+        if http_host:
+            if ":" in http_host:
+                server_name, server_port = http_host.split(":", 1)
+            else:
+                server_name = http_host
+        else:
+            server_name = (self.server.ip or
+                           socket.gethostbyaddr(socket.gethostname())[0])
+
+        environ = {'REQUEST_METHOD': request.command,
+                   'ACCEPT_ENCODING': msg.get('Accept-encoding', ''),
+                   'CONTENT_TYPE': msg.get('Content-type', ''),
+                   'CONTENT_LENGTH': len(data),
+                   "GATEWAY_INTERFACE": "CGI/1.1",
+                   'PATH_INFO': path,
+                   'QUERY_STRING': query_string,
+                   'REMOTE_ADDR': remote_addr,
+                   'REMOTE_PORT': str(remote_port),
+                   'REQUEST_URI': request.uri,
+                   'SCRIPT_NAME': '',
+                   "SCRIPT_FILENAME": '',
+                   'SERVER_NAME': server_name,
+                   'SERVER_PORT': server_port,
+                   'SERVER_PROTOCOL': 'HTTP/1.1',
+                   'SERVER_SOFTWARE': 'Quixote/2',
+                   }
+        for title, header in msg.items():
+            envname = 'HTTP_' + title.replace('-', '_').upper()
+            environ[envname] = header
+
+        stdin = StringIO(data)
+        qrequest = HTTPRequest(stdin, environ)
+        qresponse = self.publisher.process_request(qrequest)
+
+        # Copy headers from Quixote's HTTP response
+        for name, value in qresponse.generate_headers():
+            # XXX Medusa's HTTP request is buggy, and only allows unique
+            # headers.
+            request[name] = value
+
+        request.response(qresponse.status_code)
+        request.push(StreamProducer(qresponse.generate_body_chunks()))
+        request.done()
+
+
+def run(create_publisher, host='', port=80):
+    server = http_server.http_server(host, port)
+    publisher = create_publisher()
+    handler = QuixoteHandler(publisher, server)
+    server.install_handler(handler)
+    asyncore.loop()
+
+
+if __name__ == '__main__':
+    from quixote.demo import create_publisher
+    run(create_publisher, port=8080)


Property changes on: trunk/quixote/server/medusa_server.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + HeadURL Id

Added: trunk/quixote/server/scgi_server.py
===================================================================
--- trunk/quixote/server/scgi_server.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/scgi_server.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+"""$URL$
+$Id$
+
+A SCGI server that uses Quixote to publish dynamic content.
+"""
+
+from scgi import scgi_server
+from quixote.http_request import HTTPRequest
+
+class QuixoteHandler(scgi_server.SCGIHandler):
+    def __init__(self, parent_fd, create_publisher, script_name=None):
+        scgi_server.SCGIHandler.__init__(self, parent_fd)
+        self.publisher = create_publisher()
+        self.script_name = script_name
+
+    def handle_connection(self, conn):
+        input = conn.makefile("r")
+        output = conn.makefile("w")
+        env = self.read_env(input)
+
+        if self.script_name is not None:
+            # mod_scgi doesn't know SCRIPT_NAME :-(
+            prefix = self.script_name
+            path = env['SCRIPT_NAME']
+            assert path[:len(prefix)] == prefix, (
+                "path %r doesn't start with script_name %r" % (path, prefix))
+            env['SCRIPT_NAME'] = prefix
+            env['PATH_INFO'] = path[len(prefix):] + env.get('PATH_INFO', '')
+
+        request = HTTPRequest(input, env)
+        response = self.publisher.process_request(request)
+        try:
+            response.write(output)
+            input.close()
+            output.close()
+            conn.close()
+        except IOError, err:
+            self.publisher.log("IOError while sending response "
+                               "ignored: %s" % err)
+
+
+def run(create_publisher, host='', port=3000, script_name=None, max_children=5):
+    def create_handler(parent_fd):
+        return QuixoteHandler(parent_fd, create_publisher, script_name)
+    s = scgi_server.SCGIServer(create_handler, host=host, port=port,
+                               max_children=max_children)
+    s.serve()
+
+
+if __name__ == '__main__':
+    from quixote.demo import create_publisher
+    run(create_publisher)


Property changes on: trunk/quixote/server/scgi_server.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + HeadURL Id

Deleted: trunk/quixote/server/twisted_http.py
===================================================================
--- trunk/quixote/server/twisted_http.py	2004-10-27 21:43:28 UTC (rev 25475)
+++ trunk/quixote/server/twisted_http.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -1,275 +0,0 @@
-#!/usr/bin/env python
-
-"""
-twist -- Demo of an HTTP server built on top of Twisted Python.
-"""
-
-__revision__ = "$Id: medusa_http.py 21221 2003-03-20 16:02:41Z akuchlin $"
-
-# based on qserv, created 2002/03/19, AMK
-# last mod 2003.03.24, Graham Fawcett
-# tested on Win32 / Twisted 0.18.0 / Quixote 0.6b5
-#
-# version 0.2 -- 2003.03.24 11:07 PM
-#   adds missing support for session management, and for
-#   standard Quixote response headers (expires, date)
-#
-# modified 2004/04/10 jsibre
-#   better support for Streams
-#   wraps output (whether Stream or not) into twisted type producer.
-#   modified to use reactor instead of Application (Appication
-#     has been deprecated)
-
-import urllib
-from twisted.protocols import http
-from twisted.web import server
-
-# imports for the TWProducer object
-from twisted.spread import pb
-from twisted.python import threadable
-from twisted.internet import abstract
-
-from quixote.http_request import HTTPRequest
-from quixote.http_response import Stream
-
-class QuixoteTWRequest(server.Request):
-
-    def process(self):
-        self.publisher = self.channel.factory.publisher
-        environ = self.create_environment()
-        # this seek is important, it doesn't work without it (it doesn't
-        # matter for GETs, but POSTs will not work properly without it.)
-        self.content.seek(0, 0)
-        qxrequest = HTTPRequest(self.content, environ)
-        self.quixote_publish(qxrequest, environ)
-        resp = qxrequest.response
-        self.setResponseCode(resp.status_code)
-        for hdr, value in resp.generate_headers():
-            self.setHeader(hdr, value)
-        if resp.body is not None:
-            TWProducer(resp.body, self)
-        else:
-            self.finish()
-
-
-    def quixote_publish(self, qxrequest, env):
-        """
-        Warning, this sidesteps the Publisher.publish method,
-        Hope you didn't override it...
-        """
-        pub = self.publisher
-        output = pub.process_request(qxrequest, env)
-
-        # don't write out the output, just set the response body
-        # the calling method will do the rest.
-        if output:
-            qxrequest.response.set_body(output)
-
-        pub._clear_request()
-
-
-    def create_environment(self):
-        """
-        Borrowed heavily from twisted.web.twcgi
-        """
-        # Twisted doesn't decode the path for us,
-        # so let's do it here.  This is also
-        # what medusa_http.py does, right or wrong.
-        if '%' in self.path:
-            self.path = urllib.unquote(self.path)
-
-        serverName = self.getRequestHostname().split(':')[0]
-        env = {"SERVER_SOFTWARE":   server.version,
-               "SERVER_NAME":       serverName,
-               "GATEWAY_INTERFACE": "CGI/1.1",
-               "SERVER_PROTOCOL":   self.clientproto,
-               "SERVER_PORT":       str(self.getHost()[2]),
-               "REQUEST_METHOD":    self.method,
-               "SCRIPT_NAME":       '',
-               "SCRIPT_FILENAME":   '',
-               "REQUEST_URI":       self.uri,
-               "HTTPS":             (self.isSecure() and 'on') or 'off',
-               "ACCEPT_ENCODING":   self.getHeader('Accept-encoding'),
-               'CONTENT_TYPE':      self.getHeader('Content-type'),
-               'HTTP_COOKIE':       self.getHeader('Cookie'),
-               'HTTP_REFERER':      self.getHeader('Referer'),
-               'HTTP_USER_AGENT':   self.getHeader('User-agent'),
-               'SERVER_PROTOCOL':   'HTTP/1.1',
-        }
-
-        client = self.getClient()
-        if client is not None:
-            env['REMOTE_HOST'] = client
-        ip = self.getClientIP()
-        if ip is not None:
-            env['REMOTE_ADDR'] = ip
-        xx, xx, remote_port = self.transport.getPeer()
-        env['REMOTE_PORT'] = remote_port
-        env["PATH_INFO"] = self.path
-
-        qindex = self.uri.find('?')
-        if qindex != -1:
-            env['QUERY_STRING'] = self.uri[qindex+1:]
-        else:
-            env['QUERY_STRING'] = ''
-
-        # Propogate HTTP headers
-        for title, header in self.getAllHeaders().items():
-            envname = title.replace('-', '_').upper()
-            if title not in ('content-type', 'content-length'):
-                envname = "HTTP_" + envname
-            env[envname] = header
-
-        return env
-
-
-class TWProducer(pb.Viewable):
-    """
-    A class to represent the transfer of data over the network.
-
-    JES Note: This has more stuff in it than is minimally neccesary.
-    However, since I'm no twisted guru, I built this by modifing
-    twisted.web.static.FileTransfer.  FileTransfer has stuff in it
-    that I don't really understand, but know that I probably don't
-    need. I'm leaving it in under the theory that if anyone ever
-    needs that stuff (e.g. because they're running with multiple
-    threads) it'll be MUCH easier for them if I had just left it in
-    than if they have to figure out what needs to be in there.
-    Furthermore, I notice no performance penalty for leaving it in.
-    """
-    request = None
-    def __init__(self, data, request):
-        self.request = request
-        self.data = ""
-        self.size = 0
-        self.stream = None
-        self.streamIter = None
-
-        self.outputBufferSize = abstract.FileDescriptor.bufferSize
-
-        if isinstance(data, Stream):    # data could be a Stream
-            self.stream = data
-            self.streamIter = iter(data)
-            self.size = data.length
-        elif data:                      # data could be a string
-            self.data = data
-            self.size = len(data)
-        else:                           # data could be None
-            # We'll just leave self.data as ""
-            pass
-
-        request.registerProducer(self, 0)
-
-
-    def resumeProducing(self):
-        """
-        This is twisted's version of a producer's '.more()', or
-        an iterator's '.next()'.  That is, this function is
-        responsible for returning some content.
-        """
-        if not self.request:
-            return
-
-        if self.stream:
-            # If we were provided a Stream, let's grab some data
-            # and push it into our data buffer
-
-            buffer = [self.data]
-            bytesInBuffer = len(buffer[-1])
-            while bytesInBuffer < self.outputBufferSize:
-                try:
-                    buffer.append(self.streamIter.next())
-                    bytesInBuffer += len(buffer[-1])
-                except StopIteration:
-                    # We've exhausted the Stream, time to clean up.
-                    self.stream = None
-                    self.streamIter = None
-                    break
-            self.data = "".join(buffer)
-
-        if self.data:
-            chunkSize = min(self.outputBufferSize, len(self.data))
-            data, self.data = self.data[:chunkSize], self.data[chunkSize:]
-        else:
-            data = ""
-
-        if data:
-            self.request.write(data)
-
-        if not self.data:
-            self.request.unregisterProducer()
-            self.request.finish()
-            self.request = None
-
-    def pauseProducing(self):
-        pass
-
-    def stopProducing(self):
-        self.data    = ""
-        self.request = None
-        self.stream  = None
-        self.streamIter = None
-
-    # Remotely relay producer interface.
-
-    def view_resumeProducing(self, issuer):
-        self.resumeProducing()
-
-    def view_pauseProducing(self, issuer):
-        self.pauseProducing()
-
-    def view_stopProducing(self, issuer):
-        self.stopProducing()
-
-    synchronized = ['resumeProducing', 'stopProducing']
-
-threadable.synchronize(TWProducer)
-
-
-
-class QuixoteFactory(http.HTTPFactory):
-
-    def __init__(self, publisher):
-        self.publisher = publisher
-        http.HTTPFactory.__init__(self, None)
-
-    def buildProtocol(self, addr):
-        p = http.HTTPFactory.buildProtocol(self, addr)
-        p.requestFactory = QuixoteTWRequest
-        return p
-
-
-def Server(root_directory, http_port, **kwargs):
-    from twisted.internet import reactor
-    from quixote.publish import Publisher
-
-    #  If you want SSL, make sure you have OpenSSL,
-    #  uncomment the follownig, and uncomment the
-    #  listenSSL() call below.
-
-    ##from OpenSSL import SSL
-    ##class ServerContextFactory:
-    ##    def getContext(self):
-    ##        ctx = SSL.Context(SSL.SSLv23_METHOD)
-    ##        ctx.use_certificate_file('/path/to/pem/encoded/ssl_cert_file')
-    ##        ctx.use_privatekey_file('/path/to/pem/encoded/ssl_key_file')
-    ##        return ctx
-
-    publisher = Publisher(root_directory, **kwargs)
-    qf = QuixoteFactory(publisher)
-
-    reactor.listenTCP(http_port, qf)
-    ##reactor.listenSSL(http_port, qf, ServerContextFactory())
-
-    return reactor
-
-
-def run(root_directory, port, **kwargs):
-    app = Server(root_directory, port, **kwargs)
-    app.run()
-
-
-if __name__ == '__main__':
-    from quixote import enable_ptl
-    enable_ptl()
-    run('quixote.demo', 8080, display_exceptions='plain')

Copied: trunk/quixote/server/twisted_server.py (from rev 25457, trunk/quixote/server/twisted_http.py)
===================================================================
--- trunk/quixote/server/twisted_http.py	2004-10-26 17:43:03 UTC (rev 25457)
+++ trunk/quixote/server/twisted_server.py	2004-10-27 21:54:59 UTC (rev 25476)
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+"""$URL$
+$Id$
+
+An HTTP server for Twisted that publishes a Quixote application.
+"""
+
+import urllib
+from twisted.protocols import http
+from twisted.web import server
+from twisted.python import threadable
+from twisted.internet import reactor
+from quixote.http_request import HTTPRequest
+
+
+class QuixoteFactory(http.HTTPFactory):
+    def __init__(self, publisher):
+        self.publisher = publisher
+        http.HTTPFactory.__init__(self, None)
+
+    def buildProtocol(self, addr):
+        protocol = http.HTTPFactory.buildProtocol(self, addr)
+        protocol.requestFactory = QuixoteRequest
+        return protocol
+
+
+class QuixoteRequest(server.Request):
+    def process(self):
+        environ = self.create_environment()
+        # this seek is important, it doesn't work without it (it doesn't
+        # matter for GETs, but POSTs will not work properly without it.)
+        self.content.seek(0, 0)
+        qxrequest = HTTPRequest(self.content, environ)
+        qxresponse = self.channel.factory.publisher.process_request(qxrequest)
+        self.setResponseCode(qxresponse.status_code)
+        for name, value in qxresponse.generate_headers():
+            self.setHeader(name, value)
+        QuixoteProducer(qxresponse, self)
+
+    def create_environment(self):
+        """
+        Borrowed heavily from twisted.web.twcgi
+        """
+        # Twisted doesn't decode the path for us, so let's do it here.
+        if '%' in self.path:
+            self.path = urllib.unquote(self.path)
+
+        serverName = self.getRequestHostname().split(':')[0]
+        env = {"SERVER_SOFTWARE":   server.version,
+               "SERVER_NAME":       serverName,
+               "GATEWAY_INTERFACE": "CGI/1.1",
+               "SERVER_PROTOCOL":   self.clientproto,
+               "SERVER_PORT":       str(self.getHost()[2]),
+               "REQUEST_METHOD":    self.method,
+               "SCRIPT_NAME":       '',
+               "SCRIPT_FILENAME":   '',
+               "REQUEST_URI":       self.uri,
+               "HTTPS":             (self.isSecure() and 'on') or 'off',
+               "ACCEPT_ENCODING":   self.getHeader('Accept-encoding'),
+               'CONTENT_TYPE':      self.getHeader('Content-type'),
+               'HTTP_COOKIE':       self.getHeader('Cookie'),
+               'HTTP_REFERER':      self.getHeader('Referer'),
+               'HTTP_USER_AGENT':   self.getHeader('User-agent'),
+               'SERVER_PROTOCOL':   'HTTP/1.1',
+        }
+
+        client = self.getClient()
+        if client is not None:
+            env['REMOTE_HOST'] = client
+        ip = self.getClientIP()
+        if ip is not None:
+            env['REMOTE_ADDR'] = ip
+        _, _, remote_port = self.transport.getPeer()
+        env['REMOTE_PORT'] = remote_port
+        env["PATH_INFO"] = self.path
+
+        qindex = self.uri.find('?')
+        if qindex != -1:
+            env['QUERY_STRING'] = self.uri[qindex+1:]
+        else:
+            env['QUERY_STRING'] = ''
+
+        # Propogate HTTP headers
+        for title, header in self.getAllHeaders().items():
+            envname = title.replace('-', '_').upper()
+            if title not in ('content-type', 'content-length'):
+                envname = "HTTP_" + envname
+            env[envname] = header
+
+        return env
+
+
+class QuixoteProducer:
+    """
+    Produce the Quixote response for twisted.
+    """
+    def __init__(self, qxresponse, request):
+        self.request = request
+        self.size = qxresponse.get_content_length()
+        self.stream = qxresponse.generate_body_chunks()
+        request.registerProducer(self, 0)
+
+    def resumeProducing(self):
+        if self.request:
+            try:
+                chunk = self.stream.next()
+            except StopIteration:
+                self.request.unregisterProducer()
+                self.request.finish()
+                self.request = None
+            else:
+                self.request.write(chunk)
+
+    def pauseProducing(self):
+        pass
+
+    def stopProducing(self):
+        self.request = None
+
+    synchronized = ['resumeProducing', 'stopProducing']
+
+threadable.synchronize(QuixoteProducer)
+
+
+def run(create_publisher, host='', port=80):
+    publisher = create_publisher()
+    factory = QuixoteFactory(publisher)
+    reactor.listenTCP(port, factory, interface=host)
+    reactor.run()
+
+
+if __name__ == '__main__':
+    from quixote.demo import create_publisher
+    run(create_publisher, port=8080)


Property changes on: trunk/quixote/server/twisted_server.py
___________________________________________________________________
Name: svn:executable
   + *
Name: svn:keywords
   + HeadURL Id