durusmail: quixote-users: Revised module to map filesystem files into Quixote
Revised module to map filesystem files into Quixote
2002-10-05
2002-10-07
2002-10-07
Revised module to map filesystem files into Quixote
Hamish Lawson
2002-10-05
Here is a revised version of my module for mapping filesystem files into
Quixote. New in this version is the ability to wrap CGI scripts. Also, for
better performance (but at the cost of memory) the module can now
optionally cache the contents of static files or the compiled code for CGI
scripts. I've also documented the module more fully.


Hamish Lawson


=== filesystem.py =============================================================

"""
SUMMARY

This module provides these classes for mapping filesystem files into Quixote:

     FilesystemFile

         Wraps a static filesystem file as a Quixore resource.

     FilesystemFolder

         Wraps a filesystem folder containing static files as a Quixote
         namespace.

     CGIScript

         Wraps a Python CGI script as a Quixote resource.


EXAMPLES

1. Mapping an individual filesystem file. (Because 'stylesheet.css' isn't a
valid identifier name, we need to use setattr to set it as an attribute of
the current module.)

     filepath = ("/htdocs/legacy_app/stylesheet.css")
     setattr(sys.modules[__name__], "stylesheet.css", FilesystemFile(filepath))

2. Mapping a complete filesytem folder containing static files, but not
caching their contents.

     notes = FilesystemFolder("/htdocs/legacy_app/notes", use_cache=0)

3. Mapping a CGI script.

     filepath = CGIScript("/htdocs/legacy_app/results.cgi"
     setattr(sys.modules[__name__], "results.cgi", CGIScript(filepath))
"""

import quixote, os, sys, mimetypes, cgi, email, urllib
from cStringIO import StringIO


class FilesystemFile:

     """
     Wrapper for a static file on the filesystem.

     An instance is initialized with the path to the file and optionally a flag
     indicating whether the file's content should be cached. The instance can
     then be called with a request, so behaving like any Quixote object that
     models a resource.
     """

     def __init__(self, path, use_cache=1):
         self.path = path
         self.use_cache = use_cache
         self.cache = None
         # Decide the Content-Type of the file
         self.mimetype = \
                 mimetypes.guess_type(os.path.basename(path), strict=0)[0] \
                 or 'text/plain'

     def __call__(self, request):
         # Set the Content-Type for the response and return the file's
contents;
         # use caching if enabled.
         request.response.set_header('Content-Type', self.mimetype)
         if self.cache:
             contents = self.cache
         else:
             fsfile = open(self.path)
             contents = fsfile.read()
             fsfile.close()
             if self.use_cache:
                 self.cache = contents
         return contents


class FilesystemFolder:

     """
     Wrap a filesystem folder containing static files as a Quixote namespace.

     An instance is initialized with the path to the folder and optionally
a flag
     indicating whether items within the folder should be cached.
     """

     _q_exports = []

     def __init__(self, path, use_cache=1):
         self.path = path
         self.use_cache = use_cache
         self.cache = {}

     def _q_index(self, request):
         """
         Generate a simple HTML listing of the folder's contents with each item
         hyperlinked. If the item is a folder, place a '/' after it.
         """
         template = '%s%s'
         out = StringIO()
         print >>out, "

%s

" % request.environ['REQUEST_URI'] print >>out, "
"
         print >>out, template % ('..', '..', '')
         for filename in os.listdir(self.path):
             filepath = os.path.join(self.path, filename)
             marker = os.path.isdir(filepath) and "/" or ""
             print >>out, template % (urllib.quote(filename), filename, marker)
         print >>out, "
" return out.getvalue() def _q_getname(self, request, name): """ Get a file from the filesystem folder and return the FilesystemFile or FilesystemFolder wrapper of it; use caching if that is in use. """ if name in ('.', '..'): raise quixote.errors.TraversalError(name) if self.cache.has_key(name): # Get item from cache item = self.cache[name] else: # Get item from filesystem; cache it if caching is in use item_filepath = os.path.join(self.path, name) if os.path.isdir(item_filepath): item = FilesystemFolder(item_filepath, self.use_cache) elif os.path.isfile(item_filepath): item = FilesystemFile(item_filepath, self.use_cache) else: raise quixote.errors.TraversalError(name + "***") if self.use_cache: self.cache[name] = item if isinstance(item, FilesystemFolder): return item else: return item(request) class SimulatedCGIStandardInput: """ Provides a simulated stdin to CGI scripts. The data is obtained from the request.form object already created by Quixote. """ def __init__(self, request): self.request = request def read(self, length): if self.request.environ['REQUEST_METHOD'] == 'POST': return urllib.urlencode(self.request.form, doseq=1) else: return None class CGIScript: """ Wraps a Python CGI script as a Quixote resource. """ def __init__(self, filepath, use_cache=1): self.filepath = filepath self.folder, self.filename = os.path.split(filepath) self.use_cache = use_cache self.cache = None def __call__(self, request): # If the compiled script is cached, get it from there. Otherwise # read the script file and compile it; if caching is being used, # cache the compiled code. if self.cache: code = self.cache else: scriptfile = open(self.filepath) code = compile(scriptfile.read(), self.filepath, 'exec') scriptfile.close() if self.use_cache: self.cache = code # Set up the context a conventional CGI script may expect. # # If the request is a POST, Quixote will already have consumed stdin, # so we provide the CGI script with a simulated stdin that uses # the form object created by Quixote. # # We capture the script's stdout in order to return it to Quixote. # # We update os.environ to cater for the fact that a CGI script will # look for HTTP/CGI environment variables there, but Quixote stores # them in request.environ. # # We provide for two assumptions that a Python CGI script might make # about directories. First, in a conventional CGI context the web # server would set the current directory to the CGI script's location. # Second, this directory would be at the start of Python's module # search path, due to the fact that a new Python interpreter would # be started up to run the script. original_stdin = sys.stdin original_stdout = sys.stdout sys.stdin = SimulatedCGIStandardInput(request) sys.stdout = StringIO() os.environ.update(request.environ) original_cwd = os.getcwd() os.chdir(self.folder) original_sys_path = sys.path sys.path.insert(0, self.folder) # Execute the compiled CGI script and collect its output as a MIME # message (but parse only the headers). exec code parser = email.Parser.HeaderParser() mime_message = parser.parsestr(sys.stdout.getvalue()) # Restore the context that was in effect before running the script. sys.stdout = original_stdout sys.stdin = original_stdin sys.path = original_sys_path os.chdir(original_cwd) # Copy the generated headers to Quixote's response and return the body. for header, value in mime_message.items(): request.response.set_header(header, value) return str(mime_message.get_payload())
reply