HTML из БД не внедряется в маршрут hunchentoot
В моем приложении есть много маршрутов, которые получают информацию из базы данных. В одной конкретной ситуации я получаю HTML-контент из базы данных для его рендеринга с помощьюcl-who
.
Я не уверен, почему контент из БД не отображается. Остальная часть страницы работает нормально.
Когда я тестирую добавление html в маршрут вручную (жестко закодированные html-теги), все работает нормально. В случае, когда я использую(second (get-content))
(см. ниже) HTML из БД в шаблон не добавляется.
Что мне не хватает?
Макрос базы данных
(defmacro access-db(db &body query)
`(progn
(clsql:connect ,db)
(unwind-protect (progn ,@query)
(disconnect-db ,db))))
Макрос страницы
(defmacro defpage ((&key title) &body content)
`(cl-who:with-html-output-to-string
(*standard-output* nil :prologue t :indent t)
(:html
:xmlns "http://www.w3.org/1999/xhtml"
:xml\:lang "en"
:lang "en"
(:head
(:meta
:http-equiv "Content-Type"
:content "text/html;charset=utf-8")
(:title
,(format nil "~A" title))
(:link :type "text/css"
:rel "stylesheet"
:href "/styles.css"))
(:body :class "whole-page"
(:div :class "site-articles"
(:div :class "article-body"
(:div :class "on-page-title"
(:h1 :class "main-header" ,title))
,@content))))))
Маршрут Hunchentoot
(define-easy-handler (test-page :uri "/wow") ()
(let ((content
(bt:make-thread
(lambda ()
(second (get-content))))))
(defpage (:title "Main One")
(bt:join-thread content))))
использование get-contentaccess-db
функция и возвращает список первого элемента из базы данных. Заголовок и HTML. HTML занимает второе место в этом списке.
Получить контент
(defun get-content ()
(let ((blogs
(access-db *PSQL-CONNECT-INFO*
(clsql:query
"select * from content"))))
(first blogs)))
1 ответ
[Отказ от ответственности: я не могу заставить CLSQL работать и в любом случае не хочу иметь ничего общего с базами данных, поэтому этот ответ посвящен проблемам с другими вещами.]
Я думаю, что вы, возможно, запутались в макросах в целом, но ваш макрос определенно запутался.
Что такое CL-WHOwith-html-output
заключается в том, чтобы привязать поток к переменной, в которую вы можете напечатать , а затем рассматривать его тело как неявный язык «html-lisp», который понимает он и все подобные макросы (я думаю, HTOUT был первым, кто действительно это сделал). Это означает, что если вы хотите отправить выходные данные в этот поток, вам необходимо напечатать их в этом потоке.with-html-output-to-string
просто записывает этот вывод в строку очевидным образом.
Вот очищенная версия этого макроса с лучшим именем (defpage
для меня пахнет как «определить страницу», что совсем не то, что он делает).
(defmacro with-output-to-standard-page ((&key (title "page")
(stream '*standard-output*)
(string-form 'nil))
&body content)
(let ((tv (make-symbol "TITLE")))
`(let ((,tv ,title))
(with-html-output-to-string (,var ,string-form :prologue t :indent t)
(:html
:xmlns "http://www.w3.org/1999/xhtml"
:xml\:lang "en"
:lang "en"
(:head
(:meta
:http-equiv "Content-Type"
:content "text/html;charset=utf-8")
(:title (princ ,tv ,var))
(:link :type "text/css"
:rel "stylesheet"
:href "/styles.css"))
(:body :class "whole-page"
(:div :class "site-articles"
(:div :class "article-body"
(:div :class "on-page-title"
(:h1 :class "main-header"
(princ ,tv ,var)))
,@content))))))))
Это поведение более традиционное, чем ваше:
(let ((title "foo"))
(with-output-to-standard-page (:title title)
...))
будет работать, и
(with-output-to-standard-page (:title (complicated-function-with-state))
...)
позвонюcomplicated-function-with-state
только раз.
Кроме того, он позволяет вам определить, что такое переменная потока и строковая форма, если хотите:
(with-output-to-standard-page (:var s)
(princ "foo" s))
будет работать.
Наконец, он помещает контент под начальный, а не в него (и за пределы начального).on-page-title
div
на самом деле), и использует название дляh1
.
Все это также делается в пакете, который используетCL-WHO
,HUNCHENTOOT
иBORDEAUX-THREADS
чтобы избежать ужасного ужаса вездесущих префиксов пакетов и сделать код читабельным.
Итак, если мы заменим вашget-content
функция с помощью прокладки, потому что нет SQL:
(defun get-content ()
(list 'nothing "my page content"))
Затем исправьте ваш обработчик для печати в потоке, что является критической проблемой.
(define-easy-handler (test-page :uri "/wow") ()
(let ((content-thread
(make-thread
(lambda ()
(second (get-content))))))
(with-output-to-standard-page (:var p :title "Main One")
(princ (join-thread content-thread) p))))
Теперь все будет работать.
Глядя на твойaccess-db
макроса с ним тоже, почти наверняка, проблемы.
Прежде всего, я назову это примерно такwith-db-access
потому чтоwith-*
— это одно из общепринятых названий подобных макросов (другой вариант может бытьaccessing-db
). Тогда у него, по крайней мере, есть проблема: он будет многократно оценивать свой первый аргумент. Вместо этого это должно быть что-то вроде
(defmacro with-db-access (db &body query)
(let ((dbv (make-symbol "DB")))
`(let ((,dbv ,db))
(connect ,dbv)
(unwind-protect
(progn ,@query)
(disconnect-db ,dbv)))))
чтобы избежать этой проблемы.
Обратите внимание, что этот очень распространенный шаблон в макросах (и соответствующую очень распространенную проблему, когда люди об этом забывают) можно сделать намного проще, используя метатронные макросы Тима Брэдшоу . Используя это, это будет:
(defmacro/m with-db-access (db &body query)
`(let ((<db> ,db))
(connect <db>)
(unwind-protect
(progn ,@query)
(disconnect-db <db>))))
Мойwith-output-to-standard-page
макрос, приведенный выше, будет аналогичным образом упрощен с использованием макросов Metatronic.