The URI structure of my website changed drastically recently and I need to redirect all of the old pages to their corresponding new pages. I have a dotted list of pairs of all of the old and new URIs. At the moment I am trying to define easy handlers for each in a loop:
(let ((redirects '(("/old/uri/example-1" . "/new/uri/example-1"))))
(dolist (redirect redirects)
(hunchentoot:define-easy-handler (???? :uri (first redirect)) ()
(redirect (rest redirect)))
))
Maybe there is a better way. Assuming define-easy-handler is correct, it requires a function symbol for each easy handler. I tried the following to no avail:
- Placing a (gensym) where it expects a function symbol
- Using lists rather than dotted lists and calling (first redirect) where it expects a symbol
- Placing a quasiquote around the whole thing and an unquote around (first redirect)
What would be a good way to accomplish this?
Let's guess: DEFINE-EASY-HANDLER
is a macro.
Three typical ways to solve that:
expands (defredirects (a . a1) (b . b1) (c . c1)))
into
(progn
(hunchentoot:define-easy-handler (f-a ... a) () (... a1))
(hunchentoot:define-easy-handler (f-b ... b) () (... b1))
(hunchentoot:define-easy-handler (f-c ... c) () (... c1)))
- Generate the form you want to call and use
eval
(or compile
and funcall
if possible) in the loop for each form.
Although you already solved the problem I figured I might add this as an alternative. If you don't want to make a whole custom acceptor, you can add an around-method on HUNCHENTOOT:ACCEPTOR-DISPATCH-REQUEST
for HUNCHENTOOT:EASY-HANDLER
.
Let's make an acceptor and one page first:
(defparameter *acceptor* (make-instance 'hunchentoot:easy-acceptor :port 4242))
(hunchentoot:define-easy-handler (foo :uri "/foo") ()
(format nil "<html><body><h1>Test</h1><p>foo</p></body></html>"))
(hunchentoot:start *acceptor*)
Then redirect /bar
and /quux
to /foo
:
;; A simple helper to create prefix dispatchers.
(defun make-redirect-list (redirects)
(mapcar (lambda (redirect)
(destructuring-bind (from . to) redirect
(hunchentoot:create-prefix-dispatcher from
(lambda ()
(hunchentoot:redirect to)))))
redirects))
(defparameter *redirects* (make-redirect-list
'(("/bar" . "/foo")
("/quux" . "/foo"))))
(defmethod hunchentoot:acceptor-dispatch-request :around
((acceptor hunchentoot:easy-acceptor) request)
(dolist (redirect *redirects*)
;; Match the request against the prefix dispatchers in *REDIRECTS*...
(let ((handler (funcall redirect request)))
(when handler
;; and call the corresponding handler if a match is found.
(return-from hunchentoot:acceptor-dispatch-request
(funcall handler)))))
;; Unless a handler was found, call next method to
;; handle the request normally.
(call-next-method))
Edit: Use around method instead of before. I initially figured that letting it call the main method normally would be necessary for any logging/etc. happening there, but after further testing it doesn't seem to be.
This solution works. I definitely appreciate feedback regarding whether or not it's best practice.
(defun add-redirect (name from to)
(eval `(hunchentoot:define-easy-handler (,name :uri ,from) ()
(redirect ,to))))
(defun add-redirects (redirects)
(dolist (redirect redirects)
(add-redirect (first redirect) (second redirect) (third redirect))
))
(add-redirects
'(
(redirect-1 "/redirect-1/" "/destination-1/")
(redirect-2 "/redirect-2/" "/destination-2/")
(redirect-3 "/redirect-3/" "/destination-3/")
))