--- quixote1/server/twisted_http.py 2004-04-12 10:51:59.000000000 -0500 +++ quixote/server/twisted_http.py 2004-04-12 20:31:18.000000000 -0500 @@ -13,24 +13,33 @@ # 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 from quixote.http_response import Stream +# Imports for the TWProducer object +from twisted.spread import pb +from twisted.python import threadable +from twisted.internet import abstract + 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 + ## (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) @@ -39,8 +48,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 +64,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() @@ -111,6 +118,109 @@ 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): @@ -119,24 +229,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 run (): + from twisted.internet import reactor + from quixote import enable_ptl + from quixote.publish import Publisher + + enable_ptl() + import quixote.demo - # Ports this server will listen on + # Port this server will listen on http_port = 8080 namespace = quixote.demo - app = Application('Quixote') + # 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(http_port, qf) - app.run(save=0) + + reactor.listenTCP(http_port, qf) + ##reactor.listenSSL(http_port, qf, ServerContextFactory()) + + reactor.run() if __name__ == '__main__': run()