class Out(object): def get(): global theOutput if theOutput is None: raise Error, "No output object has been installed." return theOutput get = staticmethod(get) def __init__(self): global theOutput if theOutput is not None: raise Error, "An output object has already been installed." # Hook standard output self.real_stdout = sys.stdout self.buffer = cStringIO.StringIO() sys.stdout = self.buffer # Default content type self.contenttype = "text/html" # No headers self.headers = Message() self.sendEntityHeaders = True self.redirecting = False self.cookies = Cookie.SimpleCookie() # Detect compression self.acceptsGzip = 0 if os.environ.get("HTTP_ACCEPT_ENCODING", "").find("gzip") != -1: self.acceptsGzip = 1 # Trap exiting a script to force output to close self.ran = False theOutput = self sys.exitfunc = Close def getResponseBody(self, body): if self.acceptsGzip: # gzip writes the time into its header # this kills cacheability, so we hack the time to be 0 real_time = time.time time.time = lambda a=None: 0 zbuf = cStringIO.StringIO() zfile = gzip.GzipFile(mode="wb", fileobj=zbuf) zfile.write(body) zfile.close() self.headers["Content-Encoding"] = "gzip" output = zbuf.getvalue() time.time = real_time return output else: return body def close(self): if self.ran: return # Restore stdout so we can pipe our data out sys.stdout = self.real_stdout body = self.buffer.getvalue() headers = "" self.buffer.close() # Look for any headers already written if re.search("location:", body, re.I): self.redirecting = True self.sendEntityHeaders = False msg = HeaderParser().parsestr(body, True) # Add written headers to our header list for name, value in msg.items(): self.headers.add_header(name, value) elif re.search("content-type:", body, re.I): try: endOfHeaders = body.index("\n\n") headers = body[:endOfHeaders] body = body[endOfHeaders + 2 :] msg = HeaderParser().parsestr(headers, True) # Add written headers to our header list for name, value in msg.items(): self.headers.add_header(name, value) except ValueError: raise error, "Headers detected, sort of" # Get the response body, possibly compressed if self.sendEntityHeaders: body = self.getResponseBody(body) del self.headers["Content-Length"] self.headers["Content-Length"] = str(len(body)) if not self.redirecting and (not "content-type" in self.headers): self.headers["Content-Type"] = self.contenttype etag = base64.encodestring(md5.new(body).digest())[:-1] self.headers["ETag"] = '"' + etag + '"' if "HTTP_IF_NONE_MATCH" in os.environ: if etag in ETags(os.environ["HTTP_IF_NONE_MATCH"]): print "Status: 304 Not Modified" self.sendEntityHeaders = False self._send_headers() if self.sendEntityHeaders: sys.stdout.write(body) self.ran = True def _send_headers(self): for header in self.headers.keys(): if (not self.sendEntityHeaders) and (header.lower() in entity_headers): continue print header + ": " + str(self.headers[header]) if len(self.cookies) > 0: print self.cookies.output() print