[Winopenrpg-developer] openrpg1/plugins/cherrypy __init__.py,NONE,1.1 _cpconfig.py,NONE,1.1 _cpdefau
Status: Inactive
Brought to you by:
digitalxero
|
From: Digital X. <dig...@us...> - 2006-01-26 17:33:27
|
Update of /cvsroot/winopenrpg/openrpg1/plugins/cherrypy In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv30930/plugins/cherrypy Added Files: __init__.py _cpconfig.py _cpdefaults.py _cphttpserver.py _cphttptools.py _cpserver.py _cpthreadinglocal.py _cputil.py cperror.py cpg.py wsgiapp.py Log Message: Initial commit of OpenRPG++ python --- NEW FILE: wsgiapp.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ WSGI interface for CherryPy """ import StringIO, Cookie, time from cherrypy import cpg, _cphttptools, _cpserver def init(*a, **kw): kw['initOnly'] = 1 _cpserver.start(*a, **kw) def wsgiApp(environ, start_response): cpg.request.method = environ['REQUEST_METHOD'] # Rebuild first line of the request pathInfo = environ['PATH_INFO'] qString = environ.get('QUERY_STRING') if qString: pathInfo += '?' + qString firstLine = '%s %s %s' % ( environ['REQUEST_METHOD'], pathInfo or '/', environ['SERVER_PROTOCOL'] ) _cphttptools.parseFirstLine(firstLine) # Initialize variables now = time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (_cphttptools.weekdayname[wd], day, _cphttptools.monthname[month], year, hh, mm, ss) cpg.request.headerMap = {} cpg.request.simpleCookie = Cookie.SimpleCookie() cpg.response.simpleCookie = Cookie.SimpleCookie() # Rebuild headerMap for cgiName, headerName in [ ('HTTP_HOST', 'Host'), ('HTTP_USER_AGENT', 'User-Agent'), ('HTTP_CGI_AUTHORIZATION', 'Authorization'), ('CONTENT_LENGTH', 'Content-Length'), ('CONTENT_TYPE', 'Content-Type'), ('HTTP_COOKIE', 'Cookie'), ('REMOTE_HOST', 'Remote-Host'), ('REMOTE_ADDR', 'Remote-Addr'), ('HTTP_REFERER', 'Referer'), ('HTTP_ACCEPT_ENCODING', 'Accept-Encoding'), ]: if cgiName in environ: _cphttptools.insertIntoHeaderMap(headerName, environ[cgiName]) # TODO: handle POST # set up stuff similar to initRequest cpg.response.headerMap = { "protocolVersion": cpg.configOption.protocolVersion, "Status": "200 OK", "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0 } cpg.request.base = "http://" + cpg.request.headerMap['Host'] cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl cpg.request.isStatic = False cpg.request.parsePostData = True cpg.request.rfile = environ["wsgi.input"] cpg.request.objectPath = None if 'Cookie' in cpg.request.headerMap: cpg.request.simpleCookie.load(cpg.request.headerMap['Cookie']) cpg.response.simpleCookie = Cookie.SimpleCookie() cpg.response.sendResponse = 1 if cpg.request.method == 'POST' and cpg.request.parsePostData: _cphttptools.parsePostData(cpg.request.rfile) # Execute request wfile = StringIO.StringIO() cpg.response.wfile = wfile _cphttptools.handleRequest(wfile) response = wfile.getvalue() # Extract header from response headerLines = [] i = 0 while 1: j = response.find('\n', i) line = response[i:j] if line[-1] == '\r': line = line[:-1] headerLines.append(line) i = j+1 if not line: break response = response[i:] status = headerLines[0] # Remove "HTTP/1.0" at the beginning of status i = status.find(' ') status = status[i+1:] responseHeaders = [] for line in headerLines[1:]: i = line.find(':') header = line[:i] value = line[i+1:].lstrip() responseHeaders.append((header,value)) start_response(status, responseHeaders) return response if __name__ == '__main__': from cherrypy import cpg, wsgiapp class Root: def index(self, name = "world"): count = cpg.request.sessionMap.get('count', 0) + 1 cpg.request.sessionMap['count'] = count return """ <html><body> Hello, %s, count is %s: <form action="/post" method="post"> Post some data: <input name=myData type=text"> <input type=submit> </form> """ % (name, count) index.exposed = True def post(self, myData): return "myData: " + myData post.exposed = True cpg.root = Root() import sys # This uses the WSGI HTTP server from PEAK.wsgiref # sys.path.append(r"C:\Tmp\PEAK\src") from wsgiref.simple_server import WSGIServer, WSGIRequestHandler # Read the CherryPy config file and initialize some variables wsgiapp.init(configMap = {'socketPort': 8000, 'sessionStorageType': 'ram'}) server_address = ("", 8000) httpd = WSGIServer(server_address, WSGIRequestHandler) httpd.set_app(wsgiapp.wsgiApp) sa = httpd.socket.getsockname() #print "Serving HTTP on", sa[0], "port", sa[1], "..." httpd.serve_forever() --- NEW FILE: cperror.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Main CherryPy module: - Parses config file - Creates the HTTP server """ class Error(Exception): pass class InternalError(Error): """ Error that should never happen """ pass class NotFound(Error): """ Happens when a URL couldn't be mapped to any class.method """ pass class WrongResponseType(Error): """ Happens when the cpg.response.body is not a string """ pass --- NEW FILE: _cputil.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ A module containing a few utility classes/functions used by CherryPy """ import time, thread, cpg, _cpdefaults, cperror try: import zlib except ImportError: pass class EmptyClass: """ An empty class """ pass def getSpecialFunction(name): """ Return the special function """ # First, we look in the right-most object if this special function is implemented. # If not, then we try the previous object and so on until we reach cpg.root # If it's still not there, we use the implementation from the # "_cpdefaults.py" module moduleList = [_cpdefaults] root = getattr(cpg, 'root', None) if root: moduleList.append(root) # Try object path try: path = cpg.request.objectPath or cpg.request.path except: path = '/' if path: pathList = path.split('/')[1:] obj = cpg.root previousObj = None # Successively get objects from the path for newObj in pathList: previousObj = obj try: obj = getattr(obj, newObj) moduleList.append(obj) except AttributeError: break moduleList.reverse() for module in moduleList: func = getattr(module, name, None) if func != None: return func raise cperror.InternalError, "Special function %s could not be found" % repr(name) --- NEW FILE: __init__.py --- __version__ = '2.0.0' --- NEW FILE: _cpconfig.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import _cputil, ConfigParser, cpg def setDefaultConfigOption(): """ Return an EmptyClass instance with the default config options """ cpg.configOption = _cputil.EmptyClass() # Set default values for all options # Parameters used for logging cpg.configOption.logToScreen = 1 cpg.configOption.logFile = '' # Parameters used to tell which socket the server should listen on # Note that socketPort and socketFile conflict wich each # other: if one has a non-null value, the other one should be null cpg.configOption.socketHost = '' cpg.configOption.socketPort = 8080 cpg.configOption.socketFile = '' # Used if server should listen on # AF_UNIX socket cpg.configOption.reverseDNS = 0 cpg.configOption.socketQueueSize = 5 # Size of the socket queue cpg.configOption.protocolVersion = "HTTP/1.0" # Parameters used to tell what kind of server we want cpg.configOption.threadPool = 0 # Used if we want to create a pool # of threads at the beginning # Variables used to tell if this is an SSL server cpg.configOption.sslKeyFile = "" cpg.configOption.sslCertificateFile = "" cpg.configOption.sslClientCertificateVerification = 0 cpg.configOption.sslCACertificateFile = "" cpg.configOption.sslVerifyDepth = 1 # Variable used to flush cache cpg.configOption.flushCacheDelay=0 # Variable used for enabling debugging cpg.configOption.debugMode=0 # Variable used to serve static content cpg.configOption.staticContentList = [] # Variable used for session handling cpg.configOption.sessionStorageType = "" cpg.configOption.sessionTimeout = 60 # In minutes cpg.configOption.sessionCleanUpDelay = 60 # In minutes cpg.configOption.sessionCookieName = "CherryPySession" cpg.configOption.sessionStorageFileDir = "" def parseConfigFile(configFile = None, parsedConfigFile = None): """ Parse the config file and set values in cpg.configOption """ _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') if configFile: cpg.parsedConfigFile = ConfigParser.ConfigParser() if hasattr(configFile, 'read'): _cpLogMessage("Reading infos from configFile stream", 'CONFIG') cpg.parsedConfigFile.readfp(configFile) else: _cpLogMessage("Reading infos from configFile: %s" % configFile, 'CONFIG') cpg.parsedConfigFile.read(configFile) else: cpg.parsedConfigFile = parsedConfigFile # Read parameters from configFile for sectionName, optionName, valueType in [ ('server', 'logToScreen', 'int'), ('server', 'logFile', 'str'), ('server', 'socketHost', 'str'), ('server', 'protocolVersion', 'str'), ('server', 'socketPort', 'int'), ('server', 'socketFile', 'str'), ('server', 'reverseDNS', 'int'), ('server', 'threadPool', 'int'), ('server', 'sslKeyFile', 'str'), ('server', 'sslCertificateFile', 'str'), ('server', 'sslClientCertificateVerification', 'int'), ('server', 'sslCACertificateFile', 'str'), ('server', 'sslVerifyDepth', 'int'), ('session', 'storageType', 'str'), ('session', 'timeout', 'float'), ('session', 'cleanUpDelay', 'float'), ('session', 'cookieName', 'str'), ('session', 'storageFileDir', 'str') ]: try: value = cpg.parsedConfigFile.get(sectionName, optionName) if valueType == 'int': value = int(value) elif valueType == 'float': value = float(value) if sectionName == 'session': optionName = 'session' + optionName[0].upper() + optionName[1:] setattr(cpg.configOption, optionName, value) except: pass try: staticDirList = cpg.parsedConfigFile.options('staticContent') for staticDir in staticDirList: staticDirTarget = cpg.parsedConfigFile.get('staticContent', staticDir) cpg.configOption.staticContentList.append((staticDir, staticDirTarget)) except: pass def outputConfigOptions(): _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage') _cpLogMessage("Server parameters:", 'CONFIG') _cpLogMessage(" logToScreen: %s" % cpg.configOption.logToScreen, 'CONFIG') _cpLogMessage(" logFile: %s" % cpg.configOption.logFile, 'CONFIG') _cpLogMessage(" protocolVersion: %s" % cpg.configOption.protocolVersion, 'CONFIG') _cpLogMessage(" socketHost: %s" % cpg.configOption.socketHost, 'CONFIG') _cpLogMessage(" socketPort: %s" % cpg.configOption.socketPort, 'CONFIG') _cpLogMessage(" socketFile: %s" % cpg.configOption.socketFile, 'CONFIG') _cpLogMessage(" reverseDNS: %s" % cpg.configOption.reverseDNS, 'CONFIG') _cpLogMessage(" socketQueueSize: %s" % cpg.configOption.socketQueueSize, 'CONFIG') _cpLogMessage(" threadPool: %s" % cpg.configOption.threadPool, 'CONFIG') _cpLogMessage(" sslKeyFile: %s" % cpg.configOption.sslKeyFile, 'CONFIG') if cpg.configOption.sslKeyFile: _cpLogMessage(" sslCertificateFile: %s" % cpg.configOption.sslCertificateFile, 'CONFIG') _cpLogMessage(" sslClientCertificateVerification: %s" % cpg.configOption.sslClientCertificateVerification, 'CONFIG') _cpLogMessage(" sslCACertificateFile: %s" % cpg.configOption.sslCACertificateFile, 'CONFIG') _cpLogMessage(" sslVerifyDepth: %s" % cpg.configOption.sslVerifyDepth, 'CONFIG') _cpLogMessage(" flushCacheDelay: %s min" % cpg.configOption.flushCacheDelay, 'CONFIG') _cpLogMessage(" sessionStorageType: %s" % cpg.configOption.sessionStorageType, 'CONFIG') if cpg.configOption.sessionStorageType: _cpLogMessage(" sessionTimeout: %s min" % cpg.configOption.sessionTimeout, 'CONFIG') _cpLogMessage(" cleanUpDelay: %s min" % cpg.configOption.sessionCleanUpDelay, 'CONFIG') _cpLogMessage(" sessionCookieName: %s" % cpg.configOption.sessionCookieName, 'CONFIG') _cpLogMessage(" sessionStorageFileDir: %s" % cpg.configOption.sessionStorageFileDir, 'CONFIG') _cpLogMessage(" staticContent: %s" % cpg.configOption.staticContentList, 'CONFIG') def dummy(): # Check that parameters are correct and that they don't conflict with each other if _protocolVersion not in ("HTTP/1.1", "HTTP/1.0"): raise "CherryError: protocolVersion must be 'HTTP/1.1' or 'HTTP/1.0'" if _reverseDNS not in (0,1): raise "CherryError: reverseDNS must be '0' or '1'" if _socketFile and not hasattr(socket, 'AF_UNIX'): raise "CherryError: Configuration file has socketFile, but this is only available on Unix machines" if _sslKeyFile: try: global SSL from OpenSSL import SSL except: raise "CherryError: PyOpenSSL 0.5.1 or later must be installed to use SSL. You can get it from http://pyopenssl.sourceforge.net" if _socketPort and _socketFile: raise "CherryError: In configuration file: socketPort and socketFile conflict with each other" if not _socketFile and not _socketPort: _socketPort=8000 # Default port if _sslKeyFile and not _sslCertificateFile: raise "CherryError: Configuration file has sslKeyFile but no sslCertificateFile" if _sslCertificateFile and not _sslKeyFile: raise "CherryError: Configuration file has sslCertificateFile but no sslKeyFile" try: sys.stdout.flush() except: pass if _sessionStorageType not in ('', 'custom', 'ram', 'file', 'cookie'): raise "CherryError: Configuration file an invalid sessionStorageType: '%s'"%_sessionStorageType if _sessionStorageType in ('custom', 'ram', 'cookie') and _sessionStorageFileDir!='': raise "CherryError: Configuration file has sessionStorageType set to 'custom, 'ram' or 'cookie' but a sessionStorageFileDir is specified" if _sessionStorageType=='file' and _sessionStorageFileDir=='': raise "CherryError: Configuration file has sessionStorageType set to 'file' but no sessionStorageFileDir" --- NEW FILE: cpg.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ Global module that all modules developing with CherryPy should import. """ from __init__ import __version__ # import server module import _cpserver as server # decorator function for exposing methods def expose(func): func.exposed = True return func --- NEW FILE: _cpthreadinglocal.py --- # This is a backport of Python-2.4's threading.local() implementation """Thread-local objects (Note that this module provides a Python version of thread threading.local class. Depending on the version of Python you're using, there may be a faster one available. You should always import the local class from threading.) Thread-local objects support the management of thread-local data. If you have data that you want to be local to a thread, simply create a thread-local object and use its attributes: >>> mydata = local() >>> mydata.number = 42 >>> mydata.number 42 You can also access the local-object's dictionary: >>> mydata.__dict__ {'number': 42} >>> mydata.__dict__.setdefault('widgets', []) [] >>> mydata.widgets [] What's important about thread-local objects is that their data are local to a thread. If we access the data in a different thread: >>> log = [] >>> def f(): ... items = mydata.__dict__.items() ... items.sort() ... log.append(items) ... mydata.number = 11 ... log.append(mydata.number) >>> import threading >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() >>> log [[], 11] we get different data. Furthermore, changes made in the other thread don't affect data seen in this thread: >>> mydata.number 42 Of course, values you get from a local object, including a __dict__ attribute, are for whatever thread was current at the time the attribute was read. For that reason, you generally don't want to save these values across threads, as they apply only to the thread they came from. You can create custom local objects by subclassing the local class: >>> class MyLocal(local): ... number = 2 ... initialized = False ... def __init__(self, **kw): ... if self.initialized: ... raise SystemError('__init__ called too many times') ... self.initialized = True ... self.__dict__.update(kw) ... def squared(self): ... return self.number ** 2 This can be useful to support default values, methods and initialization. Note that if you define an __init__ method, it will be called each time the local object is used in a separate thread. This is necessary to initialize each thread's dictionary. Now if we create a local object: >>> mydata = MyLocal(color='red') Now we have a default number: >>> mydata.number 2 an initial color: >>> mydata.color 'red' >>> del mydata.color And a method that operates on the data: >>> mydata.squared() 4 As before, we can access the data in a separate thread: >>> log = [] >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() >>> log [[('color', 'red'), ('initialized', True)], 11] without affecting this thread's data: >>> mydata.number 2 >>> mydata.color Traceback (most recent call last): ... AttributeError: 'MyLocal' object has no attribute 'color' Note that subclasses can define slots, but they are not thread local. They are shared across threads: >>> class MyLocal(local): ... __slots__ = 'number' >>> mydata = MyLocal() >>> mydata.number = 42 >>> mydata.color = 'red' So, the separate thread: >>> thread = threading.Thread(target=f) >>> thread.start() >>> thread.join() affects what we see: >>> mydata.number 11 >>> del mydata """ # Threading import is at end class _localbase(object): __slots__ = '_local__key', '_local__args', '_local__lock' def __new__(cls, *args, **kw): self = object.__new__(cls) key = '_local__key', 'thread.local.' + str(id(self)) object.__setattr__(self, '_local__key', key) object.__setattr__(self, '_local__args', (args, kw)) object.__setattr__(self, '_local__lock', RLock()) if args or kw and (cls.__init__ is object.__init__): raise TypeError("Initialization arguments are not supported") # We need to create the thread dict in anticipation of # __init__ being called, to make sire we don't cal it # again ourselves. dict = object.__getattribute__(self, '__dict__') currentThread().__dict__[key] = dict return self def _patch(self): key = object.__getattribute__(self, '_local__key') d = currentThread().__dict__.get(key) if d is None: d = {} currentThread().__dict__[key] = d object.__setattr__(self, '__dict__', d) # we have a new instance dict, so call out __init__ if we have # one cls = type(self) if cls.__init__ is not object.__init__: args, kw = object.__getattribute__(self, '_local__args') cls.__init__(self, *args, **kw) else: object.__setattr__(self, '__dict__', d) class local(_localbase): def __getattribute__(self, name): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__getattribute__(self, name) finally: lock.release() def __setattr__(self, name, value): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__setattr__(self, name, value) finally: lock.release() def __delattr__(self, name): lock = object.__getattribute__(self, '_local__lock') lock.acquire() try: _patch(self) return object.__delattr__(self, name) finally: lock.release() def __del__(): threading_enumerate = enumerate __getattribute__ = object.__getattribute__ def __del__(self): key = __getattribute__(self, '_local__key') try: threads = list(threading_enumerate()) except: # if enumerate fails, as it seems to do during # shutdown, we'll skip cleanup under the assumption # that there is nothing to clean up return for thread in threads: try: __dict__ = thread.__dict__ except AttributeError: # Thread is dying, rest in peace continue if key in __dict__: try: del __dict__[key] except KeyError: pass # didn't have anything in this thread return __del__ __del__ = __del__() from threading import currentThread, enumerate, RLock --- NEW FILE: _cpdefaults.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ """ A module containing a few utility classes/functions used by CherryPy """ import time, thread, os, cpg import cPickle as pickle def _cpLogMessage(msg, context = '', severity = 0): """ Default method for logging messages """ nowTuple = time.localtime(time.time()) nowStr = '%04d/%02d/%02d %02d:%02d:%02d' % (nowTuple[:6]) if severity == 0: level = "INFO" elif severity == 1: level = "WARNING" elif severity == 2: level = "ERROR" else: lebel = "UNKNOWN" try: logToScreen = int(cpg.configOption.logToScreen) except: logToScreen = True s = nowStr + ' ' + context + ' ' + level + ' ' + msg if logToScreen: print s if cpg.configOption.logFile: f = open(cpg.configOption.logFile, 'ab') f.write(s + '\n') f.close() def _cpOnError(): """ Default _cpOnError method """ import traceback, StringIO bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) cpg.response.body = [bodyFile.getvalue()] cpg.response.headerMap['Content-Type'] = 'text/plain' def _cpSaveSessionData(sessionId, sessionData, expirationTime, threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Save session data if needed """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir t = time.localtime(expirationTime) if sessionStorageType == 'file': fname=os.path.join(sessionStorageFileDir,sessionId) if threadPool > 1: cpg._sessionFileLock.acquire() f = open(fname,"wb") pickle.dump((sessionData, expirationTime), f) f.close() if threadPool > 1: cpg._sessionFileLock.release() elif sessionStorageType=="ram": # Update expiration time cpg._sessionMap[sessionId] = (sessionData, expirationTime) """ TODO: implement cookie storage type elif sessionStorageType == "cookie": TODO: set siteKey in _cpConfig # Get site key from config file or compute it try: cpg._SITE_KEY_ = configFile.get('server','siteKey') except: _SITE_KEY_ = '' for i in range(30): _SITE_KEY_ += random.choice(string.letters) # Update expiration time sessionData = (sessionData, expirationTime) dumpStr = pickle.dumps(_sessionData) try: dumpStr = zlib.compress(dumpStr) except: pass # zlib is not available in all python distros dumpStr = binascii.hexlify(dumpStr) # Need to hexlify it because it will be stored in a cookie cpg.response.simpleCookie['CSession'] = dumpStr cpg.response.simpleCookie['CSession-sig'] = md5.md5(dumpStr + cpg.configOption.siteKey).hexdigest() cpg.response.simpleCookie['CSession']['path'] = '/' cpg.response.simpleCookie['CSession']['max-age'] = sessionTimeout * 60 cpg.response.simpleCookie['CSession-sig']['path'] = '/' cpg.response.simpleCookie['CSession-sig']['max-age'] = sessionTimeout * 60 """ def _cpLoadSessionData(sessionId, threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Return the session data for a given sessionId. The _expirationTime will be checked by the caller of this function """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir if sessionStorageType == "ram": if cpg._sessionMap.has_key(sessionId): return cpg._sessionMap[sessionId] else: return None elif sessionStorageType == "file": fname = os.path.join(sessionStorageFileDir, sessionId) if os.path.exists(fname): if threadPool > 1: cpg._sessionFileLock.acquire() f = open(fname, "rb") sessionData = pickle.load(f) f.close() if threadPool > 1: cpg._sessionFileLock.release() return sessionData else: return None """ TODO: implement cookie storage type elif _sessionStorageType == "cookie": if request.simpleCookie.has_key('CSession') and request.simpleCookie.has_key('CSession-sig'): data = request.simpleCookie['CSession'].value sig = request.simpleCookie['CSession-sig'].value if md5.md5(data + cpg.configOption.siteKey).hexdigest() == sig: try: dumpStr = binascii.unhexlify(data) try: dumpStr = zlib.decompress(dumpStr) except: pass # zlib is not available in all python distros dumpStr = pickle.loads(dumpStr) return dumpStr except: pass return None """ def _cpCleanUpOldSessions(threadPool = None, sessionStorageType = None, sessionStorageFileDir = None): """ Clean up old sessions """ if threadPool is None: threadPool = cpg.configOption.threadPool if sessionStorageType is None: sessionStorageType = cpg.configOption.sessionStorageType if sessionStorageFileDir is None: sessionStorageFileDir = cpg.configOption.sessionStorageFileDir # Clean up old session data now = time.time() if sessionStorageType == "ram": sessionIdToDeleteList = [] for sessionId, (dummy, expirationTime) in cpg._sessionMap.items(): if expirationTime < now: sessionIdToDeleteList.append(sessionId) for sessionId in sessionIdToDeleteList: del cpg._sessionMap[sessionId] elif sessionStorageType=="file": # This process is very expensive because we go through all files, parse them and then delete them if the session is expired # One optimization would be to just store a list of (sessionId, expirationTime) in *one* file sessionFileList = os.listdir(sessionStorageFileDir) for sessionId in sessionFileList: try: dummy, expirationTime = _cpLoadSessionData(sessionId) if expirationTime < now: os.remove(os.path.join(sessionStorageFileDir, sessionId)) except: pass elif sessionStorageType == "cookie": # Nothing to do in this case: the session data is stored on the client pass _cpFilterList = [] --- NEW FILE: _cphttptools.py --- """ Copyright (c) 2004, CherryPy Team (te...@ch...) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os import mimetypes, sha, random, string, _cputil, cperror, Cookie, urlparse from lib.filter import basefilter """ Common Service Code for CherryPy """ mimetypes.types_map['.dwg']='image/x-dwg' mimetypes.types_map['.ico']='image/x-icon' weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] class IndexRedirect(Exception): pass def parseFirstLine(data): cpg.request.path = data.split()[1] cpg.request.queryString = "" cpg.request.browserUrl = cpg.request.path cpg.request.paramMap = {} cpg.request.paramList = [] # Only used for Xml-Rpc cpg.request.filenameMap = {} cpg.request.fileTypeMap = {} i = cpg.request.path.find('?') if i != -1: # Parse parameters from URL if cpg.request.path[i+1:]: k = cpg.request.path[i+1:].find('?') if k != -1: j = cpg.request.path[:k].rfind('=') if j != -1: cpg.request.path = cpg.request.path[:j+1] + \ urllib.quote_plus(cpg.request.path[j+1:]) for paramStr in cpg.request.path[i+1:].split('&'): sp = paramStr.split('=') if len(sp) > 2: j = paramStr.find('=') sp = (paramStr[:j], paramStr[j+1:]) if len(sp) == 2: key, value = sp value = urllib.unquote_plus(value) if cpg.request.paramMap.has_key(key): # Already has a value: make a list out of it if type(cpg.request.paramMap[key]) == type([]): # Already is a list: append the new value to it cpg.request.paramMap[key].append(value) else: # Only had one value so far: start a list cpg.request.paramMap[key] = [cpg.request.paramMap[key], value] else: cpg.request.paramMap[key] = value cpg.request.queryString = cpg.request.path[i+1:] cpg.request.path = cpg.request.path[:i] def cookHeaders(clientAddress, remoteHost, headers, requestLine): """Process the headers into the request.headerMap""" cpg.request.headerMap = {} cpg.request.requestLine = requestLine cpg.request.simpleCookie = Cookie.SimpleCookie() # Build headerMap for item in headers.items(): # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) insertIntoHeaderMap(item[0],item[1]) # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key cookieList = headers.getallmatchingheaders('cookie') for cookie in cookieList: cpg.request.simpleCookie.load(cookie) cpg.request.remoteAddr = clientAddress cpg.request.remoteHost = remoteHost # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate try: cpg.request.peerCertificate = self.request.get_peer_certificate() except: pass _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") def parsePostData(rfile): # Read request body and put it in data len = int(cpg.request.headerMap.get("Content-Length","0")) if len: data = rfile.read(len) else: data="" # Put data in a StringIO so FieldStorage can read it newRfile = StringIO.StringIO(data) # Create a copy of headerMap with lowercase keys because # FieldStorage doesn't work otherwise lowerHeaderMap = {} for key, value in cpg.request.headerMap.items(): lowerHeaderMap[key.lower()] = value forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) for key in forms.keys(): # Check if it's a list or not valueList = forms[key] if type(valueList) == type([]): # It's a list of values cpg.request.paramMap[key] = [] cpg.request.filenameMap[key] = [] cpg.request.fileTypeMap[key] = [] for item in valueList: cpg.request.paramMap[key].append(item.value) cpg.request.filenameMap[key].append(item.filename) cpg.request.fileTypeMap[key].append(item.type) else: # It's a single value # In case it's a file being uploaded, we save the filename in a map (user might need it) cpg.request.paramMap[key] = valueList.value cpg.request.filenameMap[key] = valueList.filename cpg.request.fileTypeMap[key] = valueList.type def applyFilterList(methodName): try: filterList = _cputil.getSpecialFunction('_cpFilterList') for filter in filterList: method = getattr(filter, methodName, None) if method: method() except basefilter.InternalRedirect: # If we get an InternalRedirect, we start the filter list # from scratch. Is cpg.request.path or cpg.request.objectPath # has been modified by the hook, then a new filter list # will be applied. # We use recursion so if there is an infinite loop, we'll # get the regular python "recursion limit exceeded" exception. applyFilterList(methodName) def insertIntoHeaderMap(key,value): normalizedKey = '-'.join([s.capitalize() for s in key.split('-')]) cpg.request.headerMap[normalizedKey] = value def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): parseFirstLine(requestLine) cookHeaders(clientAddress, remoteHost, headers, requestLine) cpg.request.base = "http://" + cpg.request.headerMap['Host'] cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl cpg.request.isStatic = False cpg.request.parsePostData = True cpg.request.rfile = rfile # Change objectPath in filters to change the object that will get rendered cpg.request.objectPath = None applyFilterList('afterRequestHeader') if cpg.request.method == 'POST' and cpg.request.parsePostData: parsePostData(rfile) applyFilterList('afterRequestBody') def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): # creates some attributes on cpg.response so filters can use them cpg.response.wfile = wfile cpg.response.sendResponse = 1 try: initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile) except basefilter.RequestHandled: # request was already fully handled; it may be a cache hit return # Prepare response variables now = time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss) cpg.response.headerMap = { "protocolVersion": cpg.configOption.protocolVersion, "Status": "200 OK", "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0 } cpg.response.simpleCookie = Cookie.SimpleCookie() try: handleRequest(cpg.response.wfile) except: # TODO: in some cases exceptions and filters are conflicting; # error reporting seems to be broken in some cases. This code is # a helper to check it err = "" exc_info_1 = sys.exc_info()[1] if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1: err = exc_info_1.args[0] try: _cputil.getSpecialFunction('_cpOnError')() # Still save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) except: bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) body = bodyFile.getvalue() wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion) wfile.write('Content-Type: text/plain\r\n') wfile.write('Content-Length: %s\r\n' % len(body)) wfile.write('\r\n') wfile.write(body) def sendResponse(wfile): applyFilterList('beforeResponse') # Set the content-length if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) # Save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n' % (key, value)) # Send response cookies cookie = cpg.response.simpleCookie.output() if cookie: wfile.write(cookie+'\r\n') wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) # finalization hook for filter cleanup & logging purposes applyFilterList('afterResponse') def handleRequest(wfile): # Clean up expired sessions if needed: now = time.time() if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now: cpg._lastSessionCleanUpTime = now _cputil.getSpecialFunction('_cpCleanUpOldSessions')() # Save original values (in case they get modified by filters) cpg.request.originalPath = cpg.request.path cpg.request.originalParamMap = cpg.request.paramMap cpg.request.originalParamList = cpg.request.paramList path = cpg.request.path if path.startswith('/'): # Remove leading slash path = path[1:] if path.endswith('/'): # Remove trailing slash path = path[:-1] path = urllib.unquote(path) # Replace quoted chars (eg %20) from url # Handle static directories for urlDir, fsDir in cpg.configOption.staticContentList: if path == urlDir or path[:len(urlDir)+1]==urlDir+'/': cpg.request.isStatic = 1 fname = fsDir + path[len(urlDir):] start_url_var = cpg.request.browserUrl.find('?') if start_url_var != -1: fname = fname + cpg.request.browserUrl[start_url_var:] try: stat = os.stat(fname) except OSError: raise cperror.NotFound modifTime = stat.st_mtime strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime)) # Check if browser sent "if-modified-since" in request header if cpg.request.headerMap.has_key('If-Modified-Since'): # Check if if-modified-since date is the same as strModifTime if cpg.request.headerMap['If-Modified-Since'] == strModifTime: cpg.response.headerMap = { 'Status': 304, 'protocolVersion': cpg.configOption.protocolVersion, 'Date': cpg.response.headerMap['Date']} cpg.response.body = [] sendResponse(wfile) return cpg.response.headerMap['Last-Modified'] = strModifTime # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory cpg.response.headerMap['Content-Length'] = stat[6] cpg.response.body = open(fname, 'rb') # Set content-type based on filename extension i = path.rfind('.') if i != -1: ext = path[i:] else: ext = "" contentType = mimetypes.types_map.get(ext, "text/plain") cpg.response.headerMap['Content-Type'] = contentType sendResponse(wfile) return # Get session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: now = time.time() # First, get sessionId from cookie try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value except: sessionId=None if sessionId: # Load session data from wherever it was stored sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId) if sessionData == None: sessionId = None else: cpg.request.sessionMap, expirationTime = sessionData # Check that is hasn't expired if now > expirationTime: # Session expired sessionId = None # Create a new sessionId if needed if not sessionId: cpg.request.sessionMap = {} sessionId = generateSessionId() cpg.request.sessionMap['_sessionId'] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/' cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1 try: func, objectPathList, virtualPathList = mapPathToObject() except IndexRedirect, inst: # For an IndexRedirect, we don't go through the regular # mechanism: we return the redirect immediately newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion'])) cpg.response.headerMap['Location'] = newUrl for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') return # Remove "root" from objectPathList and join it to get objectPath cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap)) # builds a uniform return type if not isinstance(body, types.GeneratorType): cpg.response.body = [body] else: cpg.response.body = body if cpg.response.sendResponse: sendResponse(wfile) def generateSessionId(): s = '' for i in range(50): s += random.choice(string.letters+string.digits) s += '%s'%time.time() return sha.sha(s).hexdigest() def getObjFromPath(objPathList, objCache): """ For a given objectPathList (like ['root', 'a', 'b', 'index']), return the object (or None if it doesn't exist). Also keep a cache for maximum efficiency """ # Let cpg be the first valid object. validObjects = ["cpg"] # Scan the objPathList in order from left to right for index, obj in enumerate(objPathList): # maps virtual filenames to Python identifiers (substitutes '.' for '_') obj = obj.replace('.', '_') # currentObjStr holds something like 'cpg.root.something.else' currentObjStr = ".".join(validObjects) #--------------- # Cache check #--------------- # Generate a cacheKey from the first 'index' elements of objPathList cacheKey = tuple(objPathList[:index+1]) # Is this cacheKey in the objCache? if cacheKey in objCache: # And is its value not None? if objCache[cacheKey]: # Yes, then add it to the list of validObjects validObjects.append(obj) # OK, go to the next iteration continue # Its value is None, so we stop # (This means it is not a valid object) break #----------------- # Attribute check #----------------- if getattr(eval(currentObjStr), obj, None): # obj is a valid attribute of the current object validObjects.append(obj) # Store it in the cache objCache[cacheKey] = eval(".".join(validObjects)) else: # obj is not a valid attribute # Store None in the cache objCache[cacheKey] = None # Stop, we won't process the remaining objPathList break # Return the last cached object (even if its None) return objCache[cacheKey] def mapPathToObject(path = None): # Traverse path: # for /a/b?arg=val, we'll try: # root.a.b.index -> redirect to /a/b/?arg=val # root.a.b.default(arg='val') -> redirect to /a/b/?arg=val # root.a.b(arg='val') # root.a.default('b', arg='val') # root.default('a', 'b', arg='val') # Also, we ignore trailing slashes # Also, a method has to have ".exposed = True" in order to be exposed if path is None: path = cpg.request.objectPath or cpg.request.path if path.startswith('/'): path = path[1:] # Remove leading slash if path.endswith('/'): path = path[:-1] # Remove trailing slash if not path: objectPathList = [] else: objectPathList = path.split('/') objectPathList = ['root'] + objectPathList + ['index'] # Try successive objects... (and also keep the remaining object list) objCache = {} isFirst = True isSecond = False isDefault = False foundIt = False virtualPathList = [] while objectPathList: if isFirst or isSecond: # Only try this for a.b.index() or a.b() candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True break # Couldn't find the object: pop one from the list and try "default" lastObj = objectPathList.pop() if (not isFirst) or (not path): virtualPathList.insert(0, lastObj) objectPathList.append('default') candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True isDefault = True break objectPathList.pop() # Remove "default" if isSecond: isSecond = False if isFirst: isFirst = False isSecond = True # Check results of traversal if not foundIt: raise cperror.NotFound # We didn't find anything if isFirst: # We fo... [truncated message content] |