Path Processing Strategies

In the development of most Web applications, the structure of the application - also known as the "site map" - needs to be defined at a very early stage. We might decide that if a user requests a certain path, a particular part of the application will be invoked, and we might define the site map in a number of different ways:

As a set of acceptable paths...

As a tree of resources...

Since all of the action in WebStack applications takes place inside resources, the challenge is to define resources in such a way which makes processing paths relatively easy.

Chaining Resources

Whilst the classic resource, as described in "Applications and Resources", might resemble a simple class whose respond method performs most of the necessary work, it is useful to reconsider such a resource as doing such work only for a particular narrow part of a larger Web application. Moreover, resources are not restricted in their usage of other objects to carry out their purpose, provided they are initialised with references to those objects.Consequently, it makes sense to consider defining a resource which, if it alone cannot process a request, invokes the respond method on another resource in order to get that resource to continue with the act of processing.

We can apply this insight to the above path processing scenario. If we first employ a resource to examine details of the path, and if that resource then invokes other resources to produce certain pages, we can separate the path processing from the rest of the application's functionality. So, for the first site map strategy, we could define a path processing resource as follows:

from WebStack.Generic import EndOfResponse

class PathProcessor:

"A path processing resource."

def __init__(self, main_page, salary_report_page, complaints_page):

# Supplied resources are chained to this resource.

self.main_page = main_page
self.salary_report_page = salary_report_page
 self.complaints_page = complaints_page

 def respond(self, trans):

# This is where the resources are invoked...

path = trans.get_path_without_query() # should really use an encoding here
if path == "/":
self.main_page.respond(trans)
elif path == "/services/finance/salaries/":
self.main_page.respond(trans)
elif path == "/services/finance/salaries/":
self.main_page.respond(trans)

# Administrative details...

else:
trans.set_response_code(404) # not found!
raise EndOfResponse

Of course, more elegant methods of mapping paths to resources could be employed - a dictionary might be an acceptable solution, if defined in the initialiser method. The above class might be initialised as follows:

main_page = MainResource()
salary_report_page = SalaryReportResource()
complaints_page = ComplaintsResource()
path_processor = PathProcessor(main_page, salary_report_page, complaints_page)

For the second site map strategy, we retain the basic parts of the above strategy, focusing only on one level in the path as opposed to the complete path. However, this means that our path processing resources need to know exactly which part of the path they should be considering, and perhaps which part of the path has already been processed.

As described in "Paths To and Within Applications", special path information of the nature required is provided by two methods: get_virtual_path_info and get_processed_virtual_path_info. Such information can thus be obtained and updated at each level in the processing chain.

class MainPathProcessor:

"A path processor for the top of the application."

def __init__(self, main_page, services_processor):
self.main_page = main_page
self.services_processor = services_processor

def respond(self, trans):

# This is where the resources are invoked...

path = trans.get_virtual_path_info() # should really use an encoding here
if path == "/":
self.main_page.respond(trans)
elif path.startswith("/services/"):
trans.set_virtual_path_info(path[len("/services"):]) # cut off "/services"
self.services_processor.respond(trans)

# Administrative details...

else:
trans.set_response_code(404) # not found!
raise EndOfResponse

A suite of similar classes can be defined and might be initialised as follows:

main_page = MainResource()
salary_report_page = SalaryReportResource()
complaints_page = ComplaintsResource()
finance_processor = FinancePathProcessor(salary_report_page)
customer_processor = CustomerPathProcessor(complaints_page)
services_processor = ServicesPathProcessor(finance_processor, customer_processor)
main_processor = MainPathProcessor(main_page, services_processor)

Fortunately, this latter strategy is supported by WebStack in the form of the ResourceMap module. See "Treating the Path Like a Filesystem" and "ResourceMap - Simple Mappings from Names to Resources" for details.