python: Cleaned up dispatching in wsgidir.
[ashd.git] / python / ashd / wsgidir.py
index 6080d91..77f41fb 100644 (file)
@@ -27,15 +27,14 @@ form `.EXT=MODULE.HANDLER', where EXT is the file extension to be
 handled, and the MODULE.HANDLER string is treated by splitting it
 along its last constituent dot. The part left of the dot is the name
 of a module which is imported, and the part right of the dot is the
-name of an object in that module, which should be a callable of three
-arguments. When files of the given extension are handled, that
-callable is called with the file's absolute path, the WSGI environment
-and the WSGI `start_response' function, in that order. For example,
-the argument `.fpy=my.module.foohandler' can be given to pass requests
-for `.fpy' files to the function `foohandler' in the module
-`my.module' (which must, of course, be importable). When writing such
-handler functions, you will probably want to use the getmod() function
-in this module.
+name of an object in that module, which should be a callable adhering
+to the WSGI specification. When called, this module will have made
+sure that the WSGI environment contains the SCRIPT_FILENAME parameter
+and that it is properly working. For example, the argument
+`.fpy=my.module.foohandler' can be given to pass requests for `.fpy'
+files to the function `foohandler' in the module `my.module' (which
+must, of course, be importable). When writing such handler functions,
+you will probably want to use the getmod() function in this module.
 """
 
 import os, threading, types
@@ -56,12 +55,11 @@ class cachedmod(object):
     Additional data attributes can be arbitrarily added for recording
     any meta-data about the module.
     """
-    def __init__(self, mod, mtime):
+    def __init__(self, mod = None, mtime = -1):
         self.lock = threading.Lock()
         self.mod = mod
         self.mtime = mtime
 
-exts = {}
 modcache = {}
 cachelock = threading.Lock()
 
@@ -92,25 +90,85 @@ def getmod(path):
     try:
         if path in modcache:
             entry = modcache[path]
-            if sb.st_mtime <= entry.mtime:
-                return entry
-        
-        f = open(path)
-        try:
-            text = f.read()
-        finally:
-            f.close()
-        code = compile(text, path, "exec")
-        mod = types.ModuleType(mangle(path))
-        mod.__file__ = path
-        exec code in mod.__dict__
-        entry = cachedmod(mod, sb.st_mtime)
-        modcache[path] = entry
-        return entry
+        else:
+            entry = cachedmod()
+            modcache[path] = entry
     finally:
         cachelock.release()
+    entry.lock.acquire()
+    try:
+        if entry.mod is None or sb.st_mtime > entry.mtime:
+            f = open(path, "r")
+            try:
+                text = f.read()
+            finally:
+                f.close()
+            code = compile(text, path, "exec")
+            mod = types.ModuleType(mangle(path))
+            mod.__file__ = path
+            exec code in mod.__dict__
+            entry.mod = mod
+            entry.mtime = sb.st_mtime
+        return entry
+    finally:
+        entry.lock.release()
+
+class handler(object):
+    def __init__(self):
+        self.lock = threading.Lock()
+        self.handlers = {}
+        self.exts = {}
+        self.addext("wsgi", "chain")
+        self.addext("wsgi2", "chain")
 
-def chain(path, env, startreq):
+    def resolve(self, name):
+        self.lock.acquire()
+        try:
+            if name in self.handlers:
+                return self.handlers[name]
+            p = name.rfind('.')
+            if p < 0:
+                return globals()[name]
+            mname = name[:p]
+            hname = name[p + 1:]
+            mod = __import__(mname, fromlist = ["dummy"])
+            ret = getattr(mod, hname)
+            self.handlers[name] = ret
+            return ret
+        finally:
+            self.lock.release()
+        
+    def addext(self, ext, handler):
+        self.exts[ext] = self.resolve(handler)
+
+    def handle(self, env, startreq):
+        if not "SCRIPT_FILENAME" in env:
+            return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
+        path = env["SCRIPT_FILENAME"]
+        base = os.path.basename(path)
+        p = base.rfind('.')
+        if p < 0 or not os.access(path, os.R_OK):
+            return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
+        ext = base[p + 1:]
+        if not ext in self.exts:
+            return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
+        return(self.exts[ext](env, startreq))
+
+def wmain(*argv):
+    """Main function for ashd(7)-compatible WSGI handlers
+
+    Returns the `application' function. If any arguments are given,
+    they are parsed according to the module documentation.
+    """
+    ret = handler()
+    for arg in argv:
+        if arg[0] == '.':
+            p = arg.index('=')
+            ret.addext(arg[1:p], arg[p + 1:])
+    return ret.handle
+
+def chain(env, startreq):
+    path = env["SCRIPT_FILENAME"]
     mod = getmod(path)
     entry = None
     if mod is not None:
@@ -129,40 +187,5 @@ def chain(path, env, startreq):
     if entry is not None:
         return entry(env, startreq)
     return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Invalid WSGI handler.")
-exts["wsgi"] = chain
-
-def addext(ext, handler):
-    p = handler.rindex('.')
-    mname = handler[:p]
-    hname = handler[p + 1:]
-    mod = __import__(mname, fromlist = ["dummy"])
-    exts[ext] = getattr(mod, hname)
-
-def application(env, startreq):
-    """WSGI handler function
-
-    Handles WSGI requests as per the module documentation.
-    """
-    if not "SCRIPT_FILENAME" in env:
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    path = env["SCRIPT_FILENAME"]
-    base = os.path.basename(path)
-    p = base.rfind('.')
-    if p < 0 or not os.access(path, os.R_OK):
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    ext = base[p + 1:]
-    if not ext in exts:
-        return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
-    return(exts[ext](path, env, startreq))
 
-def wmain(*argv):
-    """Main function for ashd(7)-compatible WSGI handlers
-
-    Returns the `application' function. If any arguments are given,
-    they are parsed according to the module documentation.
-    """
-    for arg in argv:
-        if arg[0] == '.':
-            p = arg.index('=')
-            addext(arg[1:p], arg[p + 1:])
-    return application
+application = handler().handle