1 """WSGI handler for serving chained WSGI modules from physical files
3 The WSGI handler in this module ensures that the SCRIPT_FILENAME
4 variable is properly set in every request and points out a file that
5 exists and is readable. It then dispatches the request in one of two
6 ways: If the header X-Ash-Python-Handler is set in the request, its
7 value is used as the name of a handler object to dispatch the request
8 to; otherwise, the file extension of the SCRIPT_FILENAME is used to
9 determine the handler object.
11 The name of a handler object is specified as a string, which is split
12 along its last constituent dot. The part left of the dot is the name
13 of a module, which is imported; and the part right of the dot is the
14 name of an object in that module, which should be a callable adhering
15 to the WSGI specification. Alternatively, the module part may be
16 omitted (such that the name is a string with no dots), in which case
17 the handler object is looked up from this module.
19 By default, this module will handle files with the extensions `.wsgi'
20 or `.wsgi2' using the `chain' handler, which chainloads such files and
21 runs them as independent WSGI applications. See its documentation for
24 This module itself contains both an `application' and a `wmain'
25 object. If this module is used by ashd-wsgi(1) or scgi-wsgi(1) so that
26 its wmain function is called, arguments can be specified to it to
27 install handlers for other file extensions. Such arguments take the
28 form `.EXT=HANDLER', where EXT is the file extension to be handled,
29 and HANDLER is a handler name, as described above. For example, the
30 argument `.fpy=my.module.foohandler' can be given to pass requests for
31 `.fpy' files to the function `foohandler' in the module `my.module'
32 (which must, of course, be importable). When writing such handler
33 functions, you may want to use the getmod() function in this module.
36 import os, threading, types, getopt
39 __all__ = ["application", "wmain", "getmod", "cachedmod", "chain"]
41 class cachedmod(object):
42 """Cache entry for modules loaded by getmod()
44 Instances of this class are returned by the getmod()
45 function. They contain three data attributes:
46 * mod - The loaded module
47 * lock - A threading.Lock object, which can be used for
48 manipulating this instance in a thread-safe manner
49 * mtime - The time the file was last modified
51 Additional data attributes can be arbitrarily added for recording
52 any meta-data about the module.
54 def __init__(self, mod = None, mtime = -1):
55 self.lock = threading.Lock()
60 cachelock = threading.Lock()
72 """Load the given file as a module, caching it appropriately
74 The given file is loaded and compiled into a Python module. The
75 compiled module is cached and returned upon subsequent requests
76 for the same file, unless the file has changed (as determined by
77 its mtime), in which case the cached module is discarded and the
78 new file contents are reloaded in its place.
80 The return value is an instance of the cachedmod class, which can
81 be used for locking purposes and for storing arbitrary meta-data
82 about the module. See its documentation for details.
88 entry = modcache[path]
90 entry = [threading.Lock(), None]
91 modcache[path] = entry
96 if entry[1] is None or sb.st_mtime > entry[1].mtime:
102 code = compile(text, path, "exec")
103 mod = types.ModuleType(mangle(path))
105 exec code in mod.__dict__
106 entry[1] = cachedmod(mod, sb.st_mtime)
111 class handler(object):
113 self.lock = threading.Lock()
116 self.addext("wsgi", "chain")
117 self.addext("wsgi2", "chain")
119 def resolve(self, name):
122 if name in self.handlers:
123 return self.handlers[name]
126 return globals()[name]
129 mod = __import__(mname, fromlist = ["dummy"])
130 ret = getattr(mod, hname)
131 self.handlers[name] = ret
136 def addext(self, ext, handler):
137 self.exts[ext] = self.resolve(handler)
139 def handle(self, env, startreq):
140 if not "SCRIPT_FILENAME" in env:
141 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
142 path = env["SCRIPT_FILENAME"]
143 if not os.access(path, os.R_OK):
144 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
145 if "HTTP_X_ASH_PYTHON_HANDLER" in env:
146 handler = self.resolve(env["HTTP_X_ASH_PYTHON_HANDLER"])
148 base = os.path.basename(path)
151 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
153 if not ext in self.exts:
154 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "The server is erroneously configured.")
155 handler = self.exts[ext]
156 return handler(env, startreq)
159 """Main function for ashd(7)-compatible WSGI handlers
161 Returns the `application' function. If any arguments are given,
162 they are parsed according to the module documentation.
167 opts, args = getopt.getopt(argv, "V")
170 import wsgiref.validate
171 ret = wsgiref.validate.validator(ret)
176 hnd.addext(arg[1:p], arg[p + 1:])
179 def chain(env, startreq):
180 """Chain-loading WSGI handler
182 This handler loads requested files, compiles them and loads them
183 into their own modules. The compiled modules are cached and reused
184 until the file is modified, in which case the previous module is
185 discarded and the new file contents are loaded into a new module
186 in its place. When chaining such modules, an object named `wmain'
187 is first looked for and called with no arguments if found. The
188 object it returns is then used as the WSGI application object for
189 that module, which is reused until the module is reloaded. If
190 `wmain' is not found, an object named `application' is looked for
191 instead. If found, it is used directly as the WSGI application
194 path = env["SCRIPT_FILENAME"]
200 if hasattr(mod, "entry"):
203 if hasattr(mod.mod, "wmain"):
204 entry = mod.mod.wmain()
205 elif hasattr(mod.mod, "application"):
206 entry = mod.mod.application
210 if entry is not None:
211 return entry(env, startreq)
212 return wsgiutil.simpleerror(env, startreq, 500, "Internal Error", "Invalid WSGI handler.")
214 application = handler().handle