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...
/
is the main page/services/finance/salaries/
is the salary report page/services/customer/complaints/
is the complaints pageAs a tree of resources...
/
is the main page.../services/
refers to services (which may not be defined as anything viewable).../finance/
is the finance department.../salaries/
is the salary report page.../customer/
is the customer service department.../complaints/
is the complaints pageSince 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.
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.