Compojure expliqué (à un certain degré)
NB. Je suis en train de travailler avec Compojure 0.4.1 (ici's la version 0.4.1 commit sur GitHub).
Pourquoi?
Tout en haut de l' compojure/core.clj
, il y a un résumé utile des Compojure le but de:
Une syntaxe concise pour générer de l'Anneau des gestionnaires.
À un niveau superficiel, c'est le "pourquoi" question. Pour aller un peu plus loin, nous allons jeter un oeil à la façon d'un Anneau de style app fonctions:
Une requête arrive et est transformé en Clojure carte en conformité avec l'Anneau spec.
Cette carte est canalisé dans un soi-disant "fonction de gestionnaire", qui est prévu pour produire une réponse (qui est aussi un Clojure carte).
La réponse de la carte est transformée en une véritable réponse HTTP et envoyé au client.
Étape 2. dans le ci-dessus est le plus intéressant, car il est le gestionnaire a la responsabilité d'examiner les URI utilisée dans la demande, examiner tous les cookies etc. et, finalement, arriver à une réponse appropriée. Clairement, il est nécessaire que tout ce travail soit pris en compte dans une collection bien définie par morceaux; il s'agit normalement d'une "base" de la fonction du gestionnaire et une collection de middleware fonctions d'emballage. Compojure but est de simplifier la génération de la base de la fonction du gestionnaire.
Comment?
Compojure est construit autour de la notion de "routes". Ces sont effectivement mis en œuvre à un niveau plus profond par le Poids de la bibliothèque (un spin-off de la Compojure projet, beaucoup de choses ont été déplacés vers des bibliothèques distinctes à la 0.3.x -> 0.4.x de transition). Un itinéraire est défini par (1) une méthode HTTP (GET, PUT, TÊTE...), (2) une URI de schéma (spécifié avec la syntaxe qui va apparemment être familier aux Webby Rubyists), (3) une déstructuration de formulaire utilisé dans la liaison des parties de la demande de carte de noms disponibles dans le corps, (4) un corps d'expressions qui doit produire un valide Anneau de réponse (non négligeable des cas, ce n'est généralement qu'un appel à une fonction distincte).
Cela pourrait être un bon point d'avoir un coup d'oeil à un exemple simple:
(def example-route (GET "/" [] "<html>...</html>"))
Nous allons tester cela à la REPL (à la demande de la carte ci-dessous est le minimum valable Anneau de demande de la carte):
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>"}
Si :request-method
ont :head
au lieu de cela, la réponse serait nil
. Nous allons revenir à la question de savoir ce qu' nil
signifie ici dans une minute (mais remarquez qu'il n'est pas valide d'un Anneau respose!).
Ainsi qu'il ressort de cet exemple, example-route
n'est qu'une fonction, et très simple; il ressemble à la demande, détermine s'il est intéressé par la gestion (en examinant :request-method
et :uri
) et, le cas échéant, renvoie un basic de la carte réponse.
Ce qui est aussi évident que le corps de l'itinéraire n'a pas vraiment besoin d'évaluer une réponse appropriée de la carte; Compojure fournit sane défaut de manipulation de chaînes de caractères (comme vu ci-dessus) et un certain nombre d'autres types d'objet; voir l' compojure.response/render
multimethod pour les détails (le code est entièrement auto-documentation ici).
Essayons defroutes
maintenant:
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
Les réponses à la requête affichée au-dessus et à sa variante :request-method :head
sont comme prévu.
Le fonctionnement interne de l' example-routes
sont tels que chaque parcours est essayé à son tour; dès que l'un d'entre eux renvoie à un non-nil
réponse, cette réponse devient la valeur de retour de l'ensemble de l' example-routes
gestionnaire. Pour la commodité, defroutes
-défini les gestionnaires sont enveloppés dans wrap-params
et wrap-cookies
implicitement.
Voici un exemple plus complexe:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
Remarque la déstructuration forme à la place de la déjà utilisé le vecteur vide. L'idée de base ici est que le corps de la route pourrait être intéressé par un peu d'information au sujet de la demande; puisqu'il arrive toujours dans la forme d'une carte, associatif, déstructuration formulaire peut être fourni pour en extraire des informations à partir de la demande et de la lier à des variables locales qui sera portée dans la voie du corps.
Un test de la ci-dessus:
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"}
Le brillant de suivi idée de ce qui précède est que de plus en plus complexe de routes peut - assoc
d'informations supplémentaires sur la demande lors de la phase de correspondance:
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
Cela répond avec un :body
de "foo"
à la demande de l'exemple précédent.
Deux choses sont de nouveau à propos de ce dernier exemple: l' "/:fst/*"
et la non-vide de liaison vecteur [fst]
. La première est celle-ci Rails-et-Sinatra-comme syntaxe d'URI modèles. C'est un peu plus sophistiqué que ce qui est évident à partir de l'exemple ci-dessus dans cette regex contraintes de l'URI segments sont pris en charge (par exemple, ["/:fst/*" :fst #"[0-9]+"]
peut être fourni pour faire la route accepter seulement tous les chiffres des valeurs de :fst
dans le ci-dessus). La deuxième est une façon simplifiée de la mise en correspondance sur l' :params
entrée dans la demande de carte, qui est lui-même une carte; il est utile pour l'extraction de l'URI segments de la demande, les paramètres de chaîne de requête et les paramètres de formulaire. Un exemple pour illustrer ce dernier point:
(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\"}"}
Ce serait un bon moment pour jeter un coup d'oeil à l'exemple de texte de la question:
(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>"))
Nous allons analyser chaque route à son tour:
(GET "/" [] (workbench))
-- lorsque vous traitez avec un GET
de la demande avec la :uri "/"
, appelez la fonction workbench
, et de rendre ce qu'il renvoie une réponse de la carte. (Rappelons que la valeur de retour peut être une carte, mais également une chaîne de caractères etc.)
(POST "/save" {form-params :form-params} (str form-params))
-- :form-params
est une entrée dans la demande de carte fournie par l' wrap-params
middleware (rappelons qu'il est implicitement inclus en defroutes
). La réponse sera la norme {:status 200 :headers {"Content-Type" "text/html"} :body ...}
avec (str form-params)
substitué ...
. (Un peu inhabituelle POST
gestionnaire,...)
(GET "/test" [& more] (str "<pre> more "</pre>"))
-- ce serait par exemple de retour d'écho à la représentation de chaîne de la carte, {"foo" "1"}
si l'agent utilisateur a demandé, pour "/test?foo=1"
.
(GET ["/:filename" :filename #".*"] [filename] ...)
-- :filename #".*"
partie ne fait rien du tout (depuis #".*"
toujours matches). Il appelle la Bague de fonction utilitaire ring.util.response/file-response
pour produire sa réponse; l' {:root "./static"}
partie lui indique où trouver le fichier.
(ANY "*" [] ...)
-- un fourre-tout de la route. Il est bon Compojure pratique de toujours inclure une telle route à la fin d'un defroutes
formulaire afin de s'assurer que le gestionnaire en cours de définition renvoie toujours valide Anneau de la carte réponse (rappelons qu'une correspondance de route de l'échec résultats en nil
).
Pourquoi de cette façon?
Un but de l'Anneau middleware est d'ajouter de l'information à la demande de la carte; ainsi cookie de manutention middleware ajoute un :cookies
- clés de la requête, wrap-params
ajoute :query-params
et/ou :form-params
si une chaîne de requête / formulaire de données et ainsi de suite. (Strictement parlant, toutes les informations que le middleware fonctions ajoutez doit être déjà présent dans la demande de carte, puisque c'est ce qu'il se passait; leur tâche est de le transformer pour qu'il soit plus commode de travailler avec les gestionnaires qu'elles enveloppent.) En fin de compte le "enrichi" demande est transmise à la base de gestionnaire, qui examine la demande de la carte avec tous les joliment prétraitées informations ajoutées par le middleware et produit une réponse. (Middleware peut faire des choses plus complexes que celle -- comme emballage de plusieurs "intérieure" des gestionnaires et de choisir entre elles, de décider de l'appel de la enveloppé gestionnaire(s) à tous les etc. C'est, cependant, en dehors de la portée de cette réponse.)
La base de gestionnaire, à son tour, est généralement (non en cas triviaux) une fonction qui tend à besoin de quelques éléments d'informations sur la demande. (E. g. ring.util.response/file-response
ne se soucie pas plus de la demande; il a seulement besoin d'un nom de fichier.) D'où la nécessité d'un moyen simple d'extraire seulement les parties pertinentes d'un Anneau de demande. Compojure vise à fournir un objectif spécifique correspondant à un modèle de moteur, comme c'était le cas, ce qui ne fait que cela.