leibowitz
Experimental Common Lisp object storage abstraction for Unix file systems
leibowitz/web/web.lisp
Download raw file: web/web.lisp
;;; A booru-style web UI (in-package :leibowitz.web) (defclass webserver (easy-routes:easy-routes-acceptor) ((library :initarg :library :initform (error "You can't run a webserver without a library, dummy!") :accessor webserver-library :documentation "Back-link to the library instance this webserver has access to.")) (:documentation "Expand the easy-routes implementation of hunchentoot's acceptor with a back-link to a library and some custom methods that in theory allow us to run a bunch of servers on different ports from the same lisp. Each http route in Leibowitz's web ui needs to have access to the underlying `library', however, because of how hunchentoot manages its route dispatch table, there's no way to pass it to the route functions directly. Therefore; in order to avoid introducing egregious global state, we subclass `hunchentoot:acceptor' in a way that lets each route handler access the corresponding library")) (defmethod webserver-run ((w webserver)) (setf hunchentoot:*catch-errors-p* NIL) (setf hunchentoot:*show-lisp-errors-p* T) (setf hunchentoot:*show-lisp-backtraces-p* T) (hunchentoot:start w)) (defmethod webserver-die ((w webserver)) (hunchentoot:stop w)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; BEGIN QUESTIONABLE HACKS ;; FIXME: In theory I think these hacks allow us to run multiple ;; webservers on multiple libraries on multiple ports from the same ;; lisp, make sure this is actually the case! (defmethod easy-routes::process-route ((acceptor webserver) (route easy-routes:route) bindings) "A specialization of `easy-routes::process-route' on our custom `webserver' acceptor. This method is responsible for calling the endpoint handler method, which needs a library." (easy-routes::call-with-decorators (easy-routes::route-decorators route) (lambda () (apply (easy-routes::route-symbol route) (nconc (list (webserver-library acceptor)) (loop for item in (slot-value route 'easy-routes::variables) collect (cdr (assoc item bindings :test #'string=)))))))) (defmacro leibowitz-route ((name library template-and-options) params &body body) "Cribbed almost verbatim from `easy-routes:defroute' except that it creates handlers as methods of `library' rather than as normal functions." (let* ((template (if (listp template-and-options) (first template-and-options) template-and-options)) (variables (routes:template-variables (routes:parse-template template))) (arglist (mapcar (alexandria:compose #'intern #'symbol-name) variables)) (method (or (and (listp template-and-options) (getf (rest template-and-options) :method)) :get)) (acceptor-name (and (listp template-and-options) (getf (rest template-and-options) :acceptor-name))) (decorators (and (listp template-and-options) (getf (rest template-and-options) :decorators)))) (multiple-value-bind (body declarations docstring) (alexandria:parse-body body :documentation t) (easy-routes::assoc-bind ((params nil) (get-params :&get) (post-params :&post) (path-params :&path)) (easy-routes::lambda-list-split '(:&get :&post :&path) params) `(let ((%route (make-instance 'easy-routes:route :symbol ',name :template ',(routes:parse-template template) :variables ',variables :required-method ',method :decorators ',decorators))) ,(if acceptor-name `(let ((easy-routes::%routes-and-mapper (ensure-acceptor-routes-and-mapper ',acceptor-name))) (setf (gethash ',name (getf easy-routes::%routes-and-mapper :routes)) %route)) `(setf (gethash ',name easy-routes::*routes*) %route)) (easy-routes::connect-routes ',acceptor-name) (defmethod ,name ((,library library) ,@arglist) ,@(when docstring (list docstring)) (let (,@(loop for param in params collect (hunchentoot::make-defun-parameter param ''string :both)) ,@(loop for param in get-params collect (hunchentoot::make-defun-parameter param ''string :get)) ,@(loop for param in post-params collect (hunchentoot::make-defun-parameter param ''string :post)) ,@(loop for param in path-params collect (destructuring-bind (parameter-name parameter-type) param `(,parameter-name (hunchentoot::convert-parameter ,parameter-name ,parameter-type))))) ,@declarations ,@body))))))) ;;; END QUESTIONABLE HACKS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmacro static-resource (name path) (let* ((file (merge-pathnames (uiop:parse-unix-namestring (if (eql (aref path 0) #\/) (subseq path 1) (error "path not absolute"))) "web/")) (type (gethash (pathname-type file) hunchentoot::*mime-type-hash*)) (data (with-open-file (s file) (read-stream-to-string s)))) `(leibowitz-route (,name lib ,path) () (setf (hunchentoot:content-type*) ,type) ,data)))