112 votes

Ce qui ' s la « grande idée » derrière compojure routes ?

Je suis nouveau sur Clojure et ont été à l'aide de Compojure d'écrire une application web de base. Je suis de frapper un mur avec Compojure de l' defroutes de la syntaxe, bien que, et je pense que j'ai besoin de comprendre à la fois le "comment" et le "pourquoi" derrière tout ça.

Il semble comme un Anneau de style de l'application commence par une requête HTTP de la carte, puis juste transmet la demande à travers une série de middleware fonctions jusqu'à ce qu'il se transforme en une réponse de la carte, qui est envoyée au navigateur. Ce style semble trop "bas niveau" pour les développeurs, donc le besoin d'un outil comme Compojure. Je peux voir ce besoin de plus d'abstractions dans d'autres logiciels écosystèmes, notamment avec Python WSGI.

Le problème est que je ne comprends pas Compojure de l'approche. Prenons l'suivantes, defroutes S-expression:

(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>"))

Je sais que la clé de la compréhension de tout cela se trouve à l'intérieur de certains macro vaudou, mais je ne suis pas totalement comprendre les macros (encore). J'ai regardé la defroutes source pendant une longue période, mais n'ont tout simplement pas l'obtenir! Ce qui se passe ici? La compréhension de la "grande idée" vont sans doute m'aider à répondre à ces questions:

  1. Comment puis-je accéder à l'Anneau de l'environnement à partir de l'intérieur d'un acheminé fonction (par exemple, l' workbench de la fonction)? Par exemple, dire que je voulais accéder à la HTTP_ACCEPT en-têtes ou une autre partie de la demande/middleware?
  2. Quel est le problème avec la déstructuration ({form-params :form-params})? Quels mots-clés sont disponibles pour moi lors de la déstructuration?

J'aime vraiment Clojure, mais je suis perplexe!

216voto

Michał Marczyk Points 54179

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:

  1. Une requête arrive et est transformé en Clojure carte en conformité avec l'Anneau spec.

  2. 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).

  3. 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:

  1. (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.)

  2. (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,...)

  3. (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".

  4. (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.

  5. (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.

4voto

Pieter Breed Points 2288

Pour quelqu'un qui a toujours eu du mal à trouver ce qui se passe sur les routes, c'est peut être que, comme moi, vous ne comprenez pas l'idée de déstructuration.

En fait la lecture de la documentation pour let a aidé à éclaircir l'ensemble "où faire de la magie des valeurs?" question.

Je suis coller les sections pertinentes ci-dessous:

Clojure prend en charge abstrait structurels de liaison, souvent appelé déstructuration, en liaison let listes fn paramètre les listes, et une macro qui se développe en un let ou fn. L'idée de base est qu'un liaison-forme peut être une structure de données littéral contenant des symboles qui se lié aux différentes parties de la init-expr. La liaison est résumé dans le qu'un vecteur littérale peut se lier à tout ce qui est séquentielle, tandis qu'un carte littérale peut se lier à tout ce qui est associative.

Vecteur de liaison-exprs vous permettent de lier noms des pièces de la séquentielles des choses (et pas seulement des vecteurs), comme des vecteurs, listes, seqs, de chaînes, de tableaux, et quelque chose qui prend en charge la nième. La base séquentielle forme est un vecteur de liaison-formes, qui sera lié à éléments successifs de la init-expr, regarda par la nième. Dans plus, et, éventuellement, & suivi par une liaison-formes ferai en sorte que liaison-forme d'être lié à la reste de la séquence, c'est à dire que la partie non encore lié, regardé par nthnext . Enfin, également en option, : suivi par un symbole de la cause que symbole à être lié à l'ensemble de la init-expr:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

Vecteur de liaison-exprs vous permettent de lier noms des pièces de la séquentielles des choses (et pas seulement des vecteurs), comme des vecteurs, listes, seqs, de chaînes, de tableaux, et quelque chose qui prend en charge la nième. La base séquentielle forme est un vecteur de liaison-formes, qui sera lié à éléments successifs de la init-expr, regarda par la nième. Dans plus, et, éventuellement, & suivi par une liaison-formes ferai en sorte que liaison-forme d'être lié à la reste de la séquence, c'est à dire que la partie non encore lié, regardé par nthnext . Enfin, également en option, : suivi par un symbole de la cause que symbole à être lié à l'ensemble de la init-expr:

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])
->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

3voto

nickik Points 3112

1voto

noisesmith Points 8407

Quel est le problème avec la déstructuration ({form-params :forme-params})? Quels mots-clés sont disponibles pour moi lors de la déstructuration?

Les touches disponibles sont ceux qui sont dans l'entrée de la carte. Déstructuration est disponible à l'intérieur de la location et macro formes, ou à l'intérieur des paramètres de fn ou défn

Le code suivant, nous l'espérons être instructif:

(let [{a :thing-a
       c :thing-c :as things} {:thing-a 0
                               :thing-b 1
                               :thing-c 2}]
  [a c (keys things)])

=> [0 2 (:thing-b :thing-a :thing-c)]

un exemple plus complexe, montrant imbriquée déstructuration:

user> (let [{thing-id :id
             {thing-color :color :as props} :properties} {:id 1
                                                          :properties {:shape
                                                                       "square"
                                                                       :color
                                                                       0xffffff}}]
            [thing-id thing-color (keys props)])
=> [1 16777215 (:color :shape)]

Lorsqu'ils sont utilisés judicieusement, déstructuration declutters votre code en évitant standard d'accès aux données. en utilisant :et imprimer le résultat (ou le résultat de touches) vous pouvez obtenir une meilleure idée de ce que les autres données, vous pourriez avoir accès.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X