(defparameter *web* (make-instance '<app>)) @route GET „/“(defun index () (render #P"index.tmpl")) @route GET „/hello“(defun say-hello (&key (|name| „Guest“)) (Format nil „Hallo, ~A“ |Name|))
Alles. Caveman2 wurde von Grund auf neu geschrieben.
Das sind bemerkenswerte Punkte.
Basiert auf Ningle
Verfügt über eine Datenbankintegration
Verwendet neues, separates Konfigurationssystem (Envy)
Hat neues Routing-Makro
Eine der am häufigsten gestellten Fragen war „Was soll ich verwenden: Ningle oder Caveman? Was sind die Unterschiede?“ Ich denke, diese Fragen wurden so oft gestellt, weil Caveman und Ningle zu ähnlich waren. Beide heißen „Micro“ und hatten keine Datenbankunterstützung.
Mit Caveman2 ist Caveman kein „Mikro“-Webanwendungs-Framework mehr. Es unterstützt CL-DBI und verfügt standardmäßig über eine Datenbankverbindungsverwaltung. Caveman ist erwachsen geworden.
Caveman soll eine Sammlung gemeinsamer Teile von Webanwendungen sein. Bei Caveman2 verwende ich drei Regeln, um Entscheidungen zu treffen:
Seien Sie erweiterbar.
Seien Sie praktisch.
Erzwinge nichts.
Du bist hierher gekommen, weil du daran interessiert bist, wie ein Höhlenmensch zu leben, oder? Das ist zwar nicht Disneyland, aber wir können hier anfangen. Lass uns in eine Höhle gehen!
Caveman2 ist jetzt auf Quicklisp verfügbar.
(ql:quickload :caveman2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Ihr vollständiger Name>");-> writing /path/to/myapp/.gitignore; Schreiben von /path/to/myapp/README.markdown; schreiben /path/to/myapp/app.lisp; Schreiben von /path/to/myapp/db/schema.sql; Schreiben von /path/to/myapp/shlyfile.lisp; Schreiben von /path/to/myapp/myapp-test.asd; schreiben /path/to/myapp/myapp.asd; Schreiben von /path/to/myapp/src/config.lisp; Schreiben von /path/to/myapp/src/db.lisp; Schreiben von /path/to/myapp/src/main.lisp; Schreiben von /path/to/myapp/src/view.lisp; schreiben /path/to/myapp/src/web.lisp; Schreiben von /path/to/myapp/static/css/main.css; schreiben /path/to/myapp/t/myapp.lisp; Schreiben von /path/to/myapp/templates/_errors/404.html; Schreiben von /path/to/myapp/templates/index.tmpl; Schreiben von /path/to/myapp/templates/layout/default.tmpl
Dies ist ein Beispiel, das davon ausgeht, dass der Name Ihrer Anwendung „myapp“ ist. Bevor Sie den Server starten, müssen Sie zunächst Ihre App laden.
(ql:quickload:myapp)
Ihre Anwendung verfügt über Funktionen namens start
und stop
um Ihre Webanwendung zu starten/stoppen.
(myapp:start:port 8080)
Da Caveman auf Clack/Lack basiert, können Sie wählen, auf welchem Server Sie es ausführen möchten – Hunchentoot, Woo oder Wookie usw.
(myapp:start:server:hunchentoot:port 8080) (myapp:start:server:fcgi:port 8080)
Ich empfehle Ihnen, Hunchentoot auf einem lokalen Computer und Woo in einer Produktionsumgebung zu verwenden.
Sie können Ihre Anwendung auch mit dem Befehl clackup starten.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 bietet zwei Möglichkeiten, eine Route zu definieren – @route
und defroute
. Sie können beides verwenden.
@route
ist ein Annotationsmakro, das mithilfe von cl-annot definiert wird. Es benötigt eine Methode, einen URL-String und eine Funktion.
@route GET „/“(defun index () ...);; Eine Route ohne Namen.@route GET "/welcome"(lambda (&key (|name| "Guest")) (Format nil „Willkommen, ~A“ |Name|))
Dies ähnelt der @url
von Caveman1, mit Ausnahme der Argumentliste. Sie müssen kein Argument angeben, wenn es nicht erforderlich ist.
defroute
ist nur ein Makro. Es bietet die gleiche Funktionalität wie @route
.
(Defroute-Index „/“ () ...);; Eine Route ohne Namen. (defroute „/welcome“ (&key (|name| „Guest“)) (Format nil „Willkommen, ~A“ |Name|))
Da Caveman auf Ningle basiert, verfügt Caveman auch über das Sinatra-ähnliche Routing-System.
;; GET-Anfrage (Standard)@route GET „/“ (lambda () ...) (defroute ("/" :method :GET) () ...);; POST request@route POST „/“ (lambda() ...) (defroute ("/" :method :POST) () ...);; PUT request@route PUT „/“ (lambda() ...) (defroute ("/" :method :PUT) () ...);; DELETE request@route DELETE „/“ (lambda() ...) (defroute ("/" :method :DELETE) () ...);; OPTIONEN request@route OPTIONS „/“ (lambda() ...) (defroute ("/" :method :OPTIONS) () ...);; Für alle Methoden@route ANY „/“ (lambda() ...) (defroute ("/" :method :ANY) () ...)
Routenmuster können „Schlüsselwörter“ enthalten, um den Wert in das Argument einzufügen.
(Defroute „/hello/:name“ (&Schlüsselname) (Format nil „Hallo, ~A“ Name))
Der obige Controller wird aufgerufen, wenn Sie auf „/hello/Eitaro“ oder „/hello/Tomohiro“ zugreifen, und name
lautet je nach Bedarf „Eitaro“ oder „Tomohiro“.
(&key name)
ist fast dasselbe wie eine Lambda-Liste von Common Lisp, außer dass immer andere Schlüssel zugelassen sind.
(defroute "/hello/:name" (&rest params &key name) ;; ... )
Routenmuster können auch „Platzhalter“-Parameter enthalten. Sie sind über splat
zugänglich.
(defroute „/say/*/to/*“ (&key splat) ; entspricht /say/hello/to/world (format nil „~A“ splat));=> (hello world)(defroute „/download/*.*“ (&key splat) ; entspricht /download/path/to/file.xml (Format ohne „~A“ Splat)) ;=> (Pfad/zu/Datei xml)
Wenn Sie einen regulären Ausdruck in einer URL-Regel verwenden möchten, sollte :regexp t
funktionieren.
(defroute ("/hello/([w]+)" :regexp t) (&key Captures) (Format nil „Hallo, ~A!“ (erste Aufnahmen)))
Normalerweise werden Routen in der Reihenfolge, in der sie definiert wurden, auf eine Übereinstimmung getestet, und nur die erste gefundene Route wird aufgerufen, während die folgenden Routen ignoriert werden. Eine Route kann jedoch weiterhin auf Übereinstimmungen in der Liste prüfen, indem sie next-route
einschließt.
(defroute „/guess/:who“ (&key who) (if (string= who „Eitaro“) „You got me!“ (next-route))) (defroute „/guess/*“ () „Du hast es verpasst!“)
Als Ergebnis von defroute
können Sie folgende Formate zurückgeben.
Zeichenfolge
Pfadname
Clacks Antwortliste (mit Status, Headern und Text)
Mit (redirect "url")
auf eine andere Route umleiten. Ein zweites optionales Argument ist der Statuscode, standardmäßig 302.
Wenn Sie Routen mit Namen definiert haben, können Sie die URL anhand eines Namens mit (url-for route-name &rest params)
finden.
Die Funktion gibt einen Fehler aus, wenn keine Route gefunden wird.
Siehe auch:
add-query-parameters base-url params
Parameterschlüssel, die eckige Klammern („[“ & „]“) enthalten, werden als strukturierte Parameter geparst. Sie können auf die geparsten Parameter als _parsed
in Routern zugreifen.
<form action="/edit"> <input type="name" name="person[name]" /> <input type="name" name="person[email]" /> <input type="name" name="person[birth][year ]" /> <input type="name" name="person[birth][month]" /> <input type="name" name="person[birth][day]" /></form>
(defroute „/edit“ (&key _parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("Geburt" . (("Jahr" . 2000) ("Monat" . 1) ("Tag" . 1))))";; Mit assoc-utils(ql:quickload :assoc-utils) (import 'assoc-utils:aget) (defroute „/edit“ (&key _parsed) (Format nil „~S“ (aget _parsed „person“)))
Leere Schlüssel bedeuten, dass sie mehrere Werte haben.
<form action="/add"> <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>
(defroute „/add“ (&key _parsed) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("name" . "WiiU") ("price" . "30000")) ((" Name“ . „PS4“) („Preis“ . „69000“)))“
Caveman verwendet Djula als Standard-Template-Engine.
{% erweitert "layouts/default.html" %} {% block title %}Benutzer | MyApp{% endblock %} {% Blockinhalt %}<div id="main"> <ul> {% für Benutzer in Benutzern %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(import 'myapp.view:render) (render #P"users.html"'(:users ((:url "/id/1" :name "nitro_idiot") (:url „/id/2“ :name „meymao“)) :has-next-page T))
Wenn Sie etwas aus einer Datenbank abrufen oder eine Funktion mit Djula ausführen möchten, müssen Sie beim Übergeben der Argumente an render explizit list
aufrufen, damit der Code ausgeführt wird.
(import 'myapp.view:render) (render #P"users.html"(list :users (get-users-from-db)))
Dies ist ein Beispiel für eine JSON-API.
(defroute „/user.json“ (&key |id|) (let ((person (find-person-from-db |id|)));; person => (:|name| "Eitaro Fukamachi" :|email| "[email protected]")(render- json person)));=> {"name": "Eitaro Fukamachi", "email": "[email protected]"}
render-json
ist Teil eines Skelettprojekts. Sie finden den Code in „src/view.lisp“.
Bilder, CSS, JS, favicon.ico und robot.txt im Verzeichnis „static/“ werden standardmäßig bereitgestellt.
/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
Sie können diese Regeln ändern, indem Sie „PROJECT_ROOT/app.lisp“ umschreiben. Weitere Informationen finden Sie unter Clack.Middleware.Static.
Caveman übernimmt Envy als Konfigurationsumschalter. Dies ermöglicht die Definition mehrerer Konfigurationen und das Umschalten zwischen ihnen entsprechend einer Umgebungsvariablen.
Dies ist ein typisches Beispiel:
(defpackage:myapp.config (:use :cl:envy)) (im Paket: myapp.config) (setf (config-env-var) „APP_ENV“) (defconfig :common `(:application-root ,(asdf:component-pathname (asdf:find-system :myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db"*application-root*))))) (defconfig |produktion| '(:databases((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb:mysql:Datenbankname „Jobs“:Benutzername „whoami“:Passwort „1234“)))) (defconfig |staging| `(:debug T,@|produktion|))
Jede Konfiguration ist eine Eigenschaftsliste. Sie können die zu verwendende Konfiguration auswählen, indem Sie APP_ENV
festlegen.
Um einen Wert aus der aktuellen Konfiguration zu erhalten, rufen Sie myapp.config:config
mit dem gewünschten Schlüssel auf.
(import 'myapp.config:config) (setf (osicat:environment-variable „APP_ENV“) „development“) (config :debug);=> T
Wenn Sie :databases
zur Konfiguration hinzufügen, aktiviert Caveman die Datenbankunterstützung. :databases
ist eine Zuordnungsliste von Datenbankeinstellungen.
(defconfig |produktion| '(:databases((:maindb :mysql :database-name "myapp" :username "whoami" :password "1234") (:workerdb:mysql:Datenbankname „Jobs“:Benutzername „whoami“:Passwort „1234“))))
db
in einem Paket myapp.db
ist eine Funktion zum Herstellen einer Verbindung zu allen oben konfigurierten Datenbanken. Hier ist ein Beispiel.
(use-package '(:myapp.db :sxql :datafly)) (defun search-adults () (mit-Verbindung (db) (Alle abrufen (wählen Sie:*(von:Person) (wobei (:>= :Alter 20))))))
Die Verbindung bleibt während der Lisp-Sitzung bestehen und wird in jeder HTTP-Anfrage wiederverwendet.
retrieve-all
und die Abfragesprache kamen von Datafly und SxQL. Weitere Informationen finden Sie in diesen Dokumentationssätzen.
Während einer HTTP-Anfrage stehen mehrere spezielle Variablen zur Verfügung. *request*
und *response*
repräsentieren eine Anfrage und eine Antwort. Wenn Sie mit Clack vertraut sind, handelt es sich dabei um Instanzen von Unterklassen von Clack.Request und Clack.Response.
(use-package :caveman2);; Rufen Sie den Wert Referer header ab.(http-referer *request*);; Content-Type-Header festlegen.(setf (getf (response-headers *response*) :content-type) "application/json");; HTTP-Status festlegen.(setf (status *response*) 304)
Wenn Sie den Inhaltstyp „application/json“ für alle „*.json“-Anfragen festlegen möchten, kann next-route
verwendet werden.
(defroute „/*.json“ () (setf (getf (response-headers *response*) :content-type) „application/json“) (nächste Route)) (defroute „/user.json“ () ...) (defroute „/search.json“ () ...) (defroute ("/new.json" :method :POST) () ...)
Sitzungsdaten dienen der Speicherung benutzerspezifischer Daten. *session*
ist eine Hash-Tabelle, die Sitzungsdaten speichert.
In diesem Beispiel wird :counter
in der Sitzung erhöht und für jeden Besucher angezeigt.
(defroute „/counter“ () (Format nil „Du bist ~A mal hierher gekommen.“ (incf (gethash :counter *session* 0))))
Caveman2 speichert Sitzungsdaten standardmäßig im Speicher. Um dies zu ändern, geben Sie :store
to :session
in „PROJECT_ROOT/app.lisp“ an.
In diesem Beispiel wird RDBMS zum Speichern von Sitzungsdaten verwendet.
'(:backtrace :output (getf (config) :error-log)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (wenn (Produktionp) Null (Lambda (App)
HINWEIS: Vergessen Sie nicht :lack-session-store-dbi
als :depends-on
Ihrer App hinzuzufügen. Es ist kein Teil von Clack/Lack.
Weitere Informationen finden Sie im Quellcode von Lack.Session.Store.DBi.
Fehlt.Session.Store.Dbi
(importiere 'caveman2:throw-code) (defroute ("/auth" :method :POST) (&key |name| |password|) (es sei denn (autorisieren |Name| |Passwort|) (Wurfcode 403)))
Um Fehlerseiten für 404, 500 oder ähnliches anzugeben, definieren Sie eine Methode on-exception
Ihrer App.
(defmethod on-Exception ((app <web>) (code (eql 404))) (deklarieren (App-Code ignorieren)) (merge-pathnames #P"_errors/404.html" *template-directory*))
Obwohl Caveman keine Funktion für die Hot-Bereitstellung bietet, macht es Server::Starter – ein Perl-Modul – einfach.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
HINWEIS: Server::Starter erfordert, dass der Server die Bindung an einen bestimmten fd unterstützt, was bedeutet, dass nur :fcgi
und :woo
mit dem Befehl start_server
funktionieren.
Um den Server neu zu starten, senden Sie das HUP-Signal ( kill -HUP <pid>
) an den start_server
-Prozess.
Caveman gibt Fehlerrückverfolgungen in eine Datei aus, die in Ihrer Konfiguration unter :error-log
angegeben ist.
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"* Anwendungsstammverzeichnis*)))))
(import 'cl-who:with-html-output-to-string) (defroute „/“ () (with-html-output-to-string (output nil :prologue t) (:html (:head (:title „Willkommen bei Caveman!“)) (:body "Blah blah blah."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Willkommen bei Caveman!</title></head><body>Blah bla bla.</body></html>"
CL-WHO-Website
(import 'cl-markup:xhtml) (defroute „/“ () (xhtml (:head (:title „Willkommen bei Caveman!“)) (:body "Blah blah blah.")));=> "<?xml version="1.0" binding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional/ /EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><title>Willkommen bei Höhlenmensch!</title></head><body>Blah bla bla.</body></html>"
CL-Markup-Repository
{Namespace myapp.view} {template renderIndex}<!DOCTYPE html><html><head> <title>"Willkommen bei Caveman!</title></head><body> Bla bla bla.</body></html>{/template}
(import 'myapp.config:*template-directory*) (closure-template:compile-cl-templates (merge-pathnames #P"index.tmpl"*template-directory*)) (defroute „/“ () (myapp.view:render-index))
cl-closure-template
Dokumentation zu Abschlussvorlagen
Clack – Webanwendungsumgebung.
Lack – Der Kern von Clack.
ningle – Super-Micro-Webanwendungs-Framework, auf dem Caveman basiert.
Djula – HTML-Templating-Engine.
CL-DBI – Datenbankunabhängige Schnittstellenbibliothek.
SxQL – SQL-Builder-Bibliothek.
Envy – Konfigurationsumschalter.
Roswell – Common Lisp-Implementierungsmanager.
Eitaro Fukamachi ([email protected])
Lizenziert unter der LLGPL-Lizenz.