前回 は Lack のアプリケーションのコードを追い,ミドルウェアの使われ方を理解した.今回はミドルウェアを一つ取り上げる.今回取り上げるのはMountミドルウェア(lack-middleware-mount
)で,リクエスト・パスに応じたルーティングを司る.最初にミドルウェアのソースを読み,最後にMountミドルウェアを使ったサンプルWebアプリを作成する.コメント,間違いのご指摘等は [@waterloo_jp](https://twitter.com/waterloo_jp) まで.
コード1は lack-middleware-mountのソースコード である.
(defparameter *lack-middleware-mount*
(lambda (app path mount-app) (1)
(let ((len (length path)))
(lambda (env) (2)
(let ((path-info (getf env :path-info)))
(cond
((string= path-info path)
(setf (getf env :path-info) "/")
(funcall (to-app mount-app) env))
((and (< len (length path-info))
(string= path-info path :end1 len)
(char= (aref path-info len) #\/))
(setf (getf env :path-info)
(subseq path-info (length path)))
(funcall (to-app mount-app) env))
(t
(funcall app env)))))))
"Middleware for attaching another Lack application on a specific URL")
1 | アプリケーションbuilderによってビルド時に1回だけ呼び出される関数.共通設定として path と mount-app を引数に取る. |
2 | Webサーバー・ハンドラから呼び出されるミドルウェア本体. |
前回 見たように,一番外側のラムダ式はビルダによってアプリケーション生成時に1度だけ呼び出される.3行目でマウントするパスの長さを事前に取得しておく.
リクエストの度にWebサーバーから呼び出されるのは4行目のラムダ式である.まずリクエストのパスを path-info
に設定する.path-info
はURLからホストネームとポートを取り除いたパス部分から,?
以降のクエリストリングを取り除いたものだ.
ミドルウェアの処理は3つに分岐する.1つ目は,path-info
がミドルウェアのマウンント path
と完全一致した場合で,この時,env
の :path-info
をルート /
で上書きして mount-app
をコールする(9行目).つまり,mouta-app
は path
をルートとするアプリケーションということになる.
2つ目の分岐は,リクエストURLが path
で始まっていた場合である.前方一致の場合は,path
部分を取り除いた残りが :path-info
に設定されるので,mount-app
は path
をルートとし,さらに深いパスをリクエストで指定された状態としてコールされる(15行目).
3つ目の分岐は上記以外で,この場合,マウント・ミドルウェアでマウントしたアプリケーションではなく,アプリケーション・チェーンの次のアプリケーション呼び出される(17行目で).
基本事項のおさらい
-
Characterは
#\
の後に1文字置いて表す.12行目の#\/
はパス区切りのスラッシュを表す. -
(squbseq sequence start end)
はsequence
のstart
からend
までのコピーを新たに作成して返す.end
が省略されるかnil
ならstart
から最後までが返る.シーケンスはリストやベクトル(1次元配列)の親クラス. Tutorialspontsの解説が良い.
サンプルコード
Mountミドルウェアを試してみる.コードは GitHub にある.
まずは,ClackとLackをロード.
(require 'clack)
(require 'lack)
env
から :path-info
を取り出して画面に表示するアプリケーションを定義して,my-echo
に格納.
(setf my-echo
(lambda (env)
`(200 (:content-type "text/plain") ,(list (getf env :path-info)))))
Lackの builder
マクロを使ってLackアプリケーションを構築.
(defparameter *app*
(lack:builder
(:mount "/echo" my-echo) (1)
(:mount "/hello" (2)
(lambda (env)
(declare (ignore env))
'(200 (:content-type "text/plain") ("Hello world!"))))
(lambda (env)
(declare (ignore env))
'(404 (:content-type "text/plain") ("Not found, mate!"))))) (3)
1 | /echo 以下のアクセスでpath-infoを表示するアプリケーションが呼び出される. |
2 | /hello 以下のアクセスで呼び出されるアプリケーション. |
3 | それ以外のアクセスは "Not found, mate!" を表示. |
これで準備完了.REPLで以下を実行してWebサーバーを起動.
> (defparameter *handler*
(clack:clackup *app*))
Hunchentoot server is started.
Listening on localhost:5000.
*HANDLER*
以下がアクセスと結果である.
-
http://localhost:5000/echo/hello/from/fukuoka ⇒ /hello/from/fukuoka
-
http://localhost:5000/hello/everybody!/howareyou? ⇒ Hello world!
-
http://localhost:5000 ⇒ Not found, mate!
サーバーを停止するには以下を実行する.
> (clack:stop *handler*)