SVN: r24299 - trunk/quixote/server
Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Thu, 20 May 2004 13:05:38 -0400
| Newsgroups | gmane.comp.web.quixote.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: nascheme
Date: 2004-05-20 13:04:56 -0400 (Thu, 20 May 2004)
New Revision: 24299
Modified:
trunk/quixote/server/twisted_http.py
Log:
Wrap output in a twisted type producer. Use twisted reactor instead of
Application. Thanks to Jason Sibre for the patch.
Modified: trunk/quixote/server/twisted_http.py
===================================================================
--- trunk/quixote/server/twisted_http.py 2004-05-20 15:55:14 UTC (rev 24298)
+++ trunk/quixote/server/twisted_http.py 2004-05-20 17:04:56 UTC (rev 24299)
@@ -13,16 +13,22 @@
# 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.internet.app import Application
from twisted.protocols import http
from twisted.web import server
-import quixote
-quixote.enable_ptl()
-from quixote.publish import Publisher
+# imports for the TWProducer object
+from twisted.spread import pb
+from twisted.python import threadable
+from twisted.internet import abstract
+
from quixote.http_response import Stream
class QuixoteTWRequest(server.Request):
@@ -30,8 +36,9 @@
def process(self):
self.publisher = self.channel.factory.publisher
environ = self.create_environment()
- ## this seek is important, it doesn't work without it
- self.content.seek(0,0)
+ # 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 = self.publisher.create_request(self.content, environ)
self.quixote_publish(qxrequest, environ)
resp = qxrequest.response
@@ -39,8 +46,9 @@
for hdr, value in resp.generate_headers():
self.setHeader(hdr, value)
if resp.body is not None:
- self.write(resp.body)
- self.finish()
+ TWProducer(resp.body, self)
+ else:
+ self.finish()
def quixote_publish(self, qxrequest, env):
@@ -54,9 +62,6 @@
# don't write out the output, just set the response body
# the calling method will do the rest.
if output:
- if isinstance(output, Stream):
- # XXX a Twisted person needs to add support for streams
- output = ''.join(map(str, output))
qxrequest.response.set_body(output)
pub._clear_request()
@@ -117,7 +122,110 @@
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):
@@ -125,24 +233,43 @@
http.HTTPFactory.__init__(self, None)
def buildProtocol (self, addr):
- h = http.HTTPChannel()
- h.requestFactory = QuixoteTWRequest
- h.factory = self
- return h
+ p = http.HTTPFactory.buildProtocol(self, addr)
+ p.requestFactory = QuixoteTWRequest
+ return p
-def Server (namespace, port):
- app = Application('Quixote')
+def Server (namespace, http_port):
+ 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(namespace)
##publisher.setup_logs()
qf = QuixoteFactory(publisher)
- app.listenTCP(port, qf)
- return app
+ reactor.listenTCP(http_port, qf)
+ ##reactor.listenSSL(http_port, qf, ServerContextFactory())
+
+ return reactor
+
+
def run (namespace, port):
app = Server(namespace, port)
- app.run(save=0)
+ app.run()
if __name__ == '__main__':
+ from quixote import enable_ptl
+ enable_ptl()
run('quixote.demo', 8080)