[Из песочницы] Веб-приложения на Clojure

На Хабре не так уж и много статей о Clojure, и это печально, намерен это исправить. Ниже я расскажу об отличном на мой субъективный взгляд инструменте — языке программирования Clojure и его библиотеках для создания веб-приложений.
В этой статье не будет сравнения Clojure с другими языками, так как отталкиваясь от скромного опыта сравнивать не с чем.Основная информация о Clojure
Clojure — это мультипарадигмальный язык программирования общего назначения поощряющий функциональное программирование. Основой этого языка и его синтаксиса является Lisp и его S-expressions. В отличие от Lisp’a Clojure включает и другие типы данных (коллекции), такие как: векторы, ассоциативные массивы, множества и очень удобные в использовании ключевые-слова.
Код компилируется JVM в java байт-код, что позволяет развертывать приложения на большом количестве платформ и само-собой использовать все доступные Java библиотеки.

В интернетах полно информации о структурах Clojure, поэтому я пропущу описание оных.

Комюнити
Нельзя не сказать о сложившимся вокруг этого языка программирования сообществе, которое не столь огромно, но очень дружелюбно к новичкам и не только, они с готовностью помогают решать возникшие проблемы. Имеется большое количество видеоматериалов по самым разным аспектам использования Clojure, только вот практически все они на английском языке (рай для самореализации переводчиков). В конце статьи будут ссылки.Leiningen
Альтернатива Maven. Используется для управления зависимостями проекта, настройками библиотек и глобальными настройками проекта. Проекты на Clojure создаются с помощью
команды:
lein new <название шаблона проекта (app|compojure|luminus...)> <название проекта>
Это создает каталог с проектом Clojure, имеющую все необходимые файлы и каталоги. И конечно же создает файл project.clj в котором и подключаются все библиотеки и выставляются глобальные настройки: библиотек, проекта, компиляции, repl и т.п…Ring
Ring — представляет из себя слой абстракции над HTTP, предоставляя взаимодействие с ним через простой API. Очень успешно применяется при создании модульных приложений.
Примеры использования, можно увидеть на их странице github (ссылка в конце статьи). Я же использую еще одну абстракцию над Ring, которая на мой взгляд более упрощает работу с маршрутами Ring и называется Compojure.Compojure
Библиотека для маршрутизации Ring, с её помощью можно удобно упаковывать маршруты и использовать их в handler’e проекта.

Ниже привожу простой пример:

(defroutes auth-routes
  ; Очистка сессии
  (GET "/logout"

       ; Мы можем передать запрос контроллеру
       ; полностью
       request
       (-> logout-controller))

  ; Обработчик авторизации через POST запрос
  (POST "/login"

        ; Или передать определенные параметры
        [login password]
        (login-controller login password))

  ; Авторизация
  ; Указан GET запрос и можно вызвать
  ; представление страницы напрямую
  ; или упаковать все в контроллер 
  ; из которого будет вызвана функция
  ; представления страницы
  (GET "/login"
       request
       (view/login-page)))


Такое тоже возможно:

(defroutes users-routes

  ; Страница просмотра профиля
  (GET "/profile/:login"

       ; В request в :params уже будет доступно
       ; значение login с ключом указанным выше :login
       request
      
       ; Синтаксический сахар под названием ->
       ; передает первый входящий аргумент
       ; функции обработчику.
       (-> profile-view-controller-GET)))


Не стану приводить пример request’a так как вы все прекрасно понимаете как он выглядит. На этом о Compojure закончено.Buddy
Библиотека для авторизации и аутентификации пользователей. Упаковывает сессии авторизованных пользователей в HTTP заголовок в свой backend, имеет функции для шифрования паролей и т.п…

Пример шифрования пароля:

(buddy.hashers/encrypt "qwerty")


Пример функции авторизации из моего проекта:

(defn login-controller
  "Авторизация пользователя"
  [request]
  (let [
        ; Получить данные из формы
        form {:login (get-in request [:form-params "login"])
              :password (get-in request [:form-params "password"])}
        
        ; Проверить данные на валидность
        validate (bouncer/validate form valid/login-validator)
        
        ; Обработать ошибки
        errors (first validate)
        return-errors (fn [message]
                        (util/return-messages
                         view/login-page
                         :error-message message
                         :data validate))]

    ; Ошибки при валидации
    (if-not errors

      ; Наличие пользователя с указанным логином
      (if (true? (db/user-exist? {:login (:login form)}))

        ; Получить структуру пользователя
        (let [user (db/get-user {:login (:login form)}
                                [:password])]

          ; Соответсвие паролей
          (if (hashers/check (:password form) (:password user))
            (do

              ; Обновить :visited
              (db/update-user
               {:login (:login form)}
               {:visited (util/date-time)})

              ; Создать новую сессию
              (util/create-session request (:login form) "/"))

            ; Если пароли не совпали
            (return-errors "Неверный пароль")))

        ; Если логин не найден
        (return-errors "Логин не найден"))

      ; Ошибка при валидации
      (return-errors "Проверьте правильность введенных данных"))))


Так-же позволяет настраивать доступ к страницам в middleware, пример:

(def rules
  [{:pattern #"^/user/edit$"
    :handler authenticated-user}

(defn on-error
  [request response]
  {:status  403
   :headers {"Content-Type" "text/html"}
   :body    (str "Нет доступа к " (:uri request) ".
" response)}) (defn wrap-restricted [handler] (restrict handler {:handler authenticated? :on-error on-error})) (defn wrap-identity [handler] (fn [request] (binding [*identity* (or (get-in request [:session :identity]) nil)] (handler request)))) (defn wrap-auth [handler] (-> handler wrap-identity (wrap-authentication (session-backend)))) ; И пример middleware base: (defn wrap-base [handler] (-> handler wrap-dev ; Наши правила доступа (wrap-access-rules {:rules rules :on-error on-error}) ; Сама авторизация wrap-auth ; Сессия (wrap-idle-session-timeout {:timeout (* 60 30) :timeout-response (redirect "/")}) wrap-formats (wrap-defaults (-> site-defaults (assoc-in [:security :anti-forgery] false) (assoc-in [:session :store] (memory-store session/mem)))) wrap-servlet-context wrap-internal-error wrap-uri))

Selmer
HTML шаблонизатор, вдохновленный Django. Позволяет очень гибко работать с данными в HTML шаблонах.

(defn registration-page
  "Страница регистрации пользователя"
  []
  (render "registration.html"
        {:foo [1 2 3 4 5]})))

И сам шаблон:

    {% for i in foo %} {{i}} {% endfor %}


Monger
Библиотека для работы с mongodb, вообще Clojure очень удобный инструмент для работы с базами данных, все благодаря его коллекциям.

Небольшой пример:

(ns test.users.db
  (:require monger.joda-time
            [monger.collection :as m]
            [test.db :refer [db]]))

(def collection "users")

(defn get-user
  "Найти пользователя"
  ([query]
   (m/find-one-as-map db collection query))
  ([query fields]
   (m/find-one-as-map db collection query fields)))

; Пример использования:
; Вернет нужные нам поля
(get-user {:login "test"} [:first-name :last-name])

; Вернет весь документ
(get-user {:login "test"})


Про обилие скобок
Скобок много, нужно привыкнуть, но внимательный человек обратит внимание, что их не больше чем фигурных скобок в том-же JavaScript.Ссылки на описанные библиотеки
Дополнительные ссылки

На этом пожалуй все, в дальнейших статьях если к ним проявится интерес я расскажу про каждую библиотеку в отдельности, про замечательный веб-сервер immutant, ну и конечно же про ClojureScript который удобно использовать при разработке front-end приложений и компилируется он в javascript. Так-же хотелось бы осветить фреймфорк Luminus, который очень сильно помог мне разобраться с веб-разработкой на Clojure. Надеюсь моя, хоть и не всеобъемлющая статья заинтересует вас просмотреть возможности этого замечательного инструмента.

Благодарю, всего вам лучшего!

© Habrahabr.ru