Part 3: Sites, Endpoints and Handlers
As it's written right now the garter framework inappropriately combines the concept of a resource endpoint "page" and a handler into one object. These concepts need to be split out into their proper components.
Endpoints
The first part is the resource endpoint. An endpoint could be considered a mapping of a URI path to a particular module of code. Django uses the urls.py modules for this. Google Apps uses the app.yaml file's handlers: section. This is where regular expressions can assist with mapping URI paths to endpoint handlers (not to be confused with request handlers—that distinction is important).
The interface of the endpoint-handler should be
handler(request) -> response
The simplicity of the above is generally modeled by most web frameworks. WSGI is the exception where there is minimal preprocessing done on the incoming CGI data. Google App Engine, Django, mod_python use the interface above (the details of the request object are different but are generally isomorphic).
The impact that this should have to garter is the realization that the "Page" concept has to be restructured into two parts. The first part is the endpoint handler. This is what maps against a particular URI path. The second part is the request handler. A new request handler object should be instantiated for each request being serviced by the endpoint function/object. The fact that a new object ought to be created for each request should have be realized when member variables for the Page object were getting reinitialized for each new request.
Handlers
Since request handlers are the core of web applications, they generally have quite a bit of complexity depending on the service provided by the endpoint. The tendency in garter has been to model a particular service into a Page object. It's quite useful in multiple ways: the state of the request is reflected in the self variable; common code for an endpoint are grouped together in a class; and inheritance can be used for code reuse for common tasks related to the request. The majority of Page objects that currently exist in garter can directly map to an object that would be the request handler.
Modeling the Request Handler
For the majority of HTTP requests, the primary function of the request handler is to merge results from the request into the appropriate template to create response text. The results have been modeled as a dictionary-tree of values going by the name of pageData. The function of the request is to populate the name-key values in the appropriate place of the dictionary tree based. The appropriate template is then selected and both are merged using an iterable Render object.
garter code currently has a verbose way of performing the finalized response step. This is where convenience routines can help out immensely.
def respond(req):
...
template = self.loadTemplate('templatename')
self.start_response('200 OK', [('Content-Type',
'text/html;charset=utf-8')])
return Render(template, self.pageData)
can be replaced with:
def respond(req):
...
return self.render_html('templatename')
One thing to sort out is whether or not to merge in the members from the original request object into the request handler object or to keep the composition structured. For example:
if self.loggedIn:
self.pageData['entries'] = getUserEntries(self.session['id'])
else:
self.pageData['entries'] = getEntriesLatest()
or
if self.request.loggedIn:
self.pageData['entries'] = getUserEntries(self.request.session['id'])
else:
self.pageData['entries'] = getEntriesLatest()
Handlers Methods: Partitioning Request Methods
Partitioning request methods are a common aspect of request object. Many object models that have that kind of feature allow for get() and post() method overloading to simplify logic decision.
class RequestHandler(object):
def __init__(self, application, request):
pass
def post(self):
pass
def get(self):
pass
These decisions of function selection could also be handled at the endpoint. Imagine an endpoint that is defined as the normal request handler interface.
def handle_request(request):
if request.method == 'POST':
return PostActionHandler(application, request)
elif request.method == 'GET':
return GetHandler(application, request)
Not only that but within the same form action (a post specific to an endpoint) there could also be several different functions that would be defined based on the submit button used during the UI interaction. In garter this level of splitting functions based on the submit variable name posted was done as part of the request handler's method introspection.
class FormHandler(PageBasic):
def __init__(self):
PageBasic.__init__(self)
def do_login(self, post):
# perform login action for "login" button press
def do_register(self, post):
# "register" button was pressed
During the object initialization the methods of the class are inspected and added to an actions list. When the request is found to be a posted-form then the variables of the form are inspected. For the example above a list of actions ['login', 'register'] are saved and if the form contains a variable that matches any one in the list then the action matched is used to deduce the method name (e.g. do_login) and the method is performed.
This style of method selection has a very natural mapping to the submit button names that are used to invoke the actions. One can easily create the
<input type="submit" name="login" value="Login">
button and know that it matches the do_login() method.