(parámetro def *web* (make-instance '<aplicación>)) @ruta GET "/"(índice defun () (renderizar #P"index.tmpl")) @route GET "/hola"(defun decir-hola (&clave (|nombre| "Invitado")) (formato nulo "Hola, ~A" |nombre|))
Todo. Caveman2 fue escrito desde cero.
Estos son puntos dignos de mención.
Se basa en ningle
Tiene integración de base de datos
Utiliza un sistema de configuración nuevo e independiente (Envy)
Tiene nueva macro de enrutamiento
Una de las preguntas más frecuentes fue "¿Cuál debo usar: ningle o Caveman? ¿Cuáles son las diferencias?" Creo que estas preguntas se hicieron con tanta frecuencia porque Caveman y Ningle eran demasiado similares. Ambos se denominan "micro" y no tenían soporte de base de datos.
Con Caveman2, Caveman ya no es un marco de aplicación web "micro". Es compatible con CL-DBI y tiene gestión de conexión de base de datos de forma predeterminada. El hombre de las cavernas ha empezado a crecer.
Caveman pretende ser una colección de partes comunes de aplicaciones web. En Caveman2 utilizo tres reglas para tomar decisiones:
Ser extensible.
Sea práctico.
No fuerces nada.
Viniste aquí porque te interesa vivir como un cavernícola, ¿verdad? Esto no es Disneylandia, pero podemos empezar aquí. ¡Entrémonos en una cueva!
Caveman2 ya está disponible en Quicklisp.
(ql: carga rápida: cavernícola2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Su nombre completo>");-> escribiendo /path/to/myapp/.gitignore; escribiendo /ruta/a/miaplicación/README.markdown; escribiendo /ruta/a/miaplicación/app.lisp; escribiendo /ruta/a/miaplicación/db/schema.sql; escribiendo /ruta/a/miaplicación/shlyfile.lisp; escribiendo /ruta/a/miaplicación/miaplicación-test.asd; escribiendo /ruta/a/miaplicación/miaplicación.asd; escribiendo /ruta/a/miaplicación/src/config.lisp; escribiendo /ruta/a/miaplicación/src/db.lisp; escribiendo /ruta/a/miaplicación/src/main.lisp; escribiendo /ruta/a/miaplicación/src/view.lisp; escribiendo /ruta/a/miaplicación/src/web.lisp; escribiendo /ruta/a/miaplicación/static/css/main.css; escribiendo /ruta/a/miaplicación/t/miaplicación.lisp; escribiendo /ruta/a/miaplicación/templates/_errors/404.html; escribiendo /ruta/a/miaplicación/templates/index.tmpl; escribiendo /ruta/a/miaplicación/templates/layout/default.tmpl
Este es un ejemplo que supone que el nombre de su aplicación es "myapp". Antes de iniciar el servidor, primero debes cargar tu aplicación.
(ql: carga rápida: mi aplicación)
Su aplicación tiene funciones llamadas start
y stop
para iniciar/detener su aplicación web.
(mi aplicación: inicio: puerto 8080)
Como Caveman se basa en Clack/Lack, puedes elegir en qué servidor ejecutar: Hunchentoot, Woo o Wookie, etc.
(miaplicación:inicio:servidor:hunchentoot:puerto 8080) (miaplicación:inicio:servidor:fcgi:puerto 8080)
Le recomiendo que use Hunchentoot en una máquina local y use Woo en un entorno de producción.
También puede iniciar su aplicación usando el comando clackup.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 proporciona 2 formas de definir una ruta: @route
y defroute
. Puedes usar cualquiera de los dos.
@route
es una macro de anotación, definida mediante cl-annot. Se necesita un método, una cadena URL y una función.
@ruta GET "/"(índice defun () ...);; Una ruta sin nombre.@ruta GET "/bienvenido"(lambda (&key (|nombre| "Invitado")) (formato nulo "Bienvenido, ~A" |nombre|))
Esto es similar a @url
de Caveman1 excepto por su lista de argumentos. No es necesario especificar un argumento cuando no es necesario.
defroute
es sólo una macro. Proporciona la misma funcionalidad que @route
.
(índice de descongelación "/" () ...);; Una ruta sin nombre.(desfroute "/welcome" (&key (|nombre| "Invitado")) (formato nulo "Bienvenido, ~A" |nombre|))
Dado que Caveman se basa en ningle, Caveman también tiene el sistema de enrutamiento similar a Sinatra.
;; Solicitud GET (predeterminada)@ruta GET "/" (lambda () ...) (descongelar ("/" :método :GET) () ...);; Solicitud POST@ruta POST "/" (lambda()...) (descongelar ("/" :método :POST) () ...);; PUT solicitud@ruta PUT "/" (lambda()...) (descongelar ("/" :método :PUT) () ...);; ELIMINAR solicitud@ruta ELIMINAR "/" (lambda () ...) (descongelar ("/" :método :DELETE) () ...);; OPCIONES solicitud@ruta OPCIONES "/" (lambda()...) (descongelar ("/" :método :OPCIONES) () ...);; Para todos los métodos@ruta CUALQUIER "/" (lambda()...) (descongelar ("/" :método :CUALQUIER) () ...)
Los patrones de ruta pueden contener "palabras clave" para poner el valor en el argumento.
(eliminar "/hola/:nombre" (&nombre clave) (formato nulo "Hola, ~A" nombre))
El controlador anterior se invocará cuando acceda a "/hello/Eitaro" o "/hello/Tomohiro", y name
será "Eitaro" o "Tomohiro", según corresponda.
(&key name)
es casi igual que una lista lambda de Common Lisp, excepto que siempre permite otras claves.
(descongelar "/hola/:nombre" (&rest parámetros &nombre clave) ;; ... )
Los patrones de ruta también pueden contener parámetros "comodín". Son accesibles mediante splat
.
(descongelar "/say/*/to/*" (&key splat); coincide con /say/hello/to/world (formato nulo "~A" splat));=> (hola mundo)(defroute "/download/*.*" (&key splat); coincide con /download/path/to/file.xml (formato nulo "~A" splat)) ;=> (ruta/al/archivo xml)
Si desea escribir una expresión regular en una regla de URL, :regexp t
debería funcionar.
(descongelar ("/hola/([w]+)" :regexp t) (&capturas de claves) (formato nulo "¡Hola, ~A!" (primeras capturas)))
Normalmente, las rutas se prueban para detectar coincidencias en el orden en que se definen y solo se invoca la primera ruta que coincida, ignorando las siguientes. Sin embargo, una ruta puede continuar probando coincidencias en la lista, incluyendo next-route
.
(descongelar "/guess/:quién" (&clave quién) (if (string= who "Eitaro") "¡Me tienes!" (próxima ruta))) (descongelar "/guess/*" () "¡Te perdiste!")
Puede devolver los siguientes formatos como resultado de defroute
.
Cadena
Nombre de ruta
Lista de respuestas de Clack (que contiene estado, encabezados y cuerpo)
Redirigir a otra ruta con (redirect "url")
. Un segundo argumento opcional es el código de estado, 302 por defecto.
Cuando definió rutas con nombres, puede encontrar la URL de un nombre con (url-for route-name &rest params)
.
La función arrojará un error si no se encuentra ninguna ruta.
Ver también:
add-query-parameters base-url params
Las claves de parámetros que contienen corchetes ("[" y "]") se analizarán como parámetros estructurados. Puede acceder a los parámetros analizados como _parsed
en los enrutadores.
<formulario acción="/editar"> <tipo de entrada="nombre" nombre="persona[nombre]" /> <tipo de entrada="nombre" nombre="persona[correo electrónico]" /> <tipo de entrada="nombre" nombre="persona[nacimiento][año ]" /> <tipo de entrada="nombre" nombre="persona[nacimiento][mes]" /> <tipo de entrada="nombre" nombre="persona[nacimiento][día]" /></formulario>
(descongelar "/editar" (&key _parsed) (formato nulo "~S" (cdr (assoc "persona" _parsed :test #'string=))));=> "(("nombre". "Eitaro") ("correo electrónico". "e.arrows@gmail .com") ("nacimiento" . (("año" . 2000) ("mes" . 1) ("día" . 1))))";; Con assoc-utils (ql: carga rápida: assoc-utils) (importar 'assoc-utils:aget) (descongelar "/editar" (&key _parsed) (formato nulo "~S" (aget _parsed "persona")))
Las claves en blanco significan que tienen múltiples valores.
<formulario acción="/add"> <tipo de entrada="texto" nombre="artículos[][nombre]" /> <tipo de entrada="texto" nombre="artículos[][precio]" /> <tipo de entrada="texto" nombre="artículos[ ][nombre]" /> <tipo de entrada="texto" nombre="artículos[][precio]" /> <tipo de entrada="enviar" valor="Agregar" /></formulario>
(descongelar "/add" (&key _parsed) (formato nulo "~S" (assoc "items" _parsed :test #'string=)));=> "((("nombre". "WiiU") ("precio". "30000")) ((" nombre". "PS4") ("precio". "69000")))"
Caveman usa Djula como motor de plantillas predeterminado.
{% extiende "layouts/default.html" %} {% título del bloque %}Usuarios | MiAplicación{% endblock %} {% bloquear contenido %}<div id="main"> <ul> {% para usuario en usuarios %}<li><a href="{{ usuario.url }}">{{ usuario.nombre }}</a></li> {% endfor %} </ul></div>{% endblock %}
(importar 'miaplicación.vista:renderizar) (renderizar #P"usuarios.html"'(:usuarios ((:url "/id/1" :nombre "nitro_idiota") (:url "/id/2" :nombre "meymao")) :tiene-página-siguiente T))
Si desea obtener algo de una base de datos o ejecutar una función usando Djula, debe llamar explícitamente list
al pasar los argumentos para renderizar para que se ejecute el código.
(importar 'miaplicación.vista:renderizar) (renderizar #P"usuarios.html"(lista: usuarios (obtener-usuarios-de-db)))
Este es un ejemplo de una API JSON.
(eliminar "/user.json" (&clave |id|) (let ((persona (buscar-persona-de-db |id|)));; persona => (:|nombre| "Eitaro Fukamachi" :|correo electrónico| "[email protected]")(render- json persona)));=> {"nombre":"Eitaro Fukamachi","email":"[email protected]"}
render-json
es parte de un proyecto esqueleto. Puede encontrar su código en "src/view.lisp".
Las imágenes, CSS, JS, favicon.ico y robot.txt en el directorio "static/" se entregarán de forma predeterminada.
/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
Puede cambiar estas reglas reescribiendo "PROJECT_ROOT/app.lisp". Consulte Clack.Middleware.Static para obtener más detalles.
Caveman adopta Envy como conmutador de configuración. Esto permite la definición de múltiples configuraciones y cambiar entre ellas según una variable de entorno.
Este es un ejemplo típico:
(paquete def: miaplicación.config (:uso :cl:envidia)) (en el paquete: myapp.config) (setf (config-env-var) "APP_ENV") (defconfig: común `(:raíz-aplicación,(asdf:nombre-ruta-componente (asdf:find-system:miaplicación)))) (defconfig |desarrollo| `(:debug T:databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"test.db"*application-root*))))) (defconfig |producción| '(:bases de datos((:maindb :mysql :nombre-base de datos "miaplicación" :nombre de usuario "whoami" :contraseña "1234") (:workerdb :mysql :nombre-base de datos "trabajos" :nombre de usuario "whoami" :contraseña "1234")))) (defconfig |puesta en escena| `(:debug T,@|producción|))
Cada configuración es una lista de propiedades. Puede elegir la configuración que utilizar configurando APP_ENV
.
Para obtener un valor de la configuración actual, llame myapp.config:config
con la clave que desee.
(importar 'myapp.config:config) (setf (osicat:variable de entorno "APP_ENV") "desarrollo") (config:depurar);=> T
Cuando agrega :databases
a la configuración, Caveman habilita el soporte de bases de datos. :databases
es una lista de asociación de configuraciones de bases de datos.
(defconfig |producción| '(:bases de datos((:maindb :mysql :nombre-base de datos "miaplicación" :nombre de usuario "whoami" :contraseña "1234") (:workerdb :mysql :nombre-base de datos "trabajos" :nombre de usuario "whoami" :contraseña "1234"))))
db
en un paquete myapp.db
es una función para conectarse a cada base de datos configurada anteriormente. Aquí hay un ejemplo.
(uso-paquete '(:myapp.db :sxql :datafly)) (búsqueda defun-adultos () (con-conexión (db) (recuperar todo (seleccione :*(de :persona) (donde (:>=: 20 años))))))
La conexión está activa durante la sesión Lisp y se reutilizará en cada solicitud HTTP.
retrieve-all
y el lenguaje de consulta provienen de datafly y SxQL. Consulte esos conjuntos de documentación para obtener más información.
Hay varias variables especiales disponibles durante una solicitud HTTP. *request*
y *response*
representan una solicitud y una respuesta. Si está familiarizado con Clack, estas son instancias de subclases de Clack.Request y Clack.Response.
(uso-paquete: cavernícola2);; Obtenga un valor del encabezado Referer.(http-referer *solicitud*);; Establecer encabezado de tipo de contenido.(setf (getf (response-headers *response*):content-type) "application/json");; Establecer estado HTTP.(setf (estado *respuesta*) 304)
Si desea configurar el tipo de contenido "application/json" para todas las solicitudes "*.json", puede utilizar next-route
.
(descongelar "/*.json" () (setf (getf (encabezados de respuesta *respuesta*): tipo de contenido) "aplicación/json") (próxima ruta)) (eliminar "/user.json" () ...) (eliminar "/search.json" () ...) (descongelar ("/new.json" :método :POST) () ...)
Los datos de la sesión sirven para memorizar datos específicos del usuario. *session*
es una tabla hash que almacena datos de la sesión.
Este ejemplo incrementa :counter
en la sesión y lo muestra para cada visitante.
(descongelar "/contador" () (formato nulo "Viniste aquí ~A veces." (incf (gethash :counter *session* 0))))
Caveman2 almacena los datos de la sesión en la memoria de forma predeterminada. Para cambiar esto, especifique :store
en :session
en "PROJECT_ROOT/app.lisp".
Este ejemplo utiliza RDBMS para almacenar datos de sesión.
'(:retroceso :salida (getf (config):error-log)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (si (producciónp) nulo (lambda (aplicación)
NOTA: No olvide agregar :lack-session-store-dbi
como :depends-on
de su aplicación. No es parte de Clack/Lack.
Consulte el código fuente de Lack.Session.Store.DBi para obtener más información.
Falta.Session.Store.Dbi
(importar 'caveman2: código de lanzamiento) (descongelar ("/auth" :método :POST) (&clave |nombre| |contraseña|) (a menos que (autorice |nombre| |contraseña|) (código de lanzamiento 403)))
Para especificar páginas de error para 404, 500 o similares, defina un método on-exception
de su aplicación.
(defmethod en excepción ((aplicación <web>) (código (eql 404))) (declarar (ignorar el código de la aplicación)) (merge-pathnames #P"_errors/404.html" *directorio-plantilla*))
Aunque Caveman no tiene una función para implementación en caliente, Server::Starter, un módulo de Perl, lo hace fácil.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
NOTA: Server::Starter requiere que el servidor admita el enlace en un fd específico, lo que significa que solo :fcgi
y :woo
son los que funcionan con el comando start_server
.
Para reiniciar el servidor, envíe la señal HUP ( kill -HUP <pid>
) al proceso start_server
.
Caveman genera rastros de errores en un archivo que se especifica en :error-log
en su configuración.
(defconfig |predeterminado| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"* raíz de aplicación*)))))
(importar 'cl-quién:con-salida-html-a-cadena) (descongelar "/" () (con-html-salida-a-cadena (salida nula: prólogo t) (:html (:head (:title "¡Bienvenido a Caveman!")) (:body "Bla, 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>¡Bienvenido a Caveman!</title></head><body>Bla, bla bla.</body></html>"
Sitio web de CL-OMS
(importar 'cl-marcado:xhtml) (descongelar "/" () (xhtml (:head (:title "¡Bienvenido a Caveman!")) (:body "Bla, bla, bla.")));=> "<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transicional/ /ES" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><title>Bienvenido a ¡Hombre de las cavernas!</title></head><body>Bla, bla, bla.</body></html>"
Repositorio de marcado CL
{espacio de nombres miaplicación.vista} {plantilla renderIndex}<!DOCTYPE html><html><head> <title>"¡Bienvenido a Caveman!</title></head><body> Bla, bla, bla.</body></html>{/template}
(importar 'myapp.config:*directorio-plantilla*) (plantilla-cierre: compilar-cl-templates (merge-pathnames #P"index.tmpl"*template-directory*)) (descongelar "/" () (myapp.view:render-index))
plantilla-cierre-cl
Documentación de plantillas de cierre
Clack: entorno de aplicaciones web.
Falta: el núcleo de Clack.
ningle: marco de aplicación súper micro web en el que se basa Caveman.
Djula: motor de plantillas HTML.
CL-DBI: biblioteca de interfaz independiente de la base de datos.
SxQL: biblioteca de creación de SQL.
Envidia - Conmutador de configuración.
Roswell: administrador de implementación de Common Lisp.
Eitaro Fukamachi ([email protected])
Licenciado bajo la Licencia LLGPL.