Routing¶
Роутинг сопоставляет определенный запрос с обработчиком этого запроса.
Для Ring есть множество библиотек. Из популярных можно отметить:
Какую выберете вы, не очень принципиально. Однако, удобно, когда библиотека поддерживает обратный роутинг, т.е. зная имя роута можно получить url запроса. Иначе придется хардкодить url строками, и легко оставить битую ссылку.
Воспользуемся библиотекой sibiro. Это простая библиотека, не имеющая лишних для нашего приложения функций.
(def routes
#{[:get "/" #'root :root]
[:get "/:page" #'page :page]})
Каждый маршрут моделируется вектором со следующими элементами:
- http метод
- шаблон url, с поддержкой параметров и регулярных выражений
- функция-обработчик
- название маршрута для обратного роутинга
Маршруты объявляются как множество, т.е. маршруты не имеют порядка.
Нет вложенности, невозможно задать middleware для группы роутов,
например для /admin
. Подробнее ознакомьтесь с readme библиотеки.
Напомню, что ранее мы обсуждали перезагрузку кода.
Если бы мы не использовали #'root
, а просто передавали значение root
,
то при изменении кода пришлось бы перезагружать все приложение целиком.
Для примера напишем сайт из нескольких страниц. Главная страница содержит заголовок и список ссылок на прочие страницы. Страницы содержат заголовок и ссылаются на главную.
(ns app.app
(:require
[clojure.string :as str]
[ring.util.response :as ring.response]
[sibiro.core]
[sibiro.extras]))
(declare routes)
(defn path-for [& args]
(let [ret (apply sibiro.core/path-for routes args)]
(assert (some? ret) (str "route not found for " args))
ret))
(defn page-link [slug]
(let [url (path-for :page {:page slug})]
(str "<div><a href=\"" url "\">" slug "</a></div>")))
(defn root-link []
(let [url (path-for :root)]
(str "<div><a href=\"" url "\">root</a></div>")))
(defn root [req]
(let [body (str "<h1>Root page</h1>"
(->> ["about" "contacts" "resources"]
(map page-link)
str/join))]
(-> (ring.response/response body)
(ring.response/header "Content-Type" "text/html"))))
(defn page [req]
(let [slug (-> req :route-params :page)
body (str
"<h1>" slug "</h1>"
(root-link))]
(-> (ring.response/response body)
(ring.response/header "Content-Type" "text/html"))))
(def routes
(sibiro.core/compile-routes
#{[:get "/" #'root :root]
[:get "/:page" #'page :page]}))
(def handler (sibiro.extras/make-handler routes))
Библиотечная функция sibiro.core/path-for
возвращает nil
, если не находит подходящий маршрут,
что приводит к коварным ошибкам. Будем использовать обертку path-for
,
которая бросит исключение, если маршрут не найден.
Запустите этот пример, добавьте парочку маршрутов. Исходники примера.