diff --git a/lib/python/Testing/ZopeTestCase/functional.py b/lib/python/Testing/ZopeTestCase/functional.py
index 08f311b00f67d5988a9ddb1eba401c18d60d8d84..9cde7c2844a2fcfb94e9787e9978fce241cdd37d 100644
--- a/lib/python/Testing/ZopeTestCase/functional.py
+++ b/lib/python/Testing/ZopeTestCase/functional.py
@@ -23,6 +23,25 @@ import sandbox
 import interfaces
 
 
+def savestate(func):
+    '''Decorator saving thread local state before executing func
+       and restoring it afterwards.
+    '''
+    from AccessControl.SecurityManagement import getSecurityManager
+    from AccessControl.SecurityManagement import setSecurityManager
+    from zope.app.component.hooks import getSite
+    from zope.app.component.hooks import setSite
+
+    def wrapped_func(*args, **kw):
+        sm, site = getSecurityManager(), getSite()
+        try:
+            return func(*args, **kw)
+        finally:
+            setSecurityManager(sm)
+            setSite(site)
+    return wrapped_func
+
+
 class Functional(sandbox.Sandboxed):
     '''Derive from this class and an xTestCase to get functional
        testing support::
@@ -33,25 +52,15 @@ class Functional(sandbox.Sandboxed):
 
     __implements__ = (interfaces.IFunctional,)
 
+    @savestate
     def publish(self, path, basic=None, env=None, extra=None,
                 request_method='GET', stdin=None, handle_errors=True):
         '''Publishes the object at 'path' returning a response object.'''
 
-        from zope.app.component.hooks import setSite, getSite
         from StringIO import StringIO
         from ZPublisher.Response import Response
         from ZPublisher.Test import publish_module
 
-        from AccessControl.SecurityManagement import getSecurityManager
-        from AccessControl.SecurityManagement import setSecurityManager
-
-        # Save current security manager
-        sm = getSecurityManager()
-
-        # And we need to store the old site
-        old_site = getSite()
-        setSite(None)
-
         # Commit the sandbox for good measure
         transaction.commit()
 
@@ -91,12 +100,6 @@ class Functional(sandbox.Sandboxed):
                        debug=not handle_errors,
                       )
 
-        # Restore security manager
-        setSecurityManager(sm)
-
-        # And we need to restore the site again
-        setSite(old_site)
-
         return ResponseWrapper(response, outstream, path)
 
 
diff --git a/lib/python/Testing/ZopeTestCase/zopedoctest/functional.py b/lib/python/Testing/ZopeTestCase/zopedoctest/functional.py
index 98f3a814332216108956b0941fc96941051c6980..be219d8ee0c35dcc220064c8aa457e74c684bdb2 100644
--- a/lib/python/Testing/ZopeTestCase/zopedoctest/functional.py
+++ b/lib/python/Testing/ZopeTestCase/zopedoctest/functional.py
@@ -31,6 +31,7 @@ from Testing.ZopeTestCase import user_role
 from Testing.ZopeTestCase import standard_permissions
 from Testing.ZopeTestCase.sandbox import AppZapper
 from Testing.ZopeTestCase.functional import ResponseWrapper
+from Testing.ZopeTestCase.functional import savestate
 
 
 class HTTPHeaderOutput:
@@ -110,6 +111,7 @@ def sync():
     getRootFolder()._p_jar.sync()
 
 
+@savestate
 def http(request_string, handle_errors=True):
     """Execute an HTTP request string via the publisher
 
@@ -117,19 +119,9 @@ def http(request_string, handle_errors=True):
     """
     import urllib
     import rfc822
-    from zope.app.component.hooks import setSite, getSite
     from cStringIO import StringIO
     from ZPublisher.Response import Response
     from ZPublisher.Test import publish_module
-    from AccessControl.SecurityManagement import getSecurityManager
-    from AccessControl.SecurityManagement import setSecurityManager
-
-    # Save current Security Manager
-    old_sm = getSecurityManager()
-
-    # And we need to store the old site
-    old_site = getSite()
-    setSite(None)
 
     # Commit work done by previous python code.
     transaction.commit()
@@ -194,14 +186,6 @@ def http(request_string, handle_errors=True):
     header_output.appendResponseHeaders(response._cookie_list())
     header_output.appendResponseHeaders(response.accumulated_headers.splitlines())
 
-    # Restore previous security manager, which may have been changed
-    # by calling the publish method above
-    setSecurityManager(old_sm)
-
-    # And we need to restore the site again
-    setSite(old_site)
-    # Sync connection
-
     sync()
 
     return DocResponseWrapper(response, outstream, path, header_output)