(defparameter *web* (make-instance '<app>)) @route GET "/"(índice defun() (renderizar #P"index.tmpl")) @route GET "/hello"(defun say-hello (&key (|nome| "Convidado")) (formato nulo "Olá, ~A" |nome|))
Tudo. Caveman2 foi escrito do zero.
Esses são pontos dignos de nota.
É baseado em Ningle
Possui integração com banco de dados
Usa um novo sistema de configuração separado (Envy)
Possui nova macro de roteamento
Uma das perguntas mais frequentes foi "Qual devo usar: ningle ou Caveman? Quais as diferenças?" Acho que essas perguntas foram feitas com tanta frequência porque Caveman e Ningle eram muito parecidos. Ambos são chamados de "micro" e não tinham suporte de banco de dados.
Com o Caveman2, o Caveman não é mais uma estrutura de aplicação web "micro". Ele suporta CL-DBI e possui gerenciamento de conexão de banco de dados por padrão. O Homem das Cavernas começou a crescer.
Caveman pretende ser uma coleção de partes comuns de aplicativos da web. Com Caveman2, uso três regras para tomar decisões:
Seja extensível.
Seja prático.
Não force nada.
Você veio aqui porque tem interesse em viver como um homem das cavernas, certo? Isto não é a Disneylândia, mas podemos começar por aqui. Vamos entrar em uma caverna!
Caveman2 já está disponível no Quicklisp.
(ql: carregamento rápido: homem das cavernas2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Seu nome completo>");-> escrevendo /path/to/myapp/.gitignore; escrevendo /caminho/para/myapp/README.markdown; escrevendo /caminho/para/myapp/app.lisp; escrevendo /caminho/para/myapp/db/schema.sql; escrevendo /caminho/para/myapp/shlyfile.lisp; escrevendo /caminho/para/myapp/myapp-test.asd; escrevendo /caminho/para/myapp/myapp.asd; escrevendo /caminho/para/myapp/src/config.lisp; escrevendo /caminho/para/myapp/src/db.lisp; escrevendo /caminho/para/myapp/src/main.lisp; escrevendo /caminho/para/myapp/src/view.lisp; escrevendo /caminho/para/myapp/src/web.lisp; escrevendo /caminho/para/myapp/static/css/main.css; escrevendo /caminho/para/myapp/t/myapp.lisp; escrevendo /caminho/para/myapp/templates/_errors/404.html; escrevendo /caminho/para/myapp/templates/index.tmpl; escrevendo /caminho/para/myapp/templates/layout/default.tmpl
Este é um exemplo que pressupõe que o nome do seu aplicativo seja “myapp”. Antes de iniciar o servidor, você deve primeiro carregar seu aplicativo.
(ql: carregamento rápido: meu aplicativo)
Seu aplicativo possui funções chamadas start
e stop
para iniciar/parar seu aplicativo web.
(meuapp:start:porta 8080)
Como Caveman é baseado em Clack/Lack, você pode escolher em qual servidor rodar – Hunchentoot, Woo ou Wookie, etc.
(myapp:start:server:hunchentoot:porta 8080) (myapp:start:server:fcgi:porta 8080)
Eu recomendo que você use o Hunchentoot em uma máquina local e o Woo em um ambiente de produção.
Você também pode iniciar seu aplicativo usando o comando clackup.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 fornece 2 maneiras de definir uma rota - @route
e defroute
. Você pode usar qualquer um.
@route
é uma macro de anotação, definida usando cl-annot. É necessário um método, uma string de URL e uma função.
@route GET "/"(índice defun() ...);; Uma rota sem nome.@route GET "/welcome"(lambda (&key (|name| "Guest")) (formato nulo "Bem-vindo, ~A" |nome|))
Isso é semelhante ao @url
do Caveman1, exceto por sua lista de argumentos. Você não precisa especificar um argumento quando ele não for obrigatório.
defroute
é apenas uma macro. Ele fornece a mesma funcionalidade que @route
.
(índice defroute "/"() ...);; Uma rota sem nome.(defroute "/welcome" (&key (|name| "Guest")) (formato nulo "Bem-vindo, ~A" |nome|))
Como o Caveman se baseia no ningle, o Caveman também possui o sistema de roteamento semelhante ao Sinatra.
;; Solicitação GET (padrão)@route GET "/" (lambda () ...) (defroute ("/" :método :GET)() ...); POST solicitação@rota POST "/" (lambda () ...) (defroute ("/" :método :POST)() ...); PUT solicitação@rota PUT "/" (lambda () ...) (defroute ("/" :method :PUT)() ...); DELETE request@route DELETE "/" (lambda () ...) (defroute ("/" :método :DELETE)() ...); OPÇÕES request@route OPÇÕES "/" (lambda () ...) (defroute ("/" :method :OPTIONS)() ...); Para todos os métodos@rota ANY "/" (lambda () ...) (defroute ("/" :método :ANY) () ...)
Os padrões de rota podem conter "palavras-chave" para colocar o valor no argumento.
(defroute "/hello/:name" (&nome da chave) (formato nulo "Olá, ~A" nome))
O controlador acima será invocado quando você acessar "/hello/Eitaro" ou "/hello/Tomohiro", e name
será "Eitaro" ou "Tomohiro", conforme apropriado.
(&key name)
é quase igual a uma lista lambda do Common Lisp, exceto que sempre permite outras chaves.
(defroute "/hello/:name" (&rest params &key name) ;; ... )
Os padrões de rota também podem conter parâmetros "curinga". Eles são acessíveis usando splat
.
(defroute "/say/*/to/*" (&key splat) ; corresponde a /say/hello/to/world (formato nil "~A" splat));=> (olá mundo)(defroute "/download/*.*" (&key splat) ; corresponde a /download/path/to/file.xml (formato nulo "~A" splat)) ;=> (caminho/para/arquivo xml)
Se você quiser escrever uma expressão regular em uma regra de URL, :regexp t
deve funcionar.
(defroute ("/hello/([w]+)" :regexp t) (&capturas de teclas) (formato nil "Olá, ~A!" (primeiras capturas)))
Normalmente, as rotas são testadas para uma correspondência na ordem em que são definidas, e apenas a primeira rota correspondida é invocada, sendo as rotas seguintes ignoradas. No entanto, uma rota pode continuar testando correspondências na lista, incluindo next-route
.
(defroute "/guess/:who" (&key who) (if (string= who "Eitaro") "Você me pegou!" (próxima rota))) (defroute "/guess/*" () "Você errou!")
Você pode retornar os seguintes formatos como resultado de defroute
.
Corda
Nome do caminho
Lista de respostas do Clack (contendo status, cabeçalhos e corpo)
Redirecione para outra rota com (redirect "url")
. Um segundo argumento opcional é o código de status, 302 por padrão.
Ao definir rotas com nomes, você pode encontrar a URL de um nome com (url-for route-name &rest params)
.
A função gerará um erro se nenhuma rota for encontrada.
Veja também:
add-query-parameters base-url params
Chaves de parâmetro contendo colchetes ("[" & "]") serão analisadas como parâmetros estruturados. Você pode acessar os parâmetros analisados como _parsed
em roteadores.
<form action="/edit"> <input type="nome" name="pessoa[nome]" /> <input type="nome" nome="pessoa[email]" /> <input type="nome" nome="pessoa[nascimento][ano ]" /> <input type="name" name="pessoa[nascimento][mês]" /> <input type="nome" name="pessoa[nascimento][dia]" /></form>
(defroute "/edit" (&key _parsed) (formato nil "~S" (cdr (assoc "pessoa" _parsed :test #'string=))));=> "(("nome" . "Eitaro") ("email" . "e.arrows@gmail .com") ("nascimento". (("ano". 2000) ("mês". 1) ("dia". 1))))";; Com assoc-utils(ql:quickload :assoc-utils) (importar 'assoc-utils:aget) (defroute "/edit" (&key _parsed) (formato nulo "~S" (aget _parsed "pessoa")))
Chaves em branco significam que possuem vários valores.
<form action="/add"> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="text" name="items[ ][nome]" /> <input type="text" name="items[][preço]" /> <input type="submit" value="Adicionar" /></form>
(descongelar "/add" (&key _parsed) (formato nil "~S" (assoc "itens" _parsed :test #'string=)));=> "((("nome" . "WiiU") ("preço" . "30000")) ((" nome". "PS4") ("preço". "69000")))"
Caveman usa Djula como mecanismo de modelagem padrão.
{% estende "layouts/default.html" %} {% block title %}Usuários | MeuApp{% endblock %} {% bloquear conteúdo %}<div id="main"> <ul> {% para usuário em usuários %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(importar 'myapp.view:renderizar) (renderizar #P"users.html"'(:users ((:url "/id/1" :name "nitro_idiot") (:url "/id/2" :name "meymao")) :has-next-page T))
Se você deseja obter algo de um banco de dados ou executar uma função usando Djula você deve chamar explicitamente list
ao passar os argumentos para renderizar para que o código seja executado.
(importar 'myapp.view:renderizar) (renderizar #P"users.html"(lista: usuários (obter usuários do banco de dados)))
Este é um exemplo de API JSON.
(defroute "/user.json" (&key |id|) (let ((pessoa (encontrar pessoa-do-db |id|)));; pessoa => (:|nome| "Eitaro Fukamachi" :|email| "[email protected]")(renderizar- json person)));=> {"name":"Eitaro Fukamachi","email":"[email protected]"}
render-json
faz parte de um projeto esqueleto. Você pode encontrar seu código em "src/view.lisp".
Imagens, CSS, JS, favicon.ico e robot.txt no diretório "static/" serão veiculados por padrão.
/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
Você pode alterar essas regras reescrevendo "PROJECT_ROOT/app.lisp". Consulte Clack.Middleware.Static para obter detalhes.
Caveman adota o Envy como um alternador de configuração. Isto permite a definição de múltiplas configurações e a alternância entre elas de acordo com uma variável de ambiente.
Este é um exemplo típico:
(defpackage:myapp.config (:use :cl:inveja)) (no pacote: myapp.config) (setf (config-env-var) "APP_ENV") (defconfig: comum `(: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 |produção| '(:bancos de dados((:maindb :mysql :nome do banco de dados "myapp" :nome de usuário "whoami" :senha "1234") (:workerdb :mysql :nome do banco de dados "jobs" :nome de usuário "whoami" :senha "1234")))) (defconfig |staging| `(:debug T,@|produção|))
Cada configuração é uma lista de propriedades. Você pode escolher a configuração a ser usada definindo APP_ENV
.
Para obter um valor da configuração atual, chame myapp.config:config
com a chave desejada.
(importar 'myapp.config:config) (setf (osicat:variável de ambiente "APP_ENV") "desenvolvimento") (config: depuração); => T
Quando você adiciona :databases
à configuração, o Caveman habilita o suporte ao banco de dados. :databases
é uma lista de associação de configurações de banco de dados.
(defconfig |produção| '(:bancos de dados((:maindb :mysql :nome do banco de dados "myapp" :nome de usuário "whoami" :senha "1234") (:workerdb :mysql :nome do banco de dados "jobs" :nome de usuário "whoami" :senha "1234"))))
db
em um pacote myapp.db
é uma função para conectar-se a cada banco de dados configurado acima. Aqui está um exemplo.
(use-package '(:myapp.db :sxql :datafly)) (defun search-adultos () (com conexão (db) (recuperar tudo (selecione :*(de :pessoa) (onde (:>= :idade 20))))))
A conexão permanece ativa durante a sessão Lisp e será reutilizada em cada solicitação HTTP.
retrieve-all
e a linguagem de consulta vieram do datafly e SxQL. Consulte esses conjuntos de documentação para obter mais informações.
Existem diversas variáveis especiais disponíveis durante uma solicitação HTTP. *request*
e *response*
representam uma solicitação e uma resposta. Se você estiver familiarizado com Clack, estas são instâncias de subclasses de Clack.Request e Clack.Response.
(use-pacote :caveman2);; Obtenha um valor de Referer header.(http-referer *request*);; Definir o cabeçalho Content-Type.(setf (getf (response-headers *response*) :content-type) "application/json");; Definir status HTTP.(setf (status *resposta*) 304)
Se você quiser definir o Content-Type "application/json" para todas as solicitações "*.json", next-route
poderá ser usada.
(defroute "/*.json"() (setf (getf (cabeçalhos de resposta *resposta*): tipo de conteúdo) "aplicativo/json") (próxima rota)) (defroute "/user.json"() ...) (defroute "/search.json"() ...) (defroute ("/new.json" :method :POST) () ...)
Os dados da sessão servem para memorizar dados específicos do usuário. *session*
é uma tabela hash que armazena dados da sessão.
Este exemplo incrementa :counter
na sessão e o exibe para cada visitante.
(defroute "/counter"() (format nil "Você veio aqui ~Uma vez." (incf (gethash :counter *session* 0))))
Caveman2 armazena dados de sessão na memória por padrão. Para alterar isso, especifique :store
para :session
em "PROJECT_ROOT/app.lisp".
Este exemplo usa RDBMS para armazenar dados de sessão.
'(:traço de retorno :saída (getf (config):log de erros)) nil)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (se (produçãop) zero (lambda (aplicativo)
NOTA: Não se esqueça de adicionar :lack-session-store-dbi
como :depends-on
do seu aplicativo. Não faz parte do Clack/Lack.
Consulte o código-fonte de Lack.Session.Store.DBi para obter mais informações.
Falta.Sessão.Loja.Dbi
(importar 'caveman2:código de lançamento) (defroute ("/auth" :método :POST) (&key |nome| |senha|) (a menos que (autorizar |nome| |senha|) (código de lançamento 403)))
Para especificar páginas de erro para 404, 500 ou semelhantes, defina um método on-exception
do seu aplicativo.
(defmethod on-exception ((app <web>) (código (eql 404))) (declarar (ignorar o código do aplicativo)) (nomes de caminhos de mesclagem #P"_errors/404.html" *diretório de modelos*))
Embora o Caveman não tenha um recurso para implantação a quente, Server::Starter - um módulo Perl - torna isso mais fácil.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
NOTA: Server::Starter requer que o servidor suporte ligação em um fd específico, o que significa que apenas :fcgi
e :woo
são os que funcionam com o comando start_server
.
Para reiniciar o servidor, envie o sinal HUP ( kill -HUP <pid>
) para o processo start_server
.
Caveman gera backtraces de erro para um arquivo especificado em :error-log
em sua configuração.
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"* raiz do aplicativo*)))))
(importar 'cl-who:com-html-output-to-string) (descongelar "/"() (com-html-output-to-string (saída nula: prólogo t) (:html (:head (:title "Bem-vindo ao Homem das Cavernas!")) (:body "Blá, blá, blá."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Bem-vindo ao Caveman!</title></head><body>Blá, blá blá.</body></html>"
Site CL-OMS
(importar 'marcação cl:xhtml) (descongelar "/"() (xhtml (:head (:title "Bem-vindo ao Homem das Cavernas!")) (:body "Blá, blá, blá.")));=> "<?xml version="1.0" encoding="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>Bem-vindo ao Homem das cavernas!</title></head><body>Blá, blá, blá.</body></html>"
Repositório CL-Markup
{namespace meuapp.view} {template renderIndex}<!DOCTYPE html><html><head> <title>"Bem-vindo ao Homem das Cavernas!</title></head><body> Blá, blá, blá.</body></html>{/template}
(importar 'myapp.config:*diretório-modelo*) (modelo de encerramento:compile-cl-templates (nomes de caminhos de mesclagem #P"index.tmpl"*diretório de modelos*)) (descongelar "/"() (myapp.view:render-index))
modelo de fechamento cl
Documentação de modelos de fechamento
Clack - ambiente de aplicação web.
Falta - O núcleo do Clack.
ningle - Estrutura de aplicativo super micro web na qual o Caveman é baseado.
Djula - mecanismo de modelagem HTML.
CL-DBI - Biblioteca de interface independente de banco de dados.
SxQL - biblioteca construtora SQL.
Envy - alternador de configuração.
Roswell - gerente de implementação Common Lisp.
Eitaro Fukamachi ([email protected])
Licenciado sob a licença LLGPL.