コードリーディング: Lack の Mount ミドルウエアを理解する

前回Lack のアプリケーションのコードを追い,ミドルウェアの使われ方を理解した.今回はミドルウェアを一つ取り上げる.今回取り上げるのはMountミドルウェア(lack-middleware-mount)で,リクエスト・パスに応じたルーティングを司る.最初にミドルウェアのソースを読み,最後にMountミドルウェアを使ったサンプルWebアプリを作成する.コメント,間違いのご指摘等は [@waterloo_jp](https://twitter.com/waterloo_jp) まで.

コード1は lack-middleware-mountのソースコード である.

コード 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回だけ呼び出される関数.共通設定として pathmount-app を引数に取る.
2 Webサーバー・ハンドラから呼び出されるミドルウェア本体.

前回 見たように,一番外側のラムダ式はビルダによってアプリケーション生成時に1度だけ呼び出される.3行目でマウントするパスの長さを事前に取得しておく.

リクエストの度にWebサーバーから呼び出されるのは4行目のラムダ式である.まずリクエストのパスを path-info に設定する.path-info はURLからホストネームとポートを取り除いたパス部分から,? 以降のクエリストリングを取り除いたものだ.

ミドルウェアの処理は3つに分岐する.1つ目は,path-info がミドルウェアのマウンント path と完全一致した場合で,この時,env:path-info をルート / で上書きして mount-app をコールする(9行目).つまり,mouta-apppath をルートとするアプリケーションということになる.

2つ目の分岐は,リクエストURLが path で始まっていた場合である.前方一致の場合は,path 部分を取り除いた残りが :path-info に設定されるので,mount-apppath をルートとし,さらに深いパスをリクエストで指定された状態としてコールされる(15行目).

3つ目の分岐は上記以外で,この場合,マウント・ミドルウェアでマウントしたアプリケーションではなく,アプリケーション・チェーンの次のアプリケーション呼び出される(17行目で).

基本事項のおさらい

  • Characterは #\ の後に1文字置いて表す.12行目の #\/ はパス区切りのスラッシュを表す.

  • (squbseq sequence start end)sequencestart から end までのコピーを新たに作成して返す.end が省略されるか nil なら start から最後までが返る.シーケンスはリストやベクトル(1次元配列)の親クラス. Tutorialspontsの解説が良い.

サンプルコード

Mountミドルウェアを試してみる.コードは GitHub にある.

まずは,ClackとLackをロード.

(require 'clack)
(require 'lack)

env から :path-info を取り出して画面に表示するアプリケーションを定義して,my-echo に格納.

コード 2. Path-infoを表示するアプリケーションを定義.
(setf my-echo
      (lambda (env)
	      `(200 (:content-type "text/plain") ,(list (getf env :path-info)))))

Lackの builder マクロを使ってLackアプリケーションを構築.

コード 3. 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*

以下がアクセスと結果である.

サーバーを停止するには以下を実行する.

> (clack:stop *handler*)