Anonymous avatar Anonymous committed 0740b6c

Comments (0)

Files changed (3)

 weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
 monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
 
+class IndexRedirect(Exception): pass
+
 def parseFirstLine(data):
     cpg.request.path = data.split()[1]
-    if  cpg.request.path[0] == '/':
-        cpg.request.path = cpg.request.path[1:]
+    cpg.request.queryString = ""
     cpg.request.browserUrl = cpg.request.path
     cpg.request.paramMap = {}
     cpg.request.filenameMap = {}
                             cpg.request.paramMap[key] = [cpg.request.paramMap[key], value]
                     else:
                         cpg.request.paramMap[key] = value
+        cpg.request.queryString = cpg.request.path[i+1:]
         cpg.request.path = cpg.request.path[:i]
 
-    if cpg.request.path and cpg.request.path[-1] == '/':
-        cpg.request.path = cpg.request.path[:-1] # Remove trailing '/' if any
-
 def parsePostData(rfile):
     # Read request body and put it in data
     len = int(cpg.request.headerMap.get("Content-Length","0"))
         cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/'
         cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1
 
-    path = cpg.request.path
-
-    # Traverse path:
-    # for /a/b?arg=val, we'll try:
-    #   root.a.b.index(arg='val')
-    #   root.a.b(arg='val')
-    #   root.a.b.default(arg='val')
-    #   root.a.default('b', arg='val')
-    #   root.default('a', 'b', arg='val')
-
-    # Also, we ignore trailing slashes
-
-    # Also, a method has to have ".exposed = True" in order to be exposed
-
-    if not path:
-        pathList = []
-    else:
-        pathList = path.split('/')
-
-    pathList = ['root'] + pathList
-
-    # try root.a.b, then root.a, then root
-    func = None
-
-    obj = cpg
-    previousObj = None
-    objList = []
-    searchedPathList = []
-    myPath = ''
-    bestKnownDefaultMethod = None
-    # Successively get objects from the path: 'root', then 'a' then 'b'
-    for pathItem in pathList:
-        previousObj = obj
-        try:
-            # find contained object
-            obj = getattr(obj, pathItem)
-
-            # add object
-            objList.append(obj)
-            searchedPathList.append(pathItem)
-
-            # if found object has a default method, remember it for later
-            default = getattr(obj, 'default', None)
-            if default:
-                # TODO: check if default is callable?
-                bestKnownDefaultMethod = default
-                bestKnownDefaultMethodMyPath = '/'.join(searchedPathList[1:])
-
-        except AttributeError:
-            break
-
-    # TODO: the following code could probably be KISSed somewhat.
-
-    if len(objList) == len(pathList):
-        # root_a_b exists
-        root_a_b = objList[-1]
-
-        # Try root.a.b.index()
-        root_a_b_dot_index = getattr(root_a_b, 'index', None)
-        if root_a_b_dot_index and getattr(root_a_b_dot_index, 'exposed', None):
-            myPath = '/'.join(pathList[1:])
-            func = root_a_b_dot_index
-        else:
-            # Try root.a.b.default()
-            root_a_b_dot_default = getattr(root_a_b, 'default', None)
-            if root_a_b_dot_default and getattr(root_a_b_dot_default, 'exposed', None):
-                # XXX: I don't think this ever gets called?
-                myPath = 'root_a_b_dot_default'
-                func = root_a_b_dot_default
-            elif callable(root_a_b) and getattr(root_a_b, 'exposed', None):
-                # We use root.a.b()
-                myPath = '/'.join(pathList[1:-1])
-                func = root_a_b
-
-    if func == None:
-        # None of these exist: use the default method we found earlier
-        if bestKnownDefaultMethod:
-            func = bestKnownDefaultMethod
-            myPath = bestKnownDefaultMethodMyPath
-
-    if func == None:
-        raise cperror.NotFound
-
-    myPath += '/'
-    if len(myPath) > 1:
-        myPath = '/' + myPath
-
-    cpg.request.objectPath = myPath
-    cpg.request.virtualPath = cpg.request.path[len(myPath)-1:]
-    cpg.response.body = func(**(cpg.request.paramMap))
+    try:
+        func, objectPathList, virtualPathList = mapPathToObject()
+    except IndexRedirect, inst:
+        # For an IndexRedirect, we don't go through the regular
+        #   mechanism: we return the redirect immediately
+        newUrl = canonicalizeUrl(inst.args[0])
+        wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion']))
+        cpg.response.headerMap['Location'] = newUrl
+        for key, valueList in cpg.response.headerMap.items():
+            if key not in ('Status', 'protocolVersion'):
+                if type(valueList) != type([]): valueList = [valueList]
+                for value in valueList:
+                    wfile.write('%s: %s\r\n'%(key, value))
+        wfile.write('\r\n')
+        return
+         
+    cpg.request.objectPath = '/'.join(objectPathList)
+    cpg.response.body = func(*virtualPathList, **(cpg.request.paramMap))
 
     if cpg.response.sendResponse:
         sendResponse(wfile)
     s += '%s'%time.time()
     return sha.sha(s).hexdigest()
 
+def getObjFromPath(objPathList, objCache):
+    """ For a given objectPathList (like ['root', 'a', 'b', 'index']),
+        return the object (or None if it doesn't exist).
+        Also keep a cache for maximum efficiency
+    """
+    if not objPathList: return cpg
+    cacheKey = tuple(objPathList)
+    if cacheKey in objCache: return objCache[cacheKey]
+    previousObj = getObjFromPath(objPathList[:-1], objCache)
+    obj = getattr(previousObj, objPathList[-1], None)
+    objCache[cacheKey] = obj
+    return obj
 
+def mapPathToObject():
+    # Traverse path:
+    # for /a/b?arg=val, we'll try:
+    #   root.a.b.index -> redirect to /a/b/?arg=val
+    #   root.a.b.default(arg='val') -> redirect to /a/b/?arg=val
+    #   root.a.b(arg='val')
+    #   root.a.default('b', arg='val')
+    #   root.default('a', 'b', arg='val')
+
+    # Also, we ignore trailing slashes
+    # Also, a method has to have ".exposed = True" in order to be exposed
+
+    path = cpg.request.path
+    if path.startswith('/'): path = path[1:] # Remove leading slash
+    if path.endswith('/'): path = path[:-1] # Remove trailing slash
+
+    if not path:
+        objectPathList = []
+    else:
+        objectPathList = path.split('/')
+    objectPathList = ['root'] + objectPathList + ['index']
+
+    # Try successive objects... (and also keep the remaining object list)
+    objCache = {}
+    isFirst = True
+    isDefault = False
+    foundIt = False
+    virtualPathList = []
+    while objectPathList:
+        candidate = getObjFromPath(objectPathList, objCache)
+        if callable(candidate) and getattr(candidate, 'exposed', False):
+            foundIt = True
+            break
+        # Couldn't find the object: pop one from the list and try "default"
+        lastObj = objectPathList.pop()
+        if not isFirst:
+            virtualPathList.insert(0, lastObj)
+            objectPathList.append('default')
+            candidate = getObjFromPath(objectPathList, objCache)
+            if callable(candidate) and getattr(candidate, 'exposed', False):
+                foundIt = True
+                isDefault = True
+                break
+            objectPathList.pop() # Remove "default"
+        isFirst = False
+
+    # Check results of traversal
+    if not foundIt:
+        raise cperror.NotFound # We didn't find anything
+
+    if isFirst:
+        # We found the extra ".index"
+        # Check if the original path had a trailing slash (otherwise, do
+        #   a redirect)
+        if cpg.request.path[-1] != '/':
+            newUrl = cpg.request.path + '/'
+            if cpg.request.queryString: newUrl += cpg.request.queryString
+            raise IndexRedirect(newUrl)
+
+    return candidate, objectPathList, virtualPathList
+    
+def canonicalizeUrl(newUrl):
+    if not newUrl.startswith('http://') and not newUrl.startswith('https://'):
+        # If newUrl is not canonical, we must make it canonical
+        if newUrl[0] == '/':
+            # URL was absolute: we just add the request.base in front of it
+            newUrl = cpg.request.base + newUrl
+        else:
+            # URL was relative
+            if cpg.request.browserUrl == cpg.request.base:
+                # browserUrl is request.base
+                newUrl = cpg.request.base + '/' + newUrl
+            else:
+                newUrl = cpg.request.browserUrl[:i+1] + newUrl
+    return newUrl

tutorial/tutorial07.py

     index.exposed = True
 
 
-    def default(self):
+    def default(self, user):
         # Here we react depending on the virtualPath -- the part of the
         # path that could not be mapped to an object method. In a real
         # application, we would probably do some database lookups here
         # instead of the silly if/elif/else construct.
-        if cpg.request.virtualPath == 'remi':
+        if user == 'remi':
             out = "Remi Delon, CherryPy lead developer"
-        elif cpg.request.virtualPath == 'hendrik':
+        elif user == 'hendrik':
             out = "Hendrik Mans, CherryPy co-developer & crazy German"
-        elif cpg.request.virtualPath == 'lorenzo':
+        elif user == 'lorenzo':
             out = "Lorenzo Lamas, famous actor and singer!"
         else:
             out = "Unknown user. :-("

unittest/testObjectMapping.py

     def index(self, name="world"):
         return name
     index.exposed = True
+    def default(self, *params):
+        return "default:"+repr(params)
+    default.exposed = True
+    def other(self):
+        return "other"
+    other.exposed = True
+    def notExposed(self):
+        return "not exposed"
+class Dir1:
+    def index(self):
+        return "index for dir1"
+    index.exposed = True
+    def default(self, *params):
+        return repr(params)
+    default.exposed = True
+class Dir2:
+    def index(self):
+        return "index for dir2, path is:" + cpg.request.path
+    index.exposed = True
+    def method(self):
+        return "method for dir2"
+    method.exposed = True
 cpg.root = Root()
+cpg.root.dir1 = Dir1()
+cpg.root.dir1.dir2 = Dir2()
 cpg.server.start(configFile = 'testsite.cfg')
 """
 config = ""
-expectedResult = ""
+testList = [
+    ("", "world"),
+    ("/this/method/does/not/exist", "default:('this', 'method', 'does', 'not', 'exist')"),
+    ("/other", "other"),
+    ("/notExposed", "default:('notExposed',)"),
+    ("/dir1/dir2/", "index for dir2, path is:/dir1/dir2/"),
+]
+urlList = [test[0] for test in testList]
+expectedResultList = [test[1] for test in testList]
 
 def test(infoMap, failedList, skippedList):
     print "    Testing object mapping...",
-    helper.checkPageResult('Object mapping', infoMap, code, config, [""], ["world"], failedList)
+    helper.checkPageResult('Object mapping', infoMap, code, config, urlList, expectedResultList, failedList)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.