Я плохо знаком с Clojure и использовал Compojure для записи основного веб-приложения. Я врезаюсь в стену с Compojure defroutes
синтаксис, тем не менее, и я думаю, что должен понять и "как" и "почему" позади всего этого.
Кажется, что приложение Кольцевого стиля начинается с карты Запроса HTTP, затем просто передает запрос через серию функций промежуточного программного обеспечения, пока это не преобразовывается в карту ответа, которую передают обратно браузеру. Этот стиль кажется слишком "низким уровнем" для разработчиков, таким образом потребность в инструменте как Compojure. Я вижу эту потребность в большем количестве абстракций в других экосистемах программного обеспечения также, прежде всего с WSGI Python.
Проблема состоит в том, что я не понимаю подход Compojure. Давайте возьмем следующее defroutes
S-выражение:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
Я знаю, что ключ к пониманию всего этого находится в некотором макро-вуду, но я (еще) не полностью понимаю макросы. Я уставился defroutes
источник в течение долгого времени, но просто не получают его! Что продолжается здесь? Понимание "большой идеи", вероятно, поможет мне ответить на эти конкретные вопросы:
workbench
функция)? Например, скажите, что я хотел получить доступ к заголовкам HTTP_ACCEPT или некоторой другой части запроса/промежуточного программного обеспечения?{form-params :form-params}
)? Какие ключевые слова доступны для меня когда destructuring?Мне действительно нравится Clojure, но я так озадачен!
NB. Я работаю с Compojure 0.4.1 ( здесь коммит выпуска 0.4.1 на GitHub).
В самом начале compojure / core.clj
есть полезное изложение цели Compojure:
Краткий синтаксис для генерации обработчиков кольца.
На поверхностном уровне это все, что касается вопроса «почему». Чтобы пойти немного глубже, давайте посмотрим, как работает приложение в стиле Ring:
Поступает запрос и преобразуется в карту Clojure в соответствии со спецификацией Ring.
Эта карта направляется в так называемую «функцию-обработчик», которая, как ожидается, вызовет ответ (который также является картой Clojure).
Карта ответов преобразуется в фактический HTTP-ответ и отправляется обратно клиенту.
Шаг 2 из вышеизложенного является наиболее интересным, поскольку в обязанности обработчика входит проверка URI, используемого в запросе, проверка любых файлов cookie и т. Д. И получение в конечном итоге соответствующего ответа. Ясно, что необходимо, чтобы вся эта работа была собрана в сборник четко определенных частей; Обычно это «базовая» функция-обработчик и набор функций промежуточного программного обеспечения, обертывающих ее. Назначение Compojure - упростить создание функции базового обработчика.
Compojure построен на понятии «маршруты». На самом деле они реализованы на более глубоком уровне библиотекой Clout (побочный продукт проекта Compojure - многие вещи были перемещены в отдельные библиотеки при переходе 0.3.x -> 0.4.x). Маршрут определяется (1) методом HTTP (GET, PUT, HEAD ...), (2) шаблон URI (заданный с синтаксисом, который, очевидно, будет знаком Webby Rubyists), (3) форма деструктуризации, используемая для привязки частей карты запроса к именам, доступным в теле, (4) тело выражений который должен выдать правильный ответ Ring (в нетривиальных случаях это обычно просто вызов отдельной функции).
Это может быть хорошим моментом, чтобы взглянуть на простой пример:
(def example-route (GET "/" [] "<html>...</html>"))
Давайте проверим это в REPL (карта запроса ниже является минимально допустимой картой запроса Ring):
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
If : request -method
были : head
, вместо этого ответ будет nil
. Мы вернемся к вопросу о том, что здесь означает nil
, через минуту (но обратите внимание, что это не действительный ответ кольца!).
Как видно из этого примера, example-route
- это просто функция, причем очень простая; он просматривает запрос, определяет, заинтересован ли он в его обработке (проверяя : метод-запроса
и : uri
), и, если да, возвращает базовую карту ответов.
Также очевидно, что тело маршрута на самом деле не нуждается в оценке для правильной карты ответов; Compojure обеспечивает разумную обработку по умолчанию для строк (как показано выше) и ряда других типов объектов; подробности см. в мультиметоде compojure.response / render
(здесь код полностью самодокументируется).
Давайте попробуем использовать defroutes
сейчас:
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
Ответы на приведенный выше пример запроса и на его вариант с : request-method: head
ожидаются.
Внутренняя работа примеров маршрутов
такова, что каждый маршрут проверяется по очереди; как только один из них возвращает ответ, отличный от nil
, этот ответ становится возвращаемым значением всего обработчика example-routes
. В качестве дополнительного удобства обработчики defroutes
неявно обертываются в wrap-params
и wrap-cookies
.
Вот пример более сложного маршрута:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
Обратите внимание на форму деструктуризации вместо ранее использовавшегося пустого вектора. Основная идея здесь в том, что тело маршрута может интересовать некоторую информацию о запросе; поскольку он всегда поступает в виде карты, может быть предоставлена форма ассоциативной деструктуризации для извлечения информации из запроса и привязки ее к локальным переменным, которые будут в области видимости в теле маршрута.
Проверка вышеизложенного:
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
Блестящая идея продолжения вышеизложенного заключается в том, что более сложные маршруты могут связать
дополнительную информацию с запросом на этапе сопоставления:
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
Это отвечает a : тело
из "foo"
на запрос из предыдущего примера.
В этом последнем примере есть две новинки: "/: fst / *"
и непустой связывающий вектор [fst]
. Первый - это вышеупомянутый синтаксис типа Rails-and-Sinatra для шаблонов URI. Это немного сложнее, чем то, что видно из приведенного выше примера, поскольку поддерживаются ограничения регулярного выражения для сегментов URI (например, ["/: fst / *": fst # "[0-9] +"]
можно указать, чтобы маршрут принимал только все цифровые значения : fst
в выше). Второй - это упрощенный способ сопоставления на :запись params
в карте запроса, которая сама по себе является картой; это полезно для извлечения сегментов URI из запроса, параметров строки запроса и параметров формы. Пример, иллюстрирующий последний пункт:
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
Сейчас самое время взглянуть на пример из текста вопроса:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
Давайте проанализируем каждый маршрут по очереди:
(GET "/" [] ( workbench))
- при работе с запросом GET
с : uri "/"
вызовите функцию workbench
и визуализируйте все, что она вернет, в карта ответов. (Напомним, что возвращаемое значение может быть картой, но также и строкой и т. Д.)
(POST "/ save" {form-params: form-params} (str form-params))
- : form-params
- это запись в карте запроса, предоставленная промежуточным программным обеспечением wrap-params
(напомним, что она неявно включается в defroutes
). Ответ будет стандартным {: status 200: headers {"Content-Type" "text / html"}: body ...}
с замененным (str form-params)
для ...
. (Немного необычный обработчик POST
, this ...)
(GET "/ test" [& more] (str "
- - это, например, будет отображать строковое представление карты more"
")) {"foo" "1"}
, если пользовательский агент запросит "/ test? foo = 1"
.
(GET ["/: filename": filename # ". *"] [Filename] ...)
- часть : filename # ". *"
вообще ничего не делает (поскольку # ". *"
всегда соответствует). Он вызывает служебную функцию Ring ring.util.ответ / файл-ответ
для создания своего ответа; часть {: root "./static"}
сообщает ему, где искать файл.
(ЛЮБОЙ "*" [] ...)
- всеобъемлющий маршрут. Хорошая практика Compojure - всегда включать такой маршрут в конец формы defroutes
, чтобы гарантировать, что определяемый обработчик всегда возвращает действительную карту ответа Ring (напомним, что сбой сопоставления маршрута приводит к ноль
).
Одной из целей промежуточного программного обеспечения кольца является добавление информации в карту запросов; таким образом, промежуточное ПО для обработки файлов cookie добавляет к запросу ключ : cookies
, wrap-params
добавляет : query-params
и / или : form-params
, если присутствует строка запроса / данные формы и т. Д. (Строго говоря, вся информация, которую добавляют функции промежуточного программного обеспечения, должна уже присутствовать в карте запроса, поскольку это то, что они передают; их задача - преобразовать ее, чтобы было удобнее работать с обработчиками, которые они обертывают.) В конечном итоге «обогащенный» запрос передается базовому обработчику, который проверяет карту запроса со всей хорошо предварительно обработанной информацией, добавленной промежуточным программным обеспечением, и выдает ответ. (Промежуточное ПО может делать более сложные вещи, чем это - например, обертывать несколько «внутренних» обработчиков и выбирать между ними, решать, вызывать ли обернутые обработчики вообще и т. Д. Это, однако, выходит за рамки этого ответа.)
Базовый обработчик, в свою очередь, обычно (в нетривиальных случаях) является функцией, которой обычно требуется лишь несколько элементов информации о запросе. (Например, ring.util.response / file-response
не заботится о большей части запроса; ему нужно только имя файла.) Отсюда необходимость в простом способе извлечения только соответствующих частей кольца. запрос. Compojure стремится предоставить специальный механизм сопоставления с образцом, так сказать, который именно это и делает.
Я еще не начал работать над веб-материалами по закрытию, но вот что я добавил в закладки.