[hunchentoot-devel] dispatch-table request

Otto Diesenbacher diesenbacher at gmail.com
Mon Dec 4 20:00:29 UTC 2006


Cyrus Harmon <ch-tbnl at bobobeach.com> writes:

> First of all, thanks for hunchentoot! I'm still getting used to it,
> but it seems great so far. But I do have a request. Would it be
> possible (and advisable) to extend the dispatch table to provide some
> metadata for each dispatcher? this could even be done in a backward

first, i am rather new to lisp, but perhaps my additions / changes to
the hunchentoot:*dispatch-table* are useful or interesting for someone.

(Please excuse the mixture of tbnl / hunchentoot naming in the
following code)

some stuff could propably be done better, much better :) feel free to
criticise :)

and finally: thanks to Edi and all the other people in the
lisp-community, that produces so wonderful things like hunchentoot! :)


;; I wanted a *dispatch-table*, so that I can add, remove or update the
;; dispatch-functions while a hunchentoot-server is running.
;;
;; f.e. my dispatch-table looks like:

;; CL-USER> hunchentoot:*dispatch-table*
;; (("/web/log" #<CLOSURE # {B4A5515}>)
;;  ("/web/error.html" #<CLOSURE # {B4792ED}>)
;;  ("/web/index.html" #<CLOSURE # {B4776E5}>)
;;  ("/web/style.css" #<CLOSURE # {B4755E5}>)
;;  ("default" #<FUNCTION HUNCHENTOOT:DEFAULT-DISPATCHER>))

;; i redefined the method. Both types of entries should work (plain
;; function, as in the "original" hunchentoot and my "new" entries
;; '("somestring" #function)). The "somestring" is later used to identify
;; the dispatch function. 

;;; Redefined original hunchentoot:dispatch-request
(defmethod dispatch-request (dispatch-table)
  "Dispatches *REQUEST* based upon rules in the DISPATCH-TABLE.
This method provides the default tbnl/hunchentoot behavior."
  (loop for dispatcher in dispatch-table
     for action = (if (typep dispatcher 'list)
		      (funcall (cadr dispatcher) *request*)
		      (funcall dispatcher *request*))
     when action
     return (funcall action)
     finally (setf (return-code *reply*)
		   +http-not-found+)))

;; and use the following functions to work with the "new" dispatch-table

(defun safe-assoc-string (item list)
  (loop for i in list
     when (if (and (listp i)
		   (stringp (car i)))
	      (string= (car i) item))
	      return i))

(defun add-dispatcher (path function)
  "adds dispatcher to tbnl:*dispatch-table*"
  (if (safe-assoc-string path hunchentoot:*dispatch-table*)
      ;;there is already a dispatcher with that identifier
      (setf (cadr (safe-assoc-string path tbnl:*dispatch-table*)) function)
      ;;create a new entry
      (push (list path function) tbnl:*dispatch-table*)))

(defun add-dispatcher-prefix (path function)
  "convient way to construct a dispatcher with prefix."
in a lambda to call the statistic writer before."
  (add-dispatcher path (funcall #'tbnl:create-prefix-dispatcher
				path
				#'(lambda ()
				  ;;  (write-statistic tbnl:*request*) ; my statistics function
				    (funcall function)))))

(defun remove-dispatcher (path)
  "remove a dispatch-entry by their path"
  (setf tbnl:*dispatch-table* (remove (assoc path tbnl:*dispatch-table* :test #'string=)
				      tbnl:*dispatch-table*)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; additionally i made the following macros, so that i simply have to evaluate f.e.
;; 
;; (defweb "/my/page.html"
;;   (bla
;;     (bla ....)))
;;
;; or for webpages that need parameters
;;
;; (defweb* "/my/page.html" (firstparameter secondparameter)
;;   (bla
;;     (setf firstparameter 'new-value)
;;     (...)))
;;
;; and the function that listen at the path "/my/page.html" is created or updated
;;
;;
;; the macros (and some condition-stuff i use):


(define-condition web-error (error)
  ((message :initarg :message :reader message)))

(defmacro error-page (fehler)
  `(tbnl:redirect (format nil "/web/error.html?fehler=~A" (tbnl:url-encode ,fehler))))

(defmacro defweb (path &body body)
  "constructs a defun + entry into tbnl:*dispatch-table*"
  (let ((funcn (read-from-string path)))
    `(progn
       (defun ,funcn ()
	 (handler-case (progn
			 , at body)
	   (web-error (fehler)
	     (web:error-page (message fehler)))))
       (compile (quote ,funcn))
       (add-dispatcher-prefix ,path (function ,funcn)))))

(defmacro defweb* (path parameters &body body)
  "constructs a defun + entry into tbnl:*dispatch-table* and prepares local variables
coresponding to the parameters.

f.e. (web:defweb* \"/web/test\" (a)
         (format nil \"~A\" a))

it is also possible to prepare a conversion of the variable. Put the definition in extra
brackets.

f.e. (web:defweb* \"/web/test\" ((a #'parse-integer))
         (format nil \"type of a ~A\" (type-of a)))
"
  (let ((funcn (read-from-string (string path))))
    `(progn
       (defun ,funcn ,(if parameters
			  (append
			   '(&aux )
			   (loop for i in parameters collect
				(if (listp i)
				    `(,(car i)
				       (funcall ,(cadr i)
						(tbnl:parameter ,(string-downcase (string (car i))))))
				    `(,i (tbnl:parameter ,(string-downcase (string i))))))))
	 (handler-case (progn
			 , at body)
	   (web-error (fehler)
	     (web:error-page (message fehler)))))
       (compile (quote ,funcn))
       (add-dispatcher-prefix ,path (function ,funcn))
       ',funcn)))

best regards

     okflo

-- 
Otto Diesenbacher
diesenbacher at gmail.com                      
Salzburg, Österreich




More information about the Tbnl-devel mailing list