Web application in Clojure

habré not so many articles about Clojure, and it's sad, intends to fix it. Below I will tell you about perfect in my subjective opinion the tool — the programming language Clojure and its libraries to create web applications.

In this article, there is no comparison of Clojure to other languages, as proceeding from the modest experience to compare with.

Basic information about Clojure

Clojure is a multi — paradigm programming language General purpose to encourage functional programming. The basis of this language and its syntax is Lisp and S-expressions. Unlike a Lisp Clojure includes other types of data (collections), such as: vectors, associative arrays, sets, and very easy to use key-words. Clojure code is compiled into JVM bytecode, which allows you to deploy applications to many platforms and self-use all available Java libraries.

The Internet is full of information about the structures of Clojure, so I skip description thereof.

Komyuniti

We can not say about the current round of the programming language community, which is not so great, but very friendly to newcomers and not only that, they readily help to solve the problem. There are a large number of videos on various aspects of using Clojure, but that's almost all of them in English (a Paradise for self-translators). At the end of the article are links.

Leiningen

Alternative To Maven. Used to manage project dependencies, settings, libraries and global settings of the project. Provides console commands to build, test, compile and run applications.

Projects in Clojure are created using the command: $ lein new <name of the project template (app|compojure|luminus...)> <project name>
This creates a directory with the Clojure project with all necessary files and directories. And, of course, creates the project file.clj and connect all the libraries and put global settings: libraries, design, compilation, repl, etc...

Example project file.clj:
(defproject test "0.1.0-SNAPSHOT"

:description "Description"
:url "http://test.ru"

:dependencies [[org.clojure/clojure "1.7.0"]
[selmer "0.8.2"]
[com.taoensso/timbre "4.0.2"]
[com.taoensso/tower "3.0.2"]
[markdown-clj "0.9.67"]
[environ "1.0.0"]
[compojure "1.3.4"]
[ring/ring-defaults "0.1.5"]
[ring/ring-session-timeout "0.1.0"]
[metosin/ring-middleware-format "0.6.0"]
[metosin/ring-http-response "0.6.2"]
[bouncer "0.3.3"]
[prone "0.8.2"]
[org.clojure/tools.nrepl "0.2.10"]
[buddy "0.6.0"]
[com.novemberain/monger "2.0.1"]
[org.immutant/web "2.0.2"]
[clojure.joda-time "0.6.0"]]

:min-lein-version "2.0.0"
:uberjar-name "test.jar"
:jvm-opts ["-server"]

;;enable to  start  the nREPL server when the application launches
;:env {:repl-port 7001}

:main test.core

:plugins [[lein-environ "1.0.0"]
[lein-ancient "0.6.5"]]

:profiles {:uberjar {:omit-source true
:env {:production true}
:aot :all}

:dev {:dependencies [[ring-mock "0.1.5"]
[ring/ring-devel "1.3.2"]
[pjstadig/humane-test-output "0.7.0"]]

:repl-options {:init-ns test.core}
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]

:env {:dev true}}})



Ring

A Ring represents a layer of abstraction over HTTP, providing interaction through a simple API. Very successfully used when creating modular applications.
Examples of usage can be seen on their github page (link at end of article). I am using another abstraction above the Ring, which in my opinion is more easier routes is called Ring and Compojure.

Compojure

Library for routing Ring, with its help you can conveniently pack the trails and use them in handler'e of the project.

here is a simple example:
(defroutes auth-routes
; Cleaning session
(GET "/logout"

; We can pass the request to the controller
fully
request
(-> logout-controller))

; Handler login via a POST request


; Or to pass specific parameters
[login password]
(login-controller login password))

; Authorization
; Specify a GET request and you can call
; presentation page directly
; or pack everything in the controller 
; which function will be invoked
; page view
(GET "/login"
request
(view/login-page)))



This is also possible:
(defroutes user-routes

Page viewing profile
(GET "/profile/:login"

; Request :params will already be available
; set to login with the key specified above :login
request

; Syntactic sugar called ->
; sends the first incoming argument
; function handler.
(-> profile-view-controller-GET)))



I'm not going to give an example of a request'as you all well know what he looks like. This is about Compojure is finished.

Buddy

Library to authenticate and authorize users. Packs session authenticated users in the HTTP header in your backend, has the functions to encrypt passwords etc...

Example of password encryption:
the
(buddy.hashers/encrypt "qwerty")


Example of a login feature from my project:
(defn login-controller
"User authorisation"
[request]
(let [
; Get the data from the form
form {:login (get-in request [:form-params "login"])
:password (get-in request [:form-params "password"])}

; Check data for validity
validate (bouncer/validate form valid/login-validator)

; Handle errors
errors (validate first)
return-errors (fn [message]
(util/return-messages
view/login-page
:error-message message
:validate data))]

; Errors during validation
(if-not errors

; The presence of the user with the specified login
(if (true? (db/user-exist? {:login (:login form)}))

; Get structure user
(let [user (db/get-user {:login (:login form)}
[:password])]

; Match password
(if (hashers/check (:password form) (:password user))
(do

; Update :visited
(db/update-user
{:login (:login form)}
{:visited (util/date-time)})

; Create a new session
(util/create-session request (login form) "/"))

; If the two passwords do not match
(return-errors "Incorrect password")))

; If login was not found
(return-errors "Login not found"))

; Error during validation
(return-errors "Check your entries"))))



So the same Buddy allows you to configure access to pages in the middleware.

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

(defn on-error
[request response]
{:status 403
:headers {"Content-Type" "text/html"}
:body (str "No access" (:uri request) ".<br>" 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))))

; And the example of the middleware base:
(defn wrap-base
[handler]
(-> handler
wrap-dev

Our access rules
(wrap-access-rules {:rules rules :on-error on-error})

; The authorization
wrap-auth

; Session
(wrap-idle-session-timeout
{:timeout (* 30 60)
: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 template engine inspired by Django. Allows a very flexible way to work with data in HTML templates.

the
(defn registration-page
"The user registration page"
[]
(render "registration.html"
{:foo [1 2 3 4 5]})))


And the template itself:
the
<ul>
{% for i in foo %}
{{i}}
{% endfor %}
</ul>


Monger

Library for working with mongodb, Clojure is generally a very convenient tool for work with databases, all thanks to his collections. Monger is a Clojure MongoDB client (the library) provides functions of high and low level to interact with MongoDB's API. The library is written very succinctly and at the same time provides everything you need to make full use MongoDB in your applications. You will see a huge plus — it's very detailed and extensive documentation on the official site.
a Small example:
(ns test.users.db
(:require monger.joda-time
[monger.collection :as m]
[test.db :refer [db]]))

(def collection "users")

(defn get-user
"Find user"
([query]
(m/find-one-as-map db collection query))
([query, fields]
(m/find-one-as-map db collection query fields)))

; Example usage:
; Will return the fields we need
(get-user {:login "test"} [:first-name :last-name])

; Will return the whole document
(get-user {:login "test"})



About the abundance of parentheses

Brackets a lot to get used to, but the attentive person will notice that they are not more than the braces in the JavaScript.

Links mentioned libraries

the

Additional links

the

This is probably all in the future articles if they show interest I'll talk about each library individually, about the wonderful web server immutant, and of course about ClojureScript which is convenient to use when developing front-end applications and compiles it into javascript. So as I would like to highlight the Luminus framework, which greatly helped me deal with web development in Clojure. My hope, though not comprehensive, the article will interest you to see the possibilities of this wonderful tool.

Thank you, all the best!
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Automatically create Liquibase migrations for PostgreSQL

Vkontakte sync with address book for iPhone. How it was done

What part of the archived web