(defparameter *web* (make-instance '<app>)) @route GET "/"(индекс defun() (рендеринг #P"index.tmpl")) @route GET "/hello"(defun сказать привет (&key (|name| "Гость")) (формат nil "Привет, ~A" |имя|))
Все. Caveman2 был написан с нуля.
Это заслуживающие внимания моменты.
Основан на сингле
Имеет интеграцию с базой данных
Использует новую отдельную систему конфигурации (Envy).
Имеет новый макрос маршрутизации
Одним из наиболее часто задаваемых вопросов был: «Что мне использовать: Ningle или Caveman? В чем различия?» Я думаю, что их так часто спрашивали, потому что Caveman и Ningle были слишком похожи. Оба они называются «микро» и не имеют поддержки баз данных.
Благодаря Caveman2 Caveman больше не является «микро»-фреймворком для веб-приложений. Он поддерживает CL-DBI и по умолчанию имеет управление подключениями к базе данных. Пещерный человек начал взрослеть.
Caveman задуман как набор общих частей веб-приложений. В Caveman2 я использую три правила для принятия решений:
Будьте расширяемы.
Будьте практичны.
Не принуждайте ничего.
Ты пришел сюда, потому что хочешь жить как пещерный человек, верно? Это не Диснейленд, но мы можем начать здесь. Пойдем в пещеру!
Caveman2 теперь доступен на Quicklisp.
(ql:quickload:caveman2)
(caveman2:make-project #P"/path/to/myapp/" :author "<Ваше полное имя>");-> запись /path/to/myapp/.gitignore; запись /path/to/myapp/README.markdown; написание /path/to/myapp/app.lisp; запись /path/to/myapp/db/schema.sql; запись /path/to/myapp/shlyfile.lisp; запись /path/to/myapp/myapp-test.asd; запись /path/to/myapp/myapp.asd; запись /path/to/myapp/src/config.lisp; написание /path/to/myapp/src/db.lisp; запись /path/to/myapp/src/main.lisp; написание /path/to/myapp/src/view.lisp; написание /path/to/myapp/src/web.lisp; написание /path/to/myapp/static/css/main.css; запись /path/to/myapp/t/myapp.lisp; запись /path/to/myapp/templates/_errors/404.html; написание /path/to/myapp/templates/index.tmpl; запись /path/to/myapp/templates/layout/default.tmpl
Это пример, в котором предполагается, что имя вашего приложения — «myapp». Прежде чем запустить сервер, вы должны сначала загрузить свое приложение.
(ql: быстрая загрузка: myapp)
В вашем приложении есть функции с именами start
и stop
для запуска/остановки вашего веб-приложения.
(myapp: запуск: порт 8080)
Поскольку Caveman основан на Clack/Lack, вы можете выбрать, на каком сервере работать — Hunchentoot, Woo или Wookie и т. д.
(myapp: запуск: сервер: hunchentoot: порт 8080) (myapp: запуск: сервер: fcgi: порт 8080)
Я рекомендую вам использовать Hunchentoot на локальном компьютере и использовать Woo в производственной среде.
Вы также можете запустить свое приложение с помощью команды clackup.
$ ros install clack $ which clackup /Users/nitro_idiot/.roswell/bin/clackup $ APP_ENV=development clackup --server :fcgi --port 8080 app.lisp
Caveman2 предоставляет два способа определения маршрута — @route
и defroute
. Вы можете использовать любой из них.
@route
— это макрос аннотации, определенный с помощью cl-annot. Требуется метод, URL-строка и функция.
@route GET "/"(индекс defun() ...);; Маршрут без имени.@route GET "/welcome"(lambda (&key (|name| "Guest")) (формат nil "Добро пожаловать, ~A" |имя|))
Это похоже на @url
Caveman1, за исключением списка аргументов. Вам не нужно указывать аргумент, если он не требуется.
defroute
- это просто макрос. Он обеспечивает ту же функциональность, что и @route
.
(очистить индекс "/" () ...);; Маршрут без имени.(удалить "/welcome" (&key (|name| "Guest")) (формат nil "Добро пожаловать, ~A" |имя|))
Поскольку Caveman основан на ingle, Caveman также имеет систему маршрутизации, подобную Синатре.
;; GET-запрос (по умолчанию) @route GET "/" (лямбда () ...) (defroute ("/" :method :GET) () ...);; POST-запрос@маршрут POST "/" (лямбда () ...) (defroute ("/" :method :POST) () ...);; PUT запрос@маршрут PUT "/" (лямбда () ...) (defroute ("/" :method :PUT) () ...);; DELETE request@route DELETE "/" (лямбда () ...) (очистка ("/" :method :DELETE) () ...);; ОПЦИИ request@route ОПЦИИ "/" (лямбда () ...) (defroute ("/" :method :OPTIONS) () ...);; Для всех методов @route ЛЮБОЙ "/" (лямбда() ...) (очистка ("/" :метод :ЛЮБОЙ) () ...)
Шаблоны маршрутов могут содержать «ключевые слова» для помещения значения в аргумент.
(удалить "/hello/:name" (&имя ключа) (формат nil "Привет, ~A" имя))
Вышеуказанный контроллер будет вызываться при доступе к «/hello/Eitaro» или «/hello/Tomohiro», а name
будет «Eitaro» или «Tomohiro», в зависимости от ситуации.
(&key name)
почти такой же, как лямбда-список Common Lisp, за исключением того, что он всегда допускает использование других ключей.
(удалить "/hello/:name" (&rest params &key name) ;; ... )
Шаблоны маршрутов также могут содержать параметры «подстановки». Доступ к ним осуществляется с помощью splat
.
(удалить "/say/*/to/*" (&key splat); соответствует /say/hello/to/world (формат nil "~A" splat));=> (hello world)(defroute "/download/*.*" (&key splat) ; соответствует /download/path/to/file.xml (формат нулевой знак "~A")) ;=> (путь/к/файлу xml)
Если вы хотите использовать регулярное выражение в правиле URL-адреса, :regexp t
должен работать.
(defroute ("/hello/([w]+)" :regexp t) (&захват ключей) (формат nil "Привет, ~A!" (первые снимки)))
Обычно маршруты проверяются на соответствие в том порядке, в котором они определены, и вызывается только первый найденный маршрут, а последующие маршруты игнорируются. Однако маршрут может продолжить проверку совпадений в списке, включив next-route
.
(удалить "/guess/:who" (&key who) (if (string= кто "Эйтаро") "Ты меня понял!" (следующий маршрут))) (удалить "/guess/*" () "Вы промахнулись!")
В результате defroute
вы можете вернуть следующие форматы.
Нить
Путь
Список ответов Clack (содержащий статус, заголовки и тело)
Перенаправление на другой маршрут с помощью (redirect "url")
. Второй необязательный аргумент — это код состояния, по умолчанию 302.
Когда вы определили маршруты с именами, вы можете найти URL-адрес по имени с помощью (url-for route-name &rest params)
.
Функция выдаст ошибку, если маршрут не найден.
См. также:
add-query-parameters base-url params
Ключи параметров, содержащие квадратные скобки («[» и «]»), будут анализироваться как структурированные параметры. Вы можете получить доступ к анализируемым параметрам как _parsed
в маршрутизаторах.
<форма действие="/редактировать"> <input type="name" name="person[name]" /> <input type="name" name="person[email]" /> <input type="name" name="person[рождение][год] ]" /> <input type="name" name="person[рождение][месяц]" /> <input type="name" name="person[рождение][день]" /></form>
(удалить "/edit" (&key _parsed) (format nil "~S" (cdr (assoc "person" _parsed :test #'string=))));=> "(("name" . "Eitaro") ("email" . "e.arrows@gmail .com") ("рождение" . (("год" . 2000) ("месяц" . 1) ("день" . 1))))";; С помощью assoc-utils (ql:quickload:assoc-utils) (импортируйте 'assoc-utils:aget) (удалить "/edit" (&key _parsed) (формат ноль "~S" (aget _parsed "человек")))
Пустые клавиши означают, что они имеют несколько значений.
<форма действие="/добавить"> <input type="text" name="items[][name]" /> <input type="text" name="items[][price]" /> <input type="text" name="items[ ][имя]" /> <input type="text" name="items[][price]" /> <input type="submit" value="Добавить" /></form>
(удалить "/add" (&key _parsed) (format nil "~S" (assoc "items" _parsed :test #'string=)));=> "((("name" . "WiiU") ("price" . "30000")) ((" имя". "PS4") ("цена". "69000")))"
Caveman использует Djula в качестве шаблонизатора по умолчанию.
{% расширяет "layouts/default.html" %} {% название блока %}Пользователи | MyApp{% endblock %} {% блокирует контент %}<div id="main"> <ул> {% для пользователя в пользователях %}<li><a href="{{ user.url }}">{{ user.name }}</a></li> {% endfor %} </ul></div>{% endblock %}
(импортируйте 'myapp.view:render) (рендеринг #P"users.html"'(:users ((:url "/id/1" :name "nitro_idiot") (:url "/id/2" :name "меймао")) :has-next-page T))
Если вы хотите получить что-то из базы данных или выполнить функцию с помощью Djula, вы должны явно указать list
вызовов при передаче аргументов для рендеринга, чтобы код выполнялся.
(импортируйте 'myapp.view:render) (рендеринг #P"users.html"(список:пользователи (get-users-from-db)))
Это пример API JSON.
(удалить "/user.json" (&key |id|) (let ((person (find-person-from-db |id|)));; person => (:|name| "Эйтаро Фукамачи" :|email| "[email protected]")(render- json person)));=> {"name":"Эйтаро Фукамачи","email":"[email protected]"}
render-json
является частью скелетного проекта. Вы можете найти его код в «src/view.lisp».
Изображения, CSS, JS, favicon.ico и robot.txt в каталоге static/ будут обслуживаться по умолчанию.
/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
Вы можете изменить эти правила, переписав «PROJECT_ROOT/app.lisp». Подробности см. в разделе Clack.Middleware.Static.
Caveman использует Envy в качестве переключателя конфигурации. Это позволяет определять несколько конфигураций и переключаться между ними в соответствии с переменной среды.
Это типичный пример:
(defpackage:myapp.config (:используйте :cl:зависть)) (в пакете: myapp.config) (setf (config-env-var) «APP_ENV») (defconfig: общий `(:корень-приложения ,(asdf:путь-компонента (asdf:find-system :myapp)))) (defconfig |development| `(:debug T:databases((:maindb :sqlite3 :имя-базы-данных ,(имена-путей слияния #P"test.db"*корень-приложения*))))) (defconfig |production| '(:databases((:maindb :mysql :имя базы данных "myapp" :имя пользователя "whoami" :пароль "1234") (:workerdb:mysql:имя базы данных "jobs":имя пользователя "whoami":пароль "1234")))) (defconfig |staging| `(:debug T,@|production|))
Каждая конфигурация представляет собой список свойств. Вы можете выбрать конфигурацию, которую будете использовать, установив APP_ENV
.
Чтобы получить значение из текущей конфигурации, вызовите myapp.config:config
с нужным ключом.
(импортируйте 'myapp.config:config) (setf (osicat:переменная среды "APP_ENV") "разработка") (конфигурация: отладка);=> Т
Когда вы добавляете :databases
в конфигурацию, Caveman включает поддержку баз данных. :databases
— это список ассоциаций настроек базы данных.
(defconfig |production| '(:databases((:maindb :mysql :имя базы данных "myapp" :имя пользователя "whoami" :пароль "1234") (:workerdb:mysql:имя-базы данных "jobs":имя пользователя "whoami":пароль "1234"))))
db
в пакете myapp.db
— это функция для подключения к каждой базе данных, настроенной выше. Вот пример.
(использовать пакет '(:myapp.db :sxql :datafly)) (defun search-adults () (с соединением (дБ) (получить все (выберите :*(от :person) (где (:>= :возраст 20))))))
Соединение активно во время сеанса Lisp и будет повторно использоваться в каждом HTTP-запросе.
retrieve-all
и язык запросов взяты из datafly и SxQL. Дополнительные сведения см. в этих комплектах документации.
Во время HTTP-запроса доступно несколько специальных переменных. *request*
и *response*
представляют собой запрос и ответ. Если вы знакомы с Clack, это экземпляры подклассов Clack.Request и Clack.Response.
(использовать-пакет: Caveman2);; Получить значение заголовка Referer.(http-referer *request*);; Установить заголовок Content-Type.(setf (getf (response-headers *response*):content-type) "application/json");; Установить статус HTTP.(setf (status *response*) 304)
Если вы хотите установить тип контента «application/json» для всех запросов «*.json», можно использовать next-route
.
(очистить "/*.json" () (setf (getf (заголовки ответов *response*): тип контента) "application/json") (следующий маршрут)) (очистить "/user.json" () ...) (очистить "/search.json" () ...) (очистить ("/new.json" :method :POST) () ...)
Данные сеанса предназначены для запоминания пользовательских данных. *session*
— это хеш-таблица, в которой хранятся данные сеанса.
В этом примере :counter
увеличивается в сеансе и отображается для каждого посетителя.
(очистить "/counter" () (формат nil «Вы приходили сюда ~ раз.» (incf (gethash :counter *session* 0))))
Caveman2 по умолчанию хранит данные сеанса в памяти. Чтобы изменить это, укажите :store
на :session
в «PROJECT_ROOT/app.lisp».
В этом примере используется СУБД для хранения данных сеанса.
'(:обратная трассировка :output (getf (config): журнал ошибок)) ноль)- :session+ (:session+ :store (make-dbi-store :connector (lambda ()+ (apply #'dbi:connect+ (myapp.db:connection-settings))))) (если (производствоp) ноль (лямбда (приложение)
ПРИМЕЧАНИЕ. Не забудьте добавить :lack-session-store-dbi
в качестве :depends-on
от вашего приложения. Это не часть Clack/Lack.
Дополнительную информацию см. в исходном коде Lack.Session.Store.DBi.
Lack.Session.Store.Dbi
(импортировать «caveman2:throw-code») (defroute ("/auth" :method :POST) (&key |имя| |пароль|) (если только (авторизовать |имя| |пароль|) (код 403)))
Чтобы указать страницы ошибок для 404, 500 или подобных, определите метод on-exception
вашего приложения.
(defmethod on-Exception ((app <web>) (код (eql 404))) (объявить (игнорировать код приложения)) (пути слияния #P"_errors/404.html" *каталог-шаблона*))
Хотя в Caveman нет функции горячего развертывания, Server::Starter — модуль Perl — упрощает эту задачу.
$ APP_ENV=production start_server --port 8080 -- clackup --server :fcgi app.lisp
ПРИМЕЧАНИЕ. Сервер::Starter требует, чтобы сервер поддерживал привязку к определенному файловому диску, а это означает, что с командой start_server
работают только :fcgi
и :woo
.
Чтобы перезапустить сервер, отправьте сигнал HUP ( kill -HUP <pid>
) процессу start_server
.
Caveman выводит трассировки ошибок в файл, который указан в :error-log
в вашей конфигурации.
(defconfig |default| `(:error-log #P"/var/log/apps/myapp_error.log":databases((:maindb :sqlite3 :database-name ,(merge-pathnames #P"myapp.db"* корень приложения*)))))
(импортировать 'cl-who:with-html-output-to-string) (очистить "/" () (with-html-output-to-string (вывод ноль: пролог t) (:html (:head (:title "Добро пожаловать в Пещерный человек!")) (:body "Бла-бла-бла."))));=> "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/ xhtml1/DTD/xhtml1-strict.dtd">; <html><head><title>Добро пожаловать в Caveman!</title></head><body>Бла-бла бла.</body></html>"
Веб-сайт CL-ВОЗ
(импортировать cl-markup:xhtml) (очистить "/" () (xhtml (:head (:title "Добро пожаловать в Пещерный человек!")) (:body "Бла-бла-бла.")));=> "<?xml version="1.0"coding="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>Добро пожаловать в Пещерный человек!</title></head><body>Бла-бла-бла.</body></html>"
Репозиторий CL-разметки
{пространство имен myapp.view} {template renderIndex}<!DOCTYPE html><html><head> <title>"Добро пожаловать в Пещерный человек!</title></head><body> Бла-бла-бла.</body></html>{/template}
(импортируйте 'myapp.config:*каталог-шаблона*) (шаблон закрытия: компиляция-cl-шаблоны (имена путей слияния #P"index.tmpl"*каталог-шаблона*)) (очистить "/" () (myapp.view:render-index))
cl-закрытие-шаблон
Документация по шаблонам закрытия
Clack — среда веб-приложений.
Lack — ядро Clack.
ningle — супермикро-фреймворк веб-приложений, на котором основан Caveman.
Djula — движок HTML-шаблонов.
CL-DBI — независимая от базы данных интерфейсная библиотека.
SxQL — библиотека построения SQL.
Envy — переключатель конфигурации.
Розуэлл — менеджер по внедрению Common Lisp.
Эйтаро Фукамачи ([email protected])
Лицензия LLGPL.