📜 ⬆️ ⬇️

Basics of Clojure Web Applications

Today I will try to show the basics of creating web applications in Clojure. There will be no complex logic and fashionable frameworks. A number of libraries will be used to work with primitives. As far as I mention, I will try to briefly explain what functionality they provide.

The web application architecture in primitives consists of a web server that sends requests to handlers depending on the path, parameters, method. The handler executes certain code, queries the database, works with the file system. After processing the request, a response is generated and sent to the client.

Our application will take one value through the form, take the second value from the database, add them, and return the result to the client. In this case, the entered value will replace the old one in the database. Stupid, useless and not interesting logic - I know.

To develop an application on Clojure, we need, of course, Clojure and a number of auxiliary libraries. First of all we swing Cloj , SUDDENLY, Leiningen . Indeed, to write on Clojure, you can not install Clojure right away, but download Leiningen. It is a utility for building Clojure projects with support for the usual range of tasks, including dependencies, and extensible plugins. For details, send to the project page or to Alex Ott .
')
So let's get started:
D:\dev\clojure>lein new web-clojure-demo Created new project in: D:\dev\clojure\web-clojure-demo 

What just happened? Leiningen created a project folder with the following hierarchy:
 src/ web_clojure_demo/ core.clj test/ web_clojure_demo/ test/ core.clj .gitignore README project.clj 

First of all, we are interested in the project.clj file. Structurally, it contains the usual Clojure source code:
 (defproject web-clojure-demo "1.0.0-SNAPSHOT" :description "FIXME: write description" :dependencies [[org.clojure/clojure "1.2.1"]]) 

Leiningen uses the contents of this file to work with the project. It is possible to specify various directives that can be read in the documentation. We are primarily interested in the dependency section. It can specify the libraries that are used in the application. When executing the lein deps command, Leiningen will automatically place all dependencies in the lib / folder, and, if necessary, download them from the repository.

We will use a number of libraries that need to be specified in the section : dependencies :

To debug the application, we need a plugin for Leiningen. Just add one more section to the project.clj : dev-dependencies .
Now it looks like this:
 (defproject web-clojure-demo "1.0.0-SNAPSHOT" :description "FIXME: write description" :dependencies [[org.clojure/clojure "1.2.1"] [org.clojure/clojure-contrib "1.2.0"] [ring/ring-jetty-adapter "0.2.5"] [compojure "0.6.2"] [clj-redis "0.0.9"] [enlive "1.0.0-SNAPSHOT"]] :dev-dependencies [[lein-ring "0.4.0"]] :ring {:handler web-clojure-demo.core/engine}) 

Next, open the src / web_clojure_demo / core.clj file . For now, it contains only a namespace declaration. Add the necessary dependencies and the following code to it:
 (ns web-clojure-demo.core (:use compojure.core) (:use [ring.adapter.jetty :only [run-jetty]]) (:use [ring.util.response]) (:require [compojure.route :as route] [compojure.handler :as handler] [clj-redis.client :as redis] [net.cgrand.enlive-html :as html])) (def db (redis/init {:url "redis://127.0.0.1:6379"})) (defn parse-input [a] (Integer/parseInt a)) (html/deftemplate page-index "web_clojure_demo/index.html" [ctxt] [:title] (html/content "Awesome application") [:#old] (html/content (:old ctxt)) [:#msg2] (html/set-attr "style" "display: none")) (html/deftemplate page-summary "web_clojure_demo/index.html" [ctxt] [:title] (html/content "Awesome application") [:#old] (html/content (:old ctxt)) [:#msg2] (html/content (str "Summary is " (:sum ctxt)))) (defn summary [value] (let [old (redis/get db "value")] (redis/set db "value" value) (page-summary { :sum (+ (parse-input value) (parse-input old)) :old old}))) (defn index [] (let [old (redis/get db "value")] (page-index {:old old}))) (defroutes main-routes (GET "/" [] (index)) (POST "/some_action" [value] (summary value)) (route/not-found "Page not found")) (def engine (handler/site main-routes)) 

Next we place the template index.html :
 <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> <div> <div id="msg1">Old value: <span id="old" /></div> <div id="msg2" /> <form method="post" action="/some_action" > <input type="text" name="value" ></input><input type="submit" name="ok" ></input> </form> </div> 

As you noticed, it does not contain any special tags.
Next, through the console, you need to update the dependencies and start the debugging web server:
 D:\dev\clojure\web-clojure-demo>lein deps Copying 19 files to D:\dev\clojure\web-clojure-demo\lib Copying 17 files to D:\dev\clojure\web-clojure-demo\lib\dev D:\dev\clojure\web-clojure-demo>lein ring server 2011-03-31 22:23:25.125::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2011-03-31 22:23:25.125::INFO: jetty-6.1.14 2011-03-31 22:23:25.203::INFO: Started SocketConnector@0.0.0.0:3000 Started server on port 3000 

If you go back to the project.clj, you will notice that we added the attribute : ring {: handler web-clojure-demo.core / engine} . It allows the Leiningen internal web server to send all requests to our handler during the testing of the application. This is very convenient because this Leiningen plugin uses several stubs, which, for example, allow you to update the source code without rebooting the web server.

Let's see what's going on inside.

 (def db (redis/init {:url "redis://127.0.0.1:6379"})) 

This code gets a connection to the Redis repository. Variable db we will continue to use for work.

 (defroutes main-routes (GET "/" [] (index)) (POST "/some_action" [value] (summary value)) (route/not-found "Page not found")) (def engine (handler/site main-routes)) 

This code defines handlers for various requests. In this case, compojure libraries are used. The site macro creates a handler function that supports the necessary functionality for typical sites such as sessions, cookies, parameters, etc. The main-routes list contains a list of radical requests and handler functions. If none of the handlers is suitable, not-found is triggered.

 (defn index [] (let [old (redis/get db "value")] (page-index {:old old}))) (defn summary [value] (let [old (redis/get db "value")] (redis/set db "value" value) (page-summary { :sum (+ (parse-input value) (parse-input old)) :old old}))) 

These are the functions that process our requests. The first of them takes the value in the database and calls the function to generate a response from the template, the second adds the value in the database and the transmitted one and also causes the response to be generated.

 (html/deftemplate page-index "web_clojure_demo/index.html" [ctxt] [:title] (html/content "Awesome application") [:#old] (html/content (:old ctxt)) [:#msg2] (html/set-attr "style" "display: none")) (html/deftemplate page-summary "web_clojure_demo/index.html" [ctxt] [:title] (html/content "Awesome application") [:#old] (html/content (:old ctxt)) [:#msg2] (html/content (str "Summary is " (:sum ctxt)))) 

The macro deftemplate creates a function that accepts the ctxt parameter, loads the html template and converts it according to the specified rules, in our case it is the task of content or the change of styles. The Enlive library allows for much more interesting manipulations with html.

This is how our application works:

image
image
image
image
image
image

Oops!
Correct this misunderstanding. Edit the code:
 (html/deftemplate page-summary "web_clojure_demo/index.html" [ctxt] [:title] (html/content "Awesome application") [:#old] (html/content (:old ctxt)) [:#msg2] (html/content (if (:error ctxt) (:error ctxt) (str "Summary is " (:sum ctxt))))) (defn summary [value] (let [old (redis/get db "value")] (try (let [ a (parse-input value) b (parse-input old)] (redis/set db "value" value) (page-summary { :sum (+ ab) :old old})) (catch NumberFormatException e (page-summary {:old old :error "Number Format Exception"}))))) 

Now our application correctly processes inappropriate data. In case of an error, a message will be displayed to the client.
image

Conclusion


If I poorly covered any part, please let me know about it - I will add an article. In case of questions, please do not be shy. Healthy and not very criticism is also welcome.
The source code for this example can be found in the repository .

Source: https://habr.com/ru/post/116595/


All Articles