################################################## # SPYCE - Python-based HTML Scripting # Copyright (c) 2002 Rimon Barr. # # Refer to spyce.py # CVS: $Id: response.py 826 2006-03-10 00:49:04Z jbe $ ################################################## from spyceModule import spyceModule import string, time __doc__ = '''Response module provides user control over the browser response.''' class response(spyceModule): def start(self): self.clearFilters() self._unbuffer = 0 self._ioerror = 0 self._api.registerResponseCallback(self.syncResponse) self.syncResponse() def syncResponse(self): self._response = self._api.getResponse() def finish(self, theError=None): self._api.unregisterResponseCallback(self.syncResponse) if not theError: self._filter.flush(1) def clearFilters(self): self._filter = FilterUnify(self) self._filterList = [(99, self._filter)] def addFilter(self, level, filter): 'Inject filter functions into output stream at given level of precedence' filterExists = None for i in range(len(self._filterList)): l, _ = self._filterList[i] if l==level: _, filterExists = self._filterList[i] del self._filterList[i] break if filter: self._filterList.append((level, filter)) self._filterList.sort() for i in range(len(self._filterList)-1): l1, f1 = self._filterList[i] l2, f2 = self._filterList[i+1] f1.setNext(f2) _, self._filter = self._filterList[0] return filterExists # user functions def end(self): from spyceException import spyceDone raise spyceDone() def write(self, s): "Write out a dynamic (code) string." try: self._filter.write(s) if self._unbuffer: self.flush() except IOError: self._ioerror = 1 def writeln(self, s): "Writeln out a dynamic (code) string." self.write(s+'\n') def writeStatic(self, s): "Write out a static string." try: self._filter.writeStatic(s) if self._unbuffer: self.flush() except IOError: self._ioerror = 1 def writeExpr(self, s, **kwargs): "Write out an expression result." try: apply(self._filter.writeExpr, (s,), kwargs) if self._unbuffer: self.flush() except IOError: self._ioerror = 1 def clear(self): "Clear the output buffer. (must not be unbuffered)" self._filter.clear() def flush(self, stopFlag=0): "Flush resident buffer." try: self._filter.flush(stopFlag) except IOError: self._ioerror = 1 def setContentType(self, ct): "Set document content type. (must not be unbuffered)" self._response.setContentType(ct) def setReturnCode(self, code): "Set HTTP return (status) code" self._response.setReturnCode(int(code)) def isCancelled(self): return self._ioerror def addHeader(self, type, data, replace=0): "Add an HTTP header. (must not be unbuffered)" if string.find(type, ':') != -1: raise 'HTTP header type should not contain ":" (colon).' self._response.addHeader(type, data, replace) def clearHeaders(self): "Clear all HTTP headers (must not be unbuffered)" self._response.clearHeaders() def unbuffer(self): "Turn off output stream buffering; flush immediately to browser." if self._api._hasParent(): raise 'spyce code with parent template may not specify unbuffered output' self._unbuffer = 1 self.flush() def timestamp(self, thetime=None): "Timestamp response with a HTTP Date header" self.addHeader('Date', _genTimestampString(thetime), 1) def expires(self, thetime=None): "Add HTTP expiration headers" self.addHeader('Expires', _genTimestampString(thetime), 1) def expiresRel(self, secs=0): "Set response expiration (relative to now) with a HTTP Expires header" self.expires(int(time.time())+secs) def lastModified(self, thetime=-1): "Set last modification time" if thetime==-1: filename = self._api.getFilename() if not filename or not os.path.exists(filename): raise 'request filename not found; can not determine last modification time' thetime = os.stat(filename)[9] # file ctime self.addHeader('Last-Modified', _genTimestampString(thetime), 1) # ensure last modified before timestamp, at least when we're generating it if thetime==None: self.timestamp() def uncacheable(self): "Ensure that compliant clients and proxies don't cache this response" self.addHeader('Cache-Control', 'no-store, no-cache, must-revalidate') self.addHeader('Pragma', 'no-cache') def __repr__(self): s = [] s.append('filters: %s' % len(self._filterList)) s.append('unbuffered: %s' % self._unbuffer) return string.join(s, ', ') # from a chat with Rimon: # Yeah, the filters are a pain in the ass. I tried to make them very # general. I think I succeeded. :) you could practically fry eggs with them. # # It's actually not so complicated -- the complexity really only arises # as a consequence of optimizations that are sorely needed for # performance. Remember that this is the IO path, so you need to make # have a short a stack as possible. That's why it has this strange # structure of binding using a control channel, which basically copies # function pointers around that form the data channel. Remember that # the binding happens once; the write() happens hundreds of times. class Filter: def setNext(self, filter): self.next = filter def write(self, s): s = self.dynamicImpl(s) self.next.write(s) def writeStatic(self, s): s = self.staticImpl(s) self.next.writeStatic(s) def writeExpr(self, s, **kwargs): s = apply(self.exprImpl, (s,), kwargs) apply(self.next.writeExpr, (s,), kwargs) def flush(self, stopFlag=0): self.flushImpl() self.next.flush(stopFlag) def clear(self): self.clearImpl() self.next.clear() def dynamicImpl(self, s, *args, **kwargs): raise 'not implemented' def staticImpl(self, s, *args, **kwargs): raise 'not implemented' def exprImpl(self, s, *args, **kwargs): raise 'not implemented' def flushImpl(self): raise 'not implemented' def clearImpl(self): raise 'not implemented' class FilterUnify(Filter): def __init__(self, mod): self.mod = mod self.mod._api.registerResponseCallback(self.syncResponse) self.syncResponse() def syncResponse(self): response = self.mod._api.getResponse() self.write = response.write self.writeStatic = response.write self.flush = response.flush self.clear = response.clear def writeExpr(self, s, **kwargs): if s is None: s = '' self.write(str(s)) def setNext(self, filter): pass # we are always at the end def _genTimestampString(thetime=None): "Generate timestamp string" if thetime==None: thetime = int(time.time()) if type(thetime)==type(0): thetime = time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(thetime)) if type(thetime)==type(''): return thetime raise 'thetime value should be None or string or integer (seconds)'