""" This sample web server demonstrates how to implement a web service for Dynatree requests. As data source, we choose a folder in the local file system, simply because it is hierarchical and has a concept of documents (files) and folders (directories) and because it was easy to implement. A typical web service would of course read the data from a 'real' source like an SQL database or XML file, but I hope you get the idea ;-) See: http://dynatree.googlecode.com Martin Wendt, 2009-2011 Usage: 1. Python 2.5 or later is required to run this server. For Python 2.5 the simplejson module must also be installed; Python 2.6 has built in json support. 2. Configure the rootPath variable in the main() function at the bottom of this module. 3. Run this module: > python dynatree_server.py Optionally pass a root folder: > python dynatree_server.py c:\temp This module - Is a standalone web server that answers URLs beginning with http://127.0.0.1:8001/?... with JSON responses that conform to the Dynatree spec. However, we don't do error checking or anything else that would be required for production environments. - Answers requests to initialize a tree using the 'initAjax: {}' option: http://127.0.0.1:8001/?mode=baseFolders with a list of files/directories in the configured root directory. - Answers requests to lazy-load node children using 'appendAjax({...})': http://127.0.0.1:8001/?key=_25c2b6d6 with a list of files/directories in the directory that matches this key. - Supports Dynatree's 'lazy persistence': http://127.0.0.1:8001/?mode=baseFolder&expandedKeyList=_41771df2%2C_4230fb68%2C... will return not only the base entries, but also all children inside parents that are listed as expanded. - Supports &sleep=SECONDS argument for simulating slow responses. - Supports the JSONP protocol: http://127.0.0.1:8001/?mode=baseFolder&callback=jsonp1241293219729 will wrap the result like this "jsonp1241293219729()". This is only required, if this web service is not on the same host as the web page that contains the Dynatree widget. JSONP can be enabled for jQuery.ajax() by passing dataType: 'jsonp' instead of 'json'. - Dumps the POST body, if the request URL is '/submit_data' Sample Dynatree options to use this service: $("#tree").dynatree({ ... persist: true, initAjax: {url: "http://127.0.0.1:8001", dataType: "jsonp", // Enable JSONP, so this sample can be run from the local file system against a localhost server data: {key: "", // sleep: 3, // depth: 2, mode: "baseFolders" }, addExpandedKeyList: true // Send list of expanded keys, so the webservice can deliver these children also }, onLazyRead: function(dtnode){ dtnode.appendAjax( {url: "http://127.0.0.1:8001", dataType: "jsonp", data: {key: dtnode.data.key, mode: "branch" } }); } }); """ import cgi import os import sys import time from wsgiref.simple_server import WSGIServer, WSGIRequestHandler from tempfile import gettempdir try: import json # Available since Python 2.6 except ImportError: import simplejson as json #=============================================================================== # Helper functions #=============================================================================== def _keyFromString(s): """Calculate a unique key for an arbitrary string. Example: _keyFromString("c:\temp\wsgidav1\src\DAV") = "_25c2b6d6" """ return "_" + hex(hash(s)) [3:] def _findFolderByKey(rootPath, key): """Search rootPath and all sub folders for a directory that matches the key.""" for root, dirs, files in os.walk(rootPath): for name in dirs: fullPath = os.path.join(root, name) fileKey = _keyFromString(fullPath) if key == fileKey: return fullPath return None #=============================================================================== # DynaTreeWsgiApp #=============================================================================== class DynaTreeWsgiApp(object): """This WSGI application serves a file system hierarchy for dynatree.""" def __init__(self, optionDict): self.optionDict = optionDict def __call__(self, environ, start_response): """Handle one HTTP request.""" # Parse URL query into a list of 2-tuples (name, value) argList = cgi.parse_qsl(environ.get("QUERY_STRING", "")) # Convert to dictionary {"name": "value", ... } argDict = dict(argList) print "Query args: %s" % argDict # Support &sleep=SECONDS argument to simulate slow connections for debugging if argDict.get("sleep"): print "Sleeping %s seconds..." % argDict.get("sleep") time.sleep(int(argDict.get("sleep"))) # Dump POST request data, if http://HOST:PORT/submit_data was requested # print "PI", environ["PATH_INFO"] if environ["PATH_INFO"] == "/submit_data": print "Got /submit_data request, CONTENT_LENGTH=%r" % environ.get("CONTENT_LENGTH") try: length = int(environ["CONTENT_LENGTH"]) data = environ["wsgi.input"].read(length) except: print >>sys.stderr, "Couldn't read from wsgi.input! This can happen when using Firefox locally" try: data = environ["wsgi.input"].read() except: print print "Data: ", data start_response("200 OK", [("Content-Type", "text/html")]) return [ "Thanks for sending
%s
" % data ] # Support &depth=LEVEL argument to read more than one level (1: direct children) depth = int(argDict.get("depth", 0)) if depth > 1: print "'depth' mode: loading %s levels" % depth # Return empty list when '&returnEmpty' is passed returnEmpty = "returnEmpty" in argDict # Eval 'mode' and 'key' arguments rootPath = self.optionDict["rootPath"] if argDict.get("mode") == "baseFolders": folderPath = rootPath elif argDict.get("key"): key = argDict.get("key") folderPath = _findFolderByKey(rootPath, key) if not folderPath: raise RuntimeError("Could not find folder for key '%s'." % key) else: raise RuntimeError("Missing required argument '&mode=baseFolder' or '&key=...'") # Get list of child nodes (may be recursive) childList = [ ] if not returnEmpty: self.makeChildList(argDict, folderPath, childList, depth) # Convert result list to a JSON string res = json.dumps(childList, encoding="Latin-1") # Support for the JSONP protocol. if "callback" in argDict: res = argDict["callback"] + "(" + res + ")" # Return HTTP response start_response("200 OK", [("Content-Type", "application/json")]) return [ res ] def makeChildList(self, argDict, folderPath, childList, depth): print "makeChildList(%s, depth=%s) " % (folderPath, depth) expandedKeyList = argDict.get("expandedKeyList", "").split(",") filenameList = os.listdir(folderPath) for fn in filenameList: fullPath = os.path.join(folderPath, fn) isFolder = os.path.isdir(fullPath) key = _keyFromString(fullPath) try: size = os.path.getsize(fullPath) date = time.ctime(os.path.getmtime(fullPath)) except: # May fail when path contains funny chars (don't care in this sample) size = 0 date = time.ctime() # Create a node dictionary and append it to the child list node = {"title": fn, "key": key, "isFolder": isFolder, "isLazy": isFolder, "tooltip": "%s, %s bytes, modified: %s" % (fullPath, size, date), } childList.append(node) # Support lazy persistence: # If the current node was requested as 'expanded', load the children too if isFolder and (key in expandedKeyList or depth > 1): subNodes = [] self.makeChildList(argDict, fullPath, subNodes, depth-1) node["children"] = subNodes # node["isLazy"] = False # node["expand"] = True #=============================================================================== # Server #=============================================================================== # Requires Python >= 2.5 def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler): """Create a new WSGI server listening on 'host' and 'port' for 'app'.""" server = server_class((host, port), handler_class) server.set_app(app) return server def main(): # Configure root directory that will be exported: rootPath = gettempdir() if len(sys.argv) > 1: rootPath =sys.argv[1] # rootPath = "/temp" # Configure hostname and port on which the server will listen # hostname = "127.0.0.1" # Use empty string for localhost (local access only) hostname = "" # Use empty string for 0.0.0.0 (allows remote access) port = 8001 wsgi_app = DynaTreeWsgiApp({"rootPath": rootPath}) httpd = make_server(hostname, port, wsgi_app) sa = httpd.socket.getsockname() print "Exporting file system at ", rootPath, " for Dynatree." print "Serving HTTP on", sa[0], "port", sa[1], "..." assert os.path.isdir(rootPath), "Invalid root path: '%s'" % rootPath httpd.serve_forever() if __name__ == "__main__": main()