durusmail: quixote-users: version 0.2 (Re: Quixote Server on top of Twisted Python, revisited)
Quixote Server on top of Twisted Python, revisited
2003-03-25
Re: Quixote Server on top of Twisted Python, revisited
2003-03-25
version 0.2 (Re: Quixote Server on top of Twisted Python, revisited)
2003-03-25
Quixote Server on top of Twisted Python, revisited
2003-04-03
Re: Quixote Server on top of Twisted Python, revisited
2003-04-03
version 0.2 (Re: Quixote Server on top of Twisted Python, revisited)
Graham Fawcett
2003-03-25
I found a couple of problems with my original script:

The method http_response.HTTPResponse.write(), which I side-stepped,
does more than just push out the body of the response, it write cookies
and some extra headers. This version of the script replicates that
behaviour.

Also, I had forgotten the call to finish_successful_request(), so the
session management post-handler wasn't being called.

-- G.


"""
twist -- Demo of an HTTP server built on top of Twisted Python.
"""
# 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)

import quixote
quixote.enable_ptl()
import quixote.demo
from quixote import errors
from quixote.publish import Publisher

from twisted.internet.app import Application
from twisted.protocols import http
from twisted.web import server

from rfc822 import formatdate
import time


class QuixoteTWRequest(server.Request):

     def process(self):
         self.publisher = self.channel.factory.publisher
         environ = self.create_environment()
         ## this seek is important, it doesnt work without it
         self.content.seek(0,0)
         qxrequest = self.publisher.create_request(self.content, environ)
         self.quixote_publish(qxrequest, environ)
         self.massage_response(qxrequest)
         self.write(qxrequest.response.body)
         self.finish()


     def massage_response(self, qxrequest):
         """
         Does the work of reponse.write(), without actually writing,
         just massages the response into a Twisted fashion.
         """
         resp = qxrequest.response
         self.setResponseCode(resp.status_code)

         for hdr, value in resp.headers.items():
             self.setHeader(hdr, value)

         now = time.time()
         if not resp.headers.has_key('date'):
             resp.headers['date'] = '%s\r\n' % formatdate(now)

         if not resp.headers.has_key('expires'):
             if resp.cache > 0:
                 expire_date = formatdate(now + resp.cache)
             else:
                 expire_date = "-1" # allowed by HTTP spec, may work
                                    # better with some clients
             resp.headers['expires'] = '%s\r\n' % expire_date

         for k, d in resp.cookies.items():
             value = d['value']
             del d['value']
             # this might be a bit risky, the addCookie signature is
             # addCookie(self, k, v, expires=None, domain=None,
             #   path=None, max_age=None, comment=None, secure=None)
             self.addCookie(k, value, **d)


     def quixote_publish(self, qxrequest, env):
         """
         Warning, this sidesteps the Publisher.publish method,
         Hope you didn't override it...
         """
         pub = self.publisher
         try:
             pub.parse_request(qxrequest)
             output = pub.process_request(qxrequest, env)
             # needed for session management!
             pub.finish_successful_request(qxrequest)
         except errors.PublishError, exc:
             # Exit the publishing loop and return a result right away.
             output = pub.finish_interrupted_request(qxrequest, exc)
         except:
             # other exception, generate error messages to logs, etc.
             output = pub.finish_failed_request(qxrequest)

         # don't write out the output, just set the response body
         # the calling method will do the rest.
         if output:
             qxrequest.response.set_body(str(output))

         pub._clear_request()


     def create_environment(self):
         """
         Borrowed heavily from twisted.web.twcgi
         """
         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,
         }

         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 QuixoteFactory (http.HTTPFactory):

     def __init__(self, publisher):
         self.publisher = publisher
         http.HTTPFactory.__init__(self, None)

     def buildProtocol (self, addr):
         h = http.HTTPChannel()
         h.requestFactory = QuixoteTWRequest
         h.factory = self
         return h


def run ():
     # Ports this server will listen on
     http_port = 8080
     namespace = quixote.demo

     app = Application('Quixote')
     publisher = Publisher(namespace)
     publisher.setup_logs()
     qf = QuixoteFactory(publisher)
     app.listenTCP(http_port, qf)
     app.run(save=0)

if __name__ == '__main__':
     run()



reply