I've got a question about the *dispatch-table* and the associated actions that are returned from the dispatch functions. Based on the code in modlisp.lisp:
(loop for dispatcher in *dispatch-table* for action = (funcall dispatcher *request*) when action return (funcall action) finally (setf (return-code *reply*) +http-not-found+))))
Might it be desirable to check the return of (funcall action), and if nil, continue processing with the next dispatcher rather than returning and sending output at this point? If one truly wanted to return an empty body, one could still throw to the tbnl-handler-done tag which would bypass the rest of the dispatch table.
I'm writing a template dispatcher which will process a request using HTML-TEMPLATE. Of course, this is only possible if I can map a request to a template (I may store the templates on the filesystem or in a db, not sure yet). If a template cannot be found for the request, then I'd like to fall back to processing the request with the rest of the dispatch table (which may just serve a static file or may be another template dispatcher thats resolves templates differently).
In order to accomplish the above in the today's TBNL, I need to make the template resolution occur in the dispatch function (instead of the action function that the dispatcher returns) because I can return nil from the dispatcher if resolution fails and thus instruct TBNL to move on to the next dispatcher. In addition, I'd also like to make my dispatch decision based on a prefix. So I'd like to use create-prefix-dispatcher to limit the template processing to only part of the URI namespace. Thus, I need two 'tests' to occur in my dispatch function: 1) is there a prefix match (replicating the create-prefix-dispatcher functionality), and 2) does the request map to a template. With that said, I have created:
(defun create-template-dispatcher (prefix resolver-function) #'(lambda (request) (when (prefix-match-p prefix (script-name request)) (let ((template (funcall resolver-function request))) (when template #'(lambda () (funcall *default-template-handler* template)))))))
prefix-match-p is refactored code from create-prefix-dispatcher, but is essentially the same, does the request start with the given prefix? If it does not, I return nil, and processing continues with the rest of the dispatch table. Assuming the prefix does match, I then go on to call the template resolution function (resolver-function) which returns a template as a string or nil if a template could not be found. Template resolution might query a db or just read a template from the filesystem. Finally, if a template if found, I process the template with the default template handler which points to:
(defun template-handler (template) (let ((context (assemble-context))) (with-output-to-string (*default-template-output*) (fill-and-print-template template context))))
Here is a sample template resolver function that maps a request to a template on the filesystem (only works on systems with '/' as a separator, my lisp path manipulations skills are absent at the moment):
(defun filesystem-resolver (base &optional (request *request*)) (contents-of-file (parse-namestring (concatenate 'string base (script-name request)))))
And here is my sample dispatch table that pulls templates from multiple locations on the filesystem depending on the request URI:
(setf *dispatch-table* (list (create-template-dispatcher "/tbnl/space1" #'(lambda (request) (filesystem-resolver "/tmp" request))) (create-template-dispatcher "/tbnl/space2" #'(lambda (request) (filesystem-resolver "/var/www/html" request))) #'default-dispatcher))
Back to the original question and my rationale for asking it, I think I'd prefer if I didn't have to put all of my predicates for testing whether or not the template handler should be run within the dispatch function as it just doesn't seem to be the right spot for this logic. However, I definitely want the template handler executed based on the prefix. If TBNL could continue processing the dispatch table upon the return of nil from the handler (action), my dispatch table would look like:
(setq *dispatch-table* (nconc (mapcar (lambda (args) (apply #'create-prefix-dispatcher args)) '(("/tbnl/space1" template-dispatcher) ("/tbnl/space2" template-dispatcher))) (list #'default-dispatcher)))
And the template-dispatcher would consult a *resolver-table* and invoke the appropriate resolution function for the request. The nice thing about this approach is that my *dispatch-table* is not cluttered with non-dispatch related stuff such as template resolution. Of course that all requires the ability for an action (the template-dispatcher above) to signify that it cannot handle the request by returning a nil, which could then indicate to TBNL that the next entry in the *dispatch-table* should be tried. Would this functionality appeal to anyone else?
Sorry about the length of email, but I thought it would be useful to share what I'm doing with TBNL. This is all just for fun and non-profit and my pursuit of learning lisp so if I've said something stupid or more specifically am not doing things in the traditional "lisp way", please let me know.
Thanks, Pete