| 283 | | path = cpg.request.path |
|---|
| 284 | | |
|---|
| 285 | | # Traverse path: |
|---|
| 286 | | # for /a/b?arg=val, we'll try: |
|---|
| 287 | | # root.a.b.index(arg='val') |
|---|
| 288 | | # root.a.b(arg='val') |
|---|
| 289 | | # root.a.b.default(arg='val') |
|---|
| 290 | | # root.a.default('b', arg='val') |
|---|
| 291 | | # root.default('a', 'b', arg='val') |
|---|
| 292 | | |
|---|
| 293 | | # Also, we ignore trailing slashes |
|---|
| 294 | | |
|---|
| 295 | | # Also, a method has to have ".exposed = True" in order to be exposed |
|---|
| 296 | | |
|---|
| 297 | | if not path: |
|---|
| 298 | | pathList = [] |
|---|
| 299 | | else: |
|---|
| 300 | | pathList = path.split('/') |
|---|
| 301 | | |
|---|
| 302 | | pathList = ['root'] + pathList |
|---|
| 303 | | |
|---|
| 304 | | # try root.a.b, then root.a, then root |
|---|
| 305 | | func = None |
|---|
| 306 | | |
|---|
| 307 | | obj = cpg |
|---|
| 308 | | previousObj = None |
|---|
| 309 | | objList = [] |
|---|
| 310 | | searchedPathList = [] |
|---|
| 311 | | myPath = '' |
|---|
| 312 | | bestKnownDefaultMethod = None |
|---|
| 313 | | # Successively get objects from the path: 'root', then 'a' then 'b' |
|---|
| 314 | | for pathItem in pathList: |
|---|
| 315 | | previousObj = obj |
|---|
| 316 | | try: |
|---|
| 317 | | # find contained object |
|---|
| 318 | | obj = getattr(obj, pathItem) |
|---|
| 319 | | |
|---|
| 320 | | # add object |
|---|
| 321 | | objList.append(obj) |
|---|
| 322 | | searchedPathList.append(pathItem) |
|---|
| 323 | | |
|---|
| 324 | | # if found object has a default method, remember it for later |
|---|
| 325 | | default = getattr(obj, 'default', None) |
|---|
| 326 | | if default: |
|---|
| 327 | | # TODO: check if default is callable? |
|---|
| 328 | | bestKnownDefaultMethod = default |
|---|
| 329 | | bestKnownDefaultMethodMyPath = '/'.join(searchedPathList[1:]) |
|---|
| 330 | | |
|---|
| 331 | | except AttributeError: |
|---|
| 332 | | break |
|---|
| 333 | | |
|---|
| 334 | | # TODO: the following code could probably be KISSed somewhat. |
|---|
| 335 | | |
|---|
| 336 | | if len(objList) == len(pathList): |
|---|
| 337 | | # root_a_b exists |
|---|
| 338 | | root_a_b = objList[-1] |
|---|
| 339 | | |
|---|
| 340 | | # Try root.a.b.index() |
|---|
| 341 | | root_a_b_dot_index = getattr(root_a_b, 'index', None) |
|---|
| 342 | | if root_a_b_dot_index and getattr(root_a_b_dot_index, 'exposed', None): |
|---|
| 343 | | myPath = '/'.join(pathList[1:]) |
|---|
| 344 | | func = root_a_b_dot_index |
|---|
| 345 | | else: |
|---|
| 346 | | # Try root.a.b.default() |
|---|
| 347 | | root_a_b_dot_default = getattr(root_a_b, 'default', None) |
|---|
| 348 | | if root_a_b_dot_default and getattr(root_a_b_dot_default, 'exposed', None): |
|---|
| 349 | | # XXX: I don't think this ever gets called? |
|---|
| 350 | | myPath = 'root_a_b_dot_default' |
|---|
| 351 | | func = root_a_b_dot_default |
|---|
| 352 | | elif callable(root_a_b) and getattr(root_a_b, 'exposed', None): |
|---|
| 353 | | # We use root.a.b() |
|---|
| 354 | | myPath = '/'.join(pathList[1:-1]) |
|---|
| 355 | | func = root_a_b |
|---|
| 356 | | |
|---|
| 357 | | if func == None: |
|---|
| 358 | | # None of these exist: use the default method we found earlier |
|---|
| 359 | | if bestKnownDefaultMethod: |
|---|
| 360 | | func = bestKnownDefaultMethod |
|---|
| 361 | | myPath = bestKnownDefaultMethodMyPath |
|---|
| 362 | | |
|---|
| 363 | | if func == None: |
|---|
| 364 | | raise cperror.NotFound |
|---|
| 365 | | |
|---|
| 366 | | myPath += '/' |
|---|
| 367 | | if len(myPath) > 1: |
|---|
| 368 | | myPath = '/' + myPath |
|---|
| 369 | | |
|---|
| 370 | | cpg.request.objectPath = myPath |
|---|
| 371 | | cpg.request.virtualPath = cpg.request.path[len(myPath)-1:] |
|---|
| 372 | | cpg.response.body = func(**(cpg.request.paramMap)) |
|---|
| | 282 | try: |
|---|
| | 283 | func, objectPathList, virtualPathList = mapPathToObject() |
|---|
| | 284 | except IndexRedirect, inst: |
|---|
| | 285 | # For an IndexRedirect, we don't go through the regular |
|---|
| | 286 | # mechanism: we return the redirect immediately |
|---|
| | 287 | newUrl = canonicalizeUrl(inst.args[0]) |
|---|
| | 288 | wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion'])) |
|---|
| | 289 | cpg.response.headerMap['Location'] = newUrl |
|---|
| | 290 | for key, valueList in cpg.response.headerMap.items(): |
|---|
| | 291 | if key not in ('Status', 'protocolVersion'): |
|---|
| | 292 | if type(valueList) != type([]): valueList = [valueList] |
|---|
| | 293 | for value in valueList: |
|---|
| | 294 | wfile.write('%s: %s\r\n'%(key, value)) |
|---|
| | 295 | wfile.write('\r\n') |
|---|
| | 296 | return |
|---|
| | 297 | |
|---|
| | 298 | cpg.request.objectPath = '/'.join(objectPathList) |
|---|
| | 299 | cpg.response.body = func(*virtualPathList, **(cpg.request.paramMap)) |
|---|
| 384 | | |
|---|
| | 311 | def getObjFromPath(objPathList, objCache): |
|---|
| | 312 | """ For a given objectPathList (like ['root', 'a', 'b', 'index']), |
|---|
| | 313 | return the object (or None if it doesn't exist). |
|---|
| | 314 | Also keep a cache for maximum efficiency |
|---|
| | 315 | """ |
|---|
| | 316 | if not objPathList: return cpg |
|---|
| | 317 | cacheKey = tuple(objPathList) |
|---|
| | 318 | if cacheKey in objCache: return objCache[cacheKey] |
|---|
| | 319 | previousObj = getObjFromPath(objPathList[:-1], objCache) |
|---|
| | 320 | obj = getattr(previousObj, objPathList[-1], None) |
|---|
| | 321 | objCache[cacheKey] = obj |
|---|
| | 322 | return obj |
|---|
| | 323 | |
|---|
| | 324 | def mapPathToObject(): |
|---|
| | 325 | # Traverse path: |
|---|
| | 326 | # for /a/b?arg=val, we'll try: |
|---|
| | 327 | # root.a.b.index -> redirect to /a/b/?arg=val |
|---|
| | 328 | # root.a.b.default(arg='val') -> redirect to /a/b/?arg=val |
|---|
| | 329 | # root.a.b(arg='val') |
|---|
| | 330 | # root.a.default('b', arg='val') |
|---|
| | 331 | # root.default('a', 'b', arg='val') |
|---|
| | 332 | |
|---|
| | 333 | # Also, we ignore trailing slashes |
|---|
| | 334 | # Also, a method has to have ".exposed = True" in order to be exposed |
|---|
| | 335 | |
|---|
| | 336 | path = cpg.request.path |
|---|
| | 337 | if path.startswith('/'): path = path[1:] # Remove leading slash |
|---|
| | 338 | if path.endswith('/'): path = path[:-1] # Remove trailing slash |
|---|
| | 339 | |
|---|
| | 340 | if not path: |
|---|
| | 341 | objectPathList = [] |
|---|
| | 342 | else: |
|---|
| | 343 | objectPathList = path.split('/') |
|---|
| | 344 | objectPathList = ['root'] + objectPathList + ['index'] |
|---|
| | 345 | |
|---|
| | 346 | # Try successive objects... (and also keep the remaining object list) |
|---|
| | 347 | objCache = {} |
|---|
| | 348 | isFirst = True |
|---|
| | 349 | isDefault = False |
|---|
| | 350 | foundIt = False |
|---|
| | 351 | virtualPathList = [] |
|---|
| | 352 | while objectPathList: |
|---|
| | 353 | candidate = getObjFromPath(objectPathList, objCache) |
|---|
| | 354 | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| | 355 | foundIt = True |
|---|
| | 356 | break |
|---|
| | 357 | # Couldn't find the object: pop one from the list and try "default" |
|---|
| | 358 | lastObj = objectPathList.pop() |
|---|
| | 359 | if not isFirst: |
|---|
| | 360 | virtualPathList.insert(0, lastObj) |
|---|
| | 361 | objectPathList.append('default') |
|---|
| | 362 | candidate = getObjFromPath(objectPathList, objCache) |
|---|
| | 363 | if callable(candidate) and getattr(candidate, 'exposed', False): |
|---|
| | 364 | foundIt = True |
|---|
| | 365 | isDefault = True |
|---|
| | 366 | break |
|---|
| | 367 | objectPathList.pop() # Remove "default" |
|---|
| | 368 | isFirst = False |
|---|
| | 369 | |
|---|
| | 370 | # Check results of traversal |
|---|
| | 371 | if not foundIt: |
|---|
| | 372 | raise cperror.NotFound # We didn't find anything |
|---|
| | 373 | |
|---|
| | 374 | if isFirst: |
|---|
| | 375 | # We found the extra ".index" |
|---|
| | 376 | # Check if the original path had a trailing slash (otherwise, do |
|---|
| | 377 | # a redirect) |
|---|
| | 378 | if cpg.request.path[-1] != '/': |
|---|
| | 379 | newUrl = cpg.request.path + '/' |
|---|
| | 380 | if cpg.request.queryString: newUrl += cpg.request.queryString |
|---|
| | 381 | raise IndexRedirect(newUrl) |
|---|
| | 382 | |
|---|
| | 383 | return candidate, objectPathList, virtualPathList |
|---|
| | 384 | |
|---|
| | 385 | def canonicalizeUrl(newUrl): |
|---|
| | 386 | if not newUrl.startswith('http://') and not newUrl.startswith('https://'): |
|---|
| | 387 | # If newUrl is not canonical, we must make it canonical |
|---|
| | 388 | if newUrl[0] == '/': |
|---|
| | 389 | # URL was absolute: we just add the request.base in front of it |
|---|
| | 390 | newUrl = cpg.request.base + newUrl |
|---|
| | 391 | else: |
|---|
| | 392 | # URL was relative |
|---|
| | 393 | if cpg.request.browserUrl == cpg.request.base: |
|---|
| | 394 | # browserUrl is request.base |
|---|
| | 395 | newUrl = cpg.request.base + '/' + newUrl |
|---|
| | 396 | else: |
|---|
| | 397 | newUrl = cpg.request.browserUrl[:i+1] + newUrl |
|---|
| | 398 | return newUrl |
|---|