2008-10-26

hunchentootでログイン処理(2) マクロ化

で、前回のログイン処理は土曜日いっぱいを使ってすごい苦労してできていた。
いま流行のRailsとか使えば5分でできちゃうような事なのであろう。
いやいや僕の場合だとRailsでも5時間はかかるぞきっと。

さて、うまく書けたログイン処理関数(login)だがまだ重大な欠陥がある。
(login)関数の最初の10行ぐらいが大事なとこ、というのは前も書いたけどまさにそれが欠陥だ。
ログインのロジックそのものは同じであっても、アプリごとにログインページのデザインは違うだろう。作るサイトによって趣味とかいろいろあるわけだし。

だから、ログイン処理と表示するページを分離しよう。そうすればログイン処理の部分だけライブラリ化してページ自体は自分で毎回定義できる。

マクロである。

(login)関数のログイン処理固有の部分をマクロとして抜き出してみた。
こんなコードになる。

;; name は定義する関数の名前。
;; auth-p はパスワード認証する関数の名前。2引数 name, pass を取る。
;; body はHTMLを生成する式でなければならない。
;; そのHTMLページはGET, POSTの両方で呼ばれ得る。
;; GETパラメタとしてfromを取りログイン処理後にそのURLにリダイレクトする。
;; POSTパラメタとしてfrom,name,pass,cancelの3つを取る。
;; セッションが有効なときに呼ばれた場合はログアウト処理をする。
;; cancelが渡された場合はすぐにfromへリダイレクトする。
;; name,passが(auth-p name pass)でTを返すならば (session-value 'user)に
;; ユーザ名を設定してセッションを作る。
;; auth-pに失敗した場合には同じページを再表示する。
(defmacro define-login-handler (name auth-p &body body)
(let ((=user= (gensym))
(=pass= (gensym)))
`(defun ,name ()
(when *session*
(remove-session *session*)
(redirect-safe (get-parameter "from")))
(let ((,=user= (post-parameter "name"))
(,=pass= (post-parameter "pass")))
(cond ((post-parameter "cancel")
(redirect-safe (post-parameter "from")))
((,auth-p ,=user= ,=pass=)
(setf (session-value 'user) ,=user=)
(redirect-safe (post-parameter "from"))))
,@body))))

新しく作ったこの(define-login-handler)がコメントでいろいろ書いたようなログイン処理に関わる面倒事をよきに処理してくれるわけだ。このマクロにはページ生成に関わる処理はなにも書かれていない。
新しく作ったといっても実際には元の(login)の最初の10行ぐらいをコピペしてきて、バッククォートで囲んで展開したいところを「,」or「,@」でプレフィックスしたぐらいだ。あとはletで導入した変数がbody部の変数使用に悪影響を与えないようgensymしたくらいか。
マクロといってもHTMLテンプレ言語を使える知識があれば難しくないと思う。

さっそくこれを使って(login)関数を書き直してみる。

(define-login-handler login auth-valid-p
(no-cache)
(setf (content-type) "text/html; charset=utf-8"
(reply-external-format) *utf-8*)
(with-html
(:html
(:head
(:title "ログイン処理"))
(:body
(:p (if *session*
(fmt "User: ~A" (session-value 'user))
(fmt "Not Login.")))
(:p (:form :method :post
"Login: "
(:input :type :text
:name "name"
:value (or (session-value 'user) ""))
:br
"Password: "
(:input :type :password
:name "pass"
:value "")
(:input :type :hidden
:name "from"
:value (get-parameter "from"))
:br
(:input :type :submit
:value "Login")
:br
(:input :type :submit
:name "cancel"
:value "Cancel")))))))

これを実行すると(login)という関数がつくられる。auth-valid-pは前回の関数そのまま。

どうでしょう。見事にページ生成の処理だけが残った。今後は新しいログインページを作る場合にもログイン処理に頭を悩ませる必要は無くなった。GET,POSTのパラメタ名といういくつかの約束を守ったページをデザインするだけで、ログイン処理そのものはマクロが書いてくれるわけだ。

0 件のコメント: