[解決済み] コンポジュール・ルートの "ビッグ・アイデア "とは?
質問
私はClojureの初心者で、基本的なWebアプリケーションを書くためにCompojureを使用しています。 私は、Compojureの
defroutes
構文で壁にぶち当たり、その背後にある"how"と"why"の両方を理解する必要があると思います。
Ringスタイルのアプリケーションは、HTTPリクエストマップから始まり、レスポンスマップに変換され、ブラウザに送り返されるまで、一連のミドルウェア関数にリクエストを渡すだけのように見えます。 このようなスタイルは、開発者にとっては低レベルすぎるため、Compojureのようなツールが必要なのです。 私は、他のソフトウェアのエコシステムにおいても、より抽象化することの必要性を感じています。特に、PythonのWSGIがそうです。
問題は、私がCompojureのアプローチを理解していないことです。 次のようにしましょう。
defroutes
S式です。
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
このすべてを理解する鍵がマクロの魔術にあることは知っていますが、私は(まだ)マクロを完全に理解しているわけではありません。 私は
defroutes
のソースを長い間見つめてきましたが、どうしても理解できません! どうなってるんだ? という具体的な疑問に答えるには、ビッグアイデアを理解することが必要でしょう。
-
ルーティングされた関数内から Ring 環境にアクセスするにはどうすればよいですか (たとえば
workbench
関数) の中からどのようにRing環境にアクセスするのでしょうか? たとえば、HTTP_ACCEPTヘッダーや、リクエスト/ミドルウェアの他の部分にアクセスしたいとします? -
デストラクチャリング(
{form-params :form-params}
)? デストラクチャリング時に使用できるキーワードは何ですか?
私は本当にClojureが好きですが、私はとても困っています!
どのように解決するのですか?
Compojureの説明 (ある程度)
NBです。私は Compojure 0.4.1 で作業しています ( ここで はGitHubの0.4.1リリースコミットです)。
なぜですか?
の一番上にある
compojure/core.clj
の一番上に、Compojureの目的について、このように役に立つ要約があります。
Ringハンドラを生成するための簡潔な構文です。
表面的なレベルでは、これが「なぜ」という疑問に対するすべてです。 もう少し深く掘り下げるために、Ring スタイルのアプリがどのように機能するかを見てみましょう。
-
リクエストが到着すると、Ring仕様に従ってClojureマップに変換されます。
-
このマップは、いわゆる "ハンドラ関数" にファネルされ、レスポンス(これもClojureマップです)を生成することが期待されています。
-
レスポンスマップは実際のHTTPレスポンスに変換され、クライアントに送り返されます。
上記のステップ 2 は最も興味深いもので、リクエストで使用された URI を調べ、クッキーなどを調べ、最終的に適切なレスポンスに到達するのはハンドラの責任だからです。 これらは通常、ハンドラ関数とそれをラップするミドルウェア関数のコレクションです。 Compojureの目的は、ベースハンドラ関数の生成を簡素化することです。
どのように?
Compojureは、quot;routes"の概念を中心に構築されています。 これらは実際には、より深いレベルで クラウト ライブラリ (Compojure プロジェクトのスピンオフで、0.3.x -> 0.4.x の移行時に多くのものが別のライブラリに移動されました) によって、より深いレベルで実装されています。 ルートは、(1)HTTPメソッド(GET、PUT、HEAD...)、(2)URIパターン(Webby Rubyistにはおなじみの構文で指定)、(3)リクエストマップの一部をボディで利用できる名前に結びつける際に使用する破壊形式、(4)有効なRing応答を生成する必要がある式のボディ(非自明のケースでは、これは通常単に別の関数への呼び出しです)により定義されています。
これは、簡単な例を見てみるには良いポイントかもしれません。
(def example-route (GET "/" [] "<html>...</html>"))
これをREPLでテストしてみましょう(以下のリクエストマップはRingの最小限の有効なリクエストマップです)。
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
もし
:request-method
が
:head
であった場合、応答は
nil
. という問題に戻ります。
nil
が何を意味するのかについては、すぐにここで説明します(ただし、これは有効なRingのレスポンスではないことに注意してください!)。
この例から明らかなように
example-route
は単なる関数で、非常に単純なものです。 リクエストを見て、それを処理することに興味があるかどうかを (:request-method
と
:uri
を含む)、もしそうなら、基本的なレスポンス・マップを返します。
Compojureは文字列(上で見たように)と他の多くのオブジェクトタイプに対して、適切なデフォルトの処理を提供します。
compojure.response/render
マルチメソッドを参照してください(コードはここで完全にセルフドキュメント化されています)。
を使ってみましょう。
defroutes
を使ってみましょう。
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
上に表示された例のリクエストに対するレスポンスと、その変形で
:request-method :head
への応答は予想通りです。
の内部動作は
example-routes
の内部動作は、それぞれのルートが順番に試行されるようになっています。
nil
でないレスポンスを返すとすぐに、そのレスポンスは全体の
example-routes
ハンドラ全体の戻り値となります。 さらに便利なことに
defroutes
-で定義されたハンドラは
wrap-params
と
wrap-cookies
を暗黙のうちに含んでいます。
より複雑なルートの例です。
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
以前使われていた空のベクトルの代わりに、デストラクチャリングフォームがあることに注意してください。 ここでの基本的な考え方は、ルートのボディがリクエストに関するいくつかの情報に興味を持つかもしれないということです。これは常にマップの形式で到着するので、リクエストから情報を抽出し、ルートのボディのスコープにあるローカル変数にそれを結びつけるために、連想型の構造化フォームが提供されます。
上記のテストです。
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
上記に対する素晴らしいフォローアップのアイデアは、より複雑なルートは
assoc
がマッチング段階でリクエストに余分な情報を追加することです。
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
これで応答するのは
:body
の
"foo"
を前の例からのリクエストに変換します。
この最新の例では二つのことが新しくなっています。
"/:fst/*"
と空でないバインディングベクター
[fst]
. 1つ目は、前述のRailsとSinatraのようなURIパターンのための構文です。 これは、URIセグメントに対する正規表現による制約がサポートされているという点で、上記の例から明らかなものよりも少し洗練されています(例.
["/:fst/*" :fst #"[0-9]+"]
のすべての桁の値のみを受け入れるようにするために、 :fst
を指定することができます)。 2つ目は、簡略化された方法で
:params
これは、リクエストの URI セグメント、クエリ文字列パラメータ、フォームパラメータを抽出するのに便利です。 後者の点を説明するための例です。
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
これは、問題文にある例を見てもらうのが良いでしょう。
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
それぞれのルートを順番に解析してみましょう。
-
(GET "/" [] (workbench))
-- を扱う場合GET
というリクエストに:uri "/"
を指定すると、関数workbench
を呼び、それが返すものをレスポンス・マップにレンダリングします。 (戻り値はマップだけでなく、文字列などもあり得ることを思い出してください) -
(POST "/save" {form-params :form-params} (str form-params))
--:form-params
が提供するリクエストマップのエントリです。wrap-params
ミドルウェアが提供するリクエストマップのエントリです (これは暗黙のうちにdefroutes
). 応答は標準的な{:status 200 :headers {"Content-Type" "text/html"} :body ...}
と共に(str form-params)
に置き換えて...
. (少し変わったPOST
ハンドラで、これは...) -
(GET "/test" [& more] (str "<pre> more "</pre>"))
-- これは例えば、マップの文字列表現をエコーバックします。{"foo" "1"}
もしユーザエージェントが"/test?foo=1"
. -
(GET ["/:filename" :filename #".*"] [filename] ...)
--:filename #".*"
の部分は全く何もしません (なぜなら#".*"
は常にマッチするからです)。 これはリングユーティリティ関数ring.util.response/file-response
を呼び出して、その応答を生成します。{:root "./static"}
の部分はファイルを探す場所を示しています。 -
(ANY "*" [] ...)
-- キャッチオールルートです。 Compojure の良い習慣として、このようなルートは常にdefroutes
フォームの最後にこのようなルートを含めるのがよい習慣です。これは、定義されているハンドラが常に有効な Ring 応答マップを返すようにするためです (ルートのマッチングに失敗するとnil
).
なぜこの方法なのか?
リングミドルウェアの目的の一つは、リクエストマップに情報を追加することです。したがって、クッキーを処理するミドルウェアは、リクエストマップに
:cookies
キーをリクエストに追加します。
wrap-params
が追加されます。
:query-params
または
:form-params
クエリ文字列/フォームデータが存在する場合などです。 (厳密に言うと、ミドルウェア関数が追加するすべての情報はリクエストマップに既に存在していなければなりません。) 最終的に "enriched" リクエストはベースハンドラに渡されます。ベースハンドラは、ミドルウェアによって追加されたすべてのきれいに前処理された情報でリクエストマップを調べ、応答を生成します。 (ミドルウェアはそれよりももっと複雑なことができます。例えば、いくつかの "inner"ハンドラをラップして、それらの間で選択したり、 ラップしたハンドラを呼び出すかどうかを決定したり、などです。 しかし、それは、この回答の範囲外です)。
ベースハンドラは、順番に、通常(自明でない場合)、リクエストに関する情報のほんの一握りの項目を必要とする傾向がある関数です。 (例
ring.util.response/file-response
はリクエストの大部分には関心がありません; ファイル名だけが必要です)。 したがって、Ring リクエストの関連する部分のみを抽出する簡単な方法が必要なのです。 Compojureは、まさにそれを行う、いわば特別な目的のパターン マッチング エンジンを提供することを目的としています。
関連
-
[解決済み] clojureの "let "の例を理解しようとすること
-
[解決済み】ClojureでWebアプリケーションを作るには?[クローズド]
-
[解決済み] REPLでclojureファイルを再読み込みする方法
-
[解決済み] 実社会で活躍するLisp【クローズド
-
[解決済み] Lispに関するPaul Grahamの指摘を説明してください [closed].
-
[解決済み] ClojureでStringを数値に変換するにはどうしたらいいですか?
-
[解決済み] Clojure、Scheme/Racket、Common Lispの違いは何ですか?
-
[解決済み] Clojure: リデュース vs. アプライ
-
[解決済み] clojureで指数を計算するには?
-
[解決済み] Clojureのdoseqとforの違いについて
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] clojureの "let "の例を理解しようとすること
-
[解決済み] REPLでclojureファイルを再読み込みする方法
-
[解決済み] useとrequireの違い
-
[解決済み] 実社会で活躍するLisp【クローズド
-
[解決済み] Lispに関するPaul Grahamの指摘を説明してください [closed].
-
[解決済み] clojureプロトコルの簡単な説明
-
[解決済み] ClojureでStringを数値に変換するにはどうしたらいいですか?
-
[解決済み] Clojure CoreやContribでZip関数に相当するものはありますか?
-
[解決済み] Clojure: リデュース vs. アプライ
-
[解決済み] Clojureのブロックコメント