(paramètre def *web* (make-instance '<app>)) @route GET "/"(index défun () (rendu #P"index.tmpl")) @route GET "/hello"(defun say-hello (&key (|name| "Invité")) (format nul "Bonjour, ~A" |nom|))
Tout. Caveman2 a été écrit à partir de zéro.
Ce sont des points remarquables.
Est basé sur ningle
A une intégration de base de données
Utilise un nouveau système de configuration séparé (Envy)
A une nouvelle macro de routage
L'une des questions les plus fréquemment posées était « Lequel dois-je utiliser : ningle ou Caveman ? Quelles sont les différences ? » Je pense que ces questions ont été posées si fréquemment parce que Caveman et Ningle étaient trop similaires. Les deux sont appelés « micro » et ne prennent pas en charge la base de données.
Avec Caveman2, Caveman n'est plus un framework d'application web « micro ». Il prend en charge CL-DBI et dispose par défaut d'une gestion des connexions à la base de données. Caveman a commencé à grandir.
Caveman est destiné à être un ensemble de parties communes d'applications Web. Avec Caveman2, j'utilise trois règles pour prendre des décisions :
Soyez extensible.
Soyez pratique.
Ne forcez rien.
Vous êtes venu ici parce que vous souhaitez vivre comme un homme des cavernes, n'est-ce pas ? Ce n'est pas Disneyland, mais nous pouvons commencer ici. Entrons dans une grotte !
Caveman2 est maintenant disponible sur Quicklisp.
(ql: chargement rapide : homme des cavernes2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Votre nom complet>");-> écriture de /path/to/myapp/.gitignore; écrire /path/to/myapp/README.markdown ; écrire /path/to/myapp/app.lisp ; écrire /path/to/myapp/db/schema.sql ; écrire /path/to/myapp/shlyfile.lisp ; écrire /path/to/myapp/myapp-test.asd ; écrire /path/to/myapp/myapp.asd ; écrire /path/to/myapp/src/config.lisp ; écrire /path/to/myapp/src/db.lisp ; écrire /path/to/myapp/src/main.lisp ; écrire /path/to/myapp/src/view.lisp ; écrire /path/to/myapp/src/web.lisp ; écrire /path/to/myapp/static/css/main.css ; écrire /path/to/myapp/t/myapp.lisp ; écrire /path/to/myapp/templates/_errors/404.html ; écrire /path/to/myapp/templates/index.tmpl ; écrire /path/to/myapp/templates/layout/default.tmpl
Ceci est un exemple qui suppose que le nom de votre application est « myapp ». Avant de démarrer le serveur, vous devez d'abord charger votre application.
(ql: chargement rapide : mon application)
Votre application dispose de fonctions nommées start
et stop
pour démarrer/arrêter votre application web.
(monapplication : démarrer :port 8080)
Comme Caveman est basé sur Clack/Lack, vous pouvez choisir sur quel serveur exécuter : Hunchentoot, Woo ou Wookie, etc.
(monapplication : démarrer : serveur : hunchentoot : port 8080) (monapplication:start :server :fcgi :port 8080)
Je vous recommande d'utiliser Hunchentoot sur une machine locale et d'utiliser Woo dans un environnement de production.
Vous pouvez également démarrer votre application en utilisant la commande clacup.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 propose 2 façons de définir une route -- @route
et defroute
. Vous pouvez utiliser l'un ou l'autre.
@route
est une macro d'annotation, définie à l'aide de cl-annot. Il faut une méthode, une chaîne URL et une fonction.
@route GET "/"(index défun () ...);; Une route sans nom.@route GET "/welcome"(lambda (&key (|name| "Guest")) (format nul "Bienvenue, ~A" |nom|))
Ceci est similaire au @url
de Caveman1, à l'exception de sa liste d'arguments. Vous n'êtes pas obligé de spécifier un argument lorsqu'il n'est pas requis.
defroute
n'est qu'une macro. Il fournit les mêmes fonctionnalités que @route
.
(index de défroute "/" () ...);; Un itinéraire sans nom.(defroute "/welcome" (&key (|name| "Guest")) (format nul "Bienvenue, ~A" |nom|))
Puisque Caveman est basé sur ningle, Caveman dispose également du système de routage de type Sinatra.
;; Requête GET (par défaut)@route GET "/" (lambda () ...) (defroute ("/" :method :GET) () ...);; Requête POST@route POST "/" (lambda () ...) (defroute ("/" :method :POST) () ...);; PUT requête@route PUT "/" (lambda () ...) (defroute ("/" :method :PUT) () ...);; DELETE request@route DELETE "/" (lambda () ...) (defroute ("/" :method :DELETE) () ...);; OPTIONS request@route OPTIONS "/" (lambda () ...) (defroute ("/" :method :OPTIONS) () ...);; Pour toutes les méthodes@route ANY "/" (lambda () ...) (defroute ("/" :méthode :ANY) () ...)
Les modèles de route peuvent contenir des « mots-clés » pour mettre la valeur dans l'argument.
(défroutez "/hello/:name" (&nom de la clé) (format nul "Bonjour, ~A" nom))
Le contrôleur ci-dessus sera invoqué lorsque vous accéderez à "/hello/Eitaro" ou "/hello/Tomohiro", et name
sera "Eitaro" ou "Tomohiro", selon le cas.
(&key name)
est presque identique à une liste lambda de Common Lisp, sauf qu'elle autorise toujours d'autres clés.
(defroute "/hello/:name" (&rest params &key name) ;; ... )
Les modèles de route peuvent également contenir des paramètres « génériques ». Ils sont accessibles en utilisant splat
.
(défroute "/say/*/to/*" (&key splat) ; correspond à /say/hello/to/world (format nul "~A" splat));=> (hello world)(defroute "/download/*.*" (&key splat) ; correspond à /download/path/to/file.xml (format nul "~A" splat)) ;=> (chemin/vers/fichier XML)
Si vous souhaitez écrire et utiliser une expression régulière dans une règle d'URL, :regexp t
devrait fonctionner.
(defroute ("/hello/([w]+)" :regexp t) (&captures de touches) (format nul "Bonjour, ~A!" (premières captures)))
Normalement, les routes sont testées pour une correspondance dans l'ordre dans lequel elles sont définies, et seule la première route correspondante est invoquée, les routes suivantes étant ignorées. Cependant, une route peut continuer à tester les correspondances dans la liste, en incluant next-route
.
(défroutez "/devinez/:who" (&key who) (if (string= who "Eitaro") "Tu m'as eu !" (itinéraire suivant))) (defroute "/guess/*" () "Tu as raté !")
Vous pouvez renvoyer les formats suivants à la suite de defroute
.
Chaîne
Nom du chemin
Liste de réponses de Clack (contenant le statut, les en-têtes et le corps)
Redirigez vers un autre itinéraire avec (redirect "url")
. Un deuxième argument facultatif est le code d'état, 302 par défaut.
Lorsque vous avez défini des itinéraires avec des noms, vous pouvez trouver l'URL à partir d'un nom avec (url-for route-name &rest params)
.
La fonction générera une erreur si aucune route n'est trouvée.
Voir aussi :
add-query-parameters base-url params
Les clés de paramètres contenant des crochets ("[" & "]") seront analysées en tant que paramètres structurés. Vous pouvez accéder aux paramètres analysés en tant que _parsed
dans les routeurs.
<formulaire d'action="/edit"> <input type="name" name="person[name]" /> <input type="name" name="person[email]" /> <input type="name" name="personne[naissance][année ]" /> <input type="name" name="personne[naissance][mois]" /> <input type="name" name="personne[naissance][jour]" /></form>
(défroutez "/edit" (&key _parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("naissance" . (("année" . 2000) ("mois" . 1) ("jour" . 1))))";; Avec assoc-utils(ql:quickload :assoc-utils) (importer 'assoc-utils:aget) (défroutez "/edit" (&key _parsed) (format nul "~S" (aget _parsed "personne")))
Les clés vides signifient qu'elles ont plusieurs valeurs.
<formulaire d'action="/ajouter"> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="text" name="items[ ][name]" /> <input type="text" name="items[][price]" /> <input type="submit" value="Add" /></form>
(défroutez "/add" (&key _parsed) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("nom" . "WiiU") ("prix" . "30000")) ((" nom" . "PS4") ("prix" . "69000")))"
Caveman utilise Djula comme moteur de création de modèles par défaut.
{% étend "layouts/default.html" %} {% block title %}Utilisateurs | MonApp{% endblock %} {% bloquer le contenu %}<div id="main"> <ul> {% pour l'utilisateur dans les utilisateurs %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(importer 'myapp.view:render) (render #P"users.html"'(:users ((:url "/id/1" :name "nitro_idiot") (:url "/id/2" :name "meymao")) :has-next-page T))
Si vous souhaitez obtenir quelque chose d'une base de données ou exécuter une fonction à l'aide de Djula, vous devez appeler explicitement list
lors du passage des arguments à restituer afin que le code s'exécute.
(importer 'myapp.view:render) (rendu #P"users.html"(list :users (get-users-from-db)))
Ceci est un exemple d'API JSON.
(défroutez "/user.json" (&key |id|) (let ((person (find-person-from-db |id|)));; person => (:|name| "Eitaro Fukamachi" :|email| "[email protected]") (render- json personne)));=> {"name": "Eitaro Fukamachi", "email": "[email protected]"}
render-json
fait partie d'un projet squelette. Vous pouvez trouver son code dans "src/view.lisp".
Les images, CSS, JS, favicon.ico et robot.txt dans le répertoire "static/" seront servis par défaut.
/images/logo.png => {PROJECT_ROOT}/static/images/logo.png /css/main.css => {PROJECT_ROOT}/static/css/main.css /js/app/index.js => {PROJECT_ROOT}/static/js/app/index.js /robot.txt => {PROJECT_ROOT}/static/robot.txt /favicon.ico => {PROJECT_ROOT}/static/favicon.ico
Vous pouvez modifier ces règles en réécrivant "PROJECT_ROOT/app.lisp". Voir Clack.Middleware.Static pour plus de détails.
Caveman adopte Envy comme sélecteur de configuration. Cela permet de définir plusieurs configurations et de basculer entre elles en fonction d'une variable d'environnement.
Ceci est un exemple typique :
(paquet par défaut : myapp.config (:use :cl:envy)) (dans le package :myapp.config) (setf (config-env-var) "APP_ENV") (defconfig : commun `(:racine-application,(asdf:nom-chemin-du-composant (asdf:find-system :myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db"*application-root*))))) (defconfig |production| '(:databases((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb :mysql :nom de la base de données "jobs" :nom d'utilisateur "whoami" :mot de passe "1234")))) (defconfig |staging| `(:debug T,@|production|))
Chaque configuration est une liste de propriétés. Vous pouvez choisir la configuration à utiliser en définissant APP_ENV
.
Pour obtenir une valeur de la configuration actuelle, appelez myapp.config:config
avec la clé souhaitée.
(importer 'myapp.config:config) (setf (osicat:variable d'environnement "APP_ENV") "développement") (config :debug);=> T
Lorsque vous ajoutez :databases
à la configuration, Caveman active la prise en charge des bases de données. :databases
est une liste d'associations de paramètres de base de données.
(defconfig |production| '(:databases((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb :mysql :nom de la base de données "jobs" :nom d'utilisateur "whoami" :mot de passe "1234"))))
db
dans un package myapp.db
est une fonction de connexion à chaque base de données configurée ci-dessus. Voici un exemple.
(use-package '(:myapp.db :sxql :datafly)) (défun recherche-adultes () (avec connexion (db) (récupérer tout (sélectionnez :*(de :personne) (où (:>= :âge 20))))))
La connexion est active pendant la session Lisp et sera réutilisée dans chaque requête HTTP.
retrieve-all
et le langage de requête provenait de datafly et SxQL. Consultez ces ensembles de documentation pour plus d’informations.
Plusieurs variables spéciales sont disponibles lors d'une requête HTTP. *request*
et *response*
représentent une demande et une réponse. Si vous connaissez Clack, ce sont des instances de sous-classes de Clack.Request et Clack.Response.
(use-package :caveman2);; Obtenez une valeur de l'en-tête Referer.(http-referer *request*);; Définir l'en-tête Content-Type.(setf (getf (response-headers *response*) :content-type) "application/json");; Définir le statut HTTP. (setf (status *response*) 304)
Si vous souhaitez définir le type de contenu « application/json » pour toutes les requêtes « *.json », next-route
peut être utilisée.
(défroutez "/*.json" () (setf (getf (response-headers *response*) :content-type) "application/json") (prochain itinéraire)) (défroutez "/user.json" ()...) (défroutez "/search.json" ()...) (defroute ("/new.json" :method :POST) () ...)
Les données de session servent à mémoriser des données spécifiques à l'utilisateur. *session*
est une table de hachage qui stocke les données de session.
Cet exemple incrémente :counter
dans la session et l'affiche pour chaque visiteur.
(dérouter "/counter" () (format nul "Vous êtes venu ici ~A fois." (incf (gethash :counter *session* 0))))
Caveman2 stocke les données de session en mémoire par défaut. Pour changer cela, spécifiez :store
à :session
dans "PROJECT_ROOT/app.lisp".
Cet exemple utilise un SGBDR pour stocker les données de session.
'(:retrace :output (getf (config) :erreur-log)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (si (productionp) néant (lambda (application)
REMARQUE : n'oubliez pas d'ajouter :lack-session-store-dbi
comme :depends-on
de votre application. Il ne fait pas partie de Clack/Lack.
Voir le code source de Lack.Session.Store.DBi pour plus d'informations.
Manque.Session.Store.Dbi
(importer 'caveman2:throw-code) (defroute ("/auth" :méthode :POST) (&clé |nom| |mot de passe|) (sauf si (autoriser |nom| |mot de passe|) (code de jet 403)))
Pour spécifier les pages d'erreur pour 404, 500 ou autre, définissez une méthode on-exception
de votre application.
(defmethod sur exception ((app <web>) (code (eql 404))) (déclarer (ignorer le code de l'application)) (noms de chemin de fusion #P"_errors/404.html" *répertoire-modèle*))
Bien que Caveman ne dispose pas de fonctionnalité de déploiement à chaud, Server::Starter -- un module Perl -- facilite la tâche.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
REMARQUE : Server::Starter nécessite que le serveur prenne en charge la liaison sur un fd spécifique, ce qui signifie que seuls :fcgi
et :woo
fonctionnent avec la commande start_server
.
Pour redémarrer le serveur, envoyez le signal HUP ( kill -HUP <pid>
) au processus start_server
.
Caveman génère des traces d'erreur dans un fichier spécifié dans :error-log
dans votre configuration.
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"* racine-application*)))))
(importer 'cl-who:with-html-output-to-string) (dérouter "/" () (with-html-output-to-string (sortie nulle :prologue t) (:html (:head (:title "Bienvenue chez Caveman !")) (:body "Blah bla bla."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Bienvenue à Caveman !</title></head><body>Blah bla bof.</body></html>"
Site Web CL-OMS
(importer 'cl-markup:xhtml) (dérouter "/" () (xhtml (:head (:title "Bienvenue chez Caveman!")) (:body "Blah bla bla."));=> "<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/ /FR" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><title>Bienvenue sur Homme des cavernes !</title></head><body>Blah bla bla.</body></html>"
Référentiel CL-Markup
{espace de noms myapp.view} {template renderIndex}<!DOCTYPE html><html><head> <title>"Bienvenue à Caveman !</title></head><body> Bla bla bla.</body></html>{/template}
(importer 'myapp.config:*template-directory*) (modèle de fermeture : compile-cl-templates (noms de chemin de fusion #P"index.tmpl"*répertoire-modèle*)) (dérouter "/" () (monapp.view:render-index))
modèle de fermeture cl
Documentation sur les modèles de clôture
Clack - Environnement d'applications Web.
Manque - Le noyau de Clack.
ningle - Cadre d'application super micro web sur lequel Caveman est basé.
Djula - Moteur de création de modèles HTML.
CL-DBI - Bibliothèque d'interface indépendante de la base de données.
SxQL - Bibliothèque de création SQL.
Envy - Sélecteur de configuration.
Roswell - Responsable de l'implémentation de Common Lisp.
Eitaro Fukamachi ([email protected])
Sous licence LLGPL.