Commits

Robert Brewer  committed 92dcea8

Fix for #309 (confusion between threads and requests).

  • Participants
  • Parent commits 9b6e897

Comments (0)

Files changed (9)

File cherrypy/__init__.py

 except ImportError:
     from cherrypy._cpthreadinglocal import local
 
+# Create a threadlocal object to hold the request and response objects.
+# In this way, we can easily dump those objects when we stop/start a
+# new HTTP conversation.
+serving = local()
+
+class _AttributeDump:
+    pass
+
+class _ThreadLocalProxy:
+    
+    def __init__(self, attrname):
+        self.__dict__["__attrname__"] = attrname
+    
+    def purge__(self):
+        """Make a new, emtpy proxied object in cherrypy.serving."""
+        setattr(serving, self.__attrname__, _AttributeDump())
+    
+    def __getattr__(self, name):
+        childobject = getattr(serving, self.__attrname__)
+        return getattr(childobject, name)
+    
+    def __setattr__(self, name, value):
+        childobject = getattr(serving, self.__attrname__)
+        setattr(childobject, name, value)
+    
+    def __delattr__(self, name):
+        childobject = getattr(serving, self.__attrname__)
+        delattr(childobject, name)
+
 # Create request and response object (the same objects will be used
-#   throughout the entire life of the webserver)
-request = local()
-response = local()
+#   throughout the entire life of the webserver, but will redirect
+#   to the "serving" object)
+request = _ThreadLocalProxy('request')
+response = _ThreadLocalProxy('response')
 
 # Create threadData object as a thread-specific all-purpose storage
 threadData = local()

File cherrypy/_cphttpserver.py

         if not self.parse_request(): # An error code has been sent, just exit
             return
         
+        cherrypy.request.purge__()
+        cherrypy.response.purge__()
+        
         cherrypy.request.multithread = cherrypy.config.get("server.threadPool") > 1
         cherrypy.request.multiprocess = False
         cherrypy.server.request(self.client_address,

File cherrypy/_cpwsgi.py

         sys.stderr = NullWriter()
     
     try:
+        cherrypy.request.purge__()
+        cherrypy.response.purge__()
+        
         # LOGON_USER is served by IIS, and is the name of the
         # user after having been mapped to a local account.
         # Both IIS and Apache set REMOTE_USER, when possible.

File cherrypy/lib/filter/cachefilter.py

         #   circular module imports :-(
         global cherrypy
         import cherrypy
-        cherrypy.threadData.cacheable = True
+        cherrypy.request.cacheable = True
     
     def beforeMain(self):
         if not cherrypy.config.get('cacheFilter.on', False):
                 self.maxobjsize, self.maxsize, self.maxobjects)
         
         cacheData = cherrypy._cache.get()
-        cherrypy.threadData.cacheable = not cacheData
+        cherrypy.request.cacheable = not cacheData
         if cacheData:
             expirationTime, lastModified, obj = cacheData
             # found a hit! check the if-modified-since request header
         if not cherrypy.config.get('cacheFilter.on', False):
             return
         
-        if cherrypy.threadData.cacheable:
+        if cherrypy.request.cacheable:
             status = cherrypy.response.status
             headers = cherrypy.response.headers
             

File cherrypy/lib/filter/sessionauthenticatefilter.py

             return
         elif cherrypy.request.path.endswith('doLogout'):
             cherrypy.session[sessionKey] = None
-            cherrypy.threadData.user = None
+            cherrypy.request.user = None
             fromPage = cherrypy.request.paramMap.get('fromPage', '..')
             cherrypy.response.body = httptools.redirect(fromPage)
         elif cherrypy.request.path.endswith('doLogin'):
         # Everything is OK: user is logged in
         if loadUserByUsername:
             username = cherrypy.session[sessionKey]
-            cherrypy.threadData.user = loadUserByUsername(username)
+            cherrypy.request.user = loadUserByUsername(username)

File cherrypy/lib/filter/sessionfilter.py

 """
 
 """ Session implementation for CherryPy.
-We use cherrypy.threadData to store some convenient variables as
+We use cherrypy.request to store some convenient variables as
 well as data about the session for the current request. Instead of
-polluting cherrypy.threadData we use a dummy object called
-cherrypy.threadData._session (sess) to store these variables.
+polluting cherrypy.request we use a dummy object called
+cherrypy.request._session (sess) to store these variables.
 
 Variables used to store config options:
     - sess.sessionTimeout: timeout delay for the session
         import cherrypy
         conf = cherrypy.config.get
         
-        cherrypy.threadData._session = EmptyClass()
-        sess = cherrypy.threadData._session
+        cherrypy.request._session = EmptyClass()
+        sess = cherrypy.request._session
         now = datetime.datetime.now()
         # Dont enable session if sessionFilter is off or if this is a
         #   request for static data
                 raise
             self._clean(sess)
         
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         if not sess.sessionStorage:
             # Sessions are not enabled: do nothing
             return
         cherrypy.response.body = saveData(cherrypy.response.body, sess)
     
     def afterErrorResponse(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         if not sess.sessionStorage:
             # Sessions are not enabled: do nothing
             return
         cherrypy._sessionDataHolder[id] = (data, expirationTime)
     
     def acquireLock(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         id = cherrypy.session['_id']
         lock = cherrypy._sessionLockDict.get(id)
         if lock is None:
         sess.locked = True
     
     def releaseLock(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         id = cherrypy.session['_id']
         cherrypy._sessionLockDict[id].release()
         sess.locked = False
     
     def cleanUp(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         toBeDeleted = []
         now = datetime.datetime.now()
         for id, (data, expirationTime) in cherrypy._sessionDataHolder.iteritems():
         f.close()
     
     def acquireLock(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         filePath = self._getFilePath(cherrypy.session['_id'])
         lockFilePath = filePath + self.LOCK_SUFFIX
         self._lockFile(lockFilePath)
         sess.locked = True
     
     def releaseLock(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         filePath = self._getFilePath(cherrypy.session['_id'])
         lockFilePath = filePath + self.LOCK_SUFFIX
         self._unlockFile(lockFilePath)
         sess.locked = False
     
     def cleanUp(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         storagePath = cherrypy.config.get('sessionFilter.storagePath')
         now = datetime.datetime.now()
         # Iterate over all files in the dir/ and exclude non session files
         return filePath
     
     def _lockFile(self, path):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         startTime = time.time()
         while True:
             try:
         self.cursor = None
     
     def cleanUp(self):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         now = datetime.datetime.now()
         self.cursor.execute(
             'select data from session where expiration_time < %s',
 # Users access sessions through cherrypy.session, but we want this
 #   to be thread-specific so we use a special wrapper that forwards
 #   calls to cherrypy.session to a thread-specific dictionary called
-#   cherrypy.threadData._session.sessionData
+#   cherrypy.request._session.sessionData
 class SessionWrapper:
     
     def __getattr__(self, name):
-        sess = cherrypy.threadData._session
+        sess = cherrypy.request._session
         if sess.sessionStorage is None:
             raise SessionNotEnabledError()
         # Create thread-specific dictionary if needed

File cherrypy/lib/filter/xmlrpcfilter.py

     
     def beforeRequestBody(self):
         """ Called after the request header has been read/parsed"""
-        cherrypy.threadData.xmlRpcFilterOn = cherrypy.config.get('xmlRpcFilter.on', False)
-        if not cherrypy.threadData.xmlRpcFilterOn:
+        cherrypy.request.xmlRpcFilterOn = cherrypy.config.get('xmlRpcFilter.on', False)
+        if not cherrypy.request.xmlRpcFilterOn:
             return True
         
         cherrypy.request.isRPC = self.testValidityOfRequest()
     
     def beforeFinalize(self):
         """ Called before finalizing output """
-        if (not cherrypy.threadData.xmlRpcFilterOn
+        if (not cherrypy.request.xmlRpcFilterOn
             or not cherrypy.request.isRPC):
             return
 
     
     def beforeErrorResponse(self):
         try:
-            if not cherrypy.threadData.xmlRpcFilterOn:
+            if not cherrypy.request.xmlRpcFilterOn:
                 return
             body = ''.join([chunk for chunk in cherrypy.response.body])
             cherrypy.response.body = [xmlrpclib.dumps(xmlrpclib.Fault(1, body))]

File cherrypy/test/helper.py

         if body is not None:
             body = StringIO.StringIO(body)
         
+        cherrypy.request.purge__()
+        cherrypy.response.purge__()
+        
         cherrypy.server.request((self.HOST, self.PORT), self.HOST,
                                 requestLine, headers, body, "http")
         

File cherrypy/test/test_core.py

     def upload(self, file):
         return "Size: %s" % len(file.file.read())
 
+class ThreadLocal(Test):
+    
+    def index(self):
+        existing = repr(getattr(cherrypy.request, "asdf", None))
+        cherrypy.request.asdf = "hello"
+        return existing
+
+
 logFile = os.path.join(localDir, "error.log")
 logAccessFile = os.path.join(localDir, "access.log")
 
             ('/foo/bar', 'baz', 'that2'),
             ('/foo/nex', 'baz', 'that2'),
         ]
+        cherrypy.request.purge__()
         for path, key, expected in tests:
             cherrypy.request.path = path
             result = cherrypy.config.get(key, None)
             self.getPage('/maxrequestsize/upload', h, "POST", b)
             self.assertStatus("413 Request Entity Too Large")
             self.assertInBody("Request Entity Too Large")
+    
+    def testEmptyThreadlocals(self):
+        results = []
+        for x in xrange(20):
+            self.getPage("/threadlocal/")
+            results.append(self.body)
+        self.assertEqual(results, ["None"] * 20)
+
 
 if __name__ == '__main__':
     helper.testmain()