- 치트시트: http://clojure.org/cheatsheet
- -> / ->>: 설명
- 구조분해: 설명
클로저로 실제 프로그램을 만들 때는 주로 프로젝트 단위로 한다.
$ lein new app my-stuff
- new: 프로젝트를 생성하는 작업
- app: 독립형 프로그램을 만들 때 많이 쓰이는 템플릿
- my-stuff: 프로젝트 이름
$ cd my-stuff
$ lein run
명령창에 "Hello, World!"가 찍힌다.
src/my_stuff/core.clj
파일을 편한 에디터로 연다. 다음과 같은 내용이다.
(ns my-stuff.core ; (1)
(:gen-class))
(defn -main ; (2)
"I don't do a whole lot ... yet."
[& args]
(println "Hello, World!"))
- (1)
my-stuff.core
라는 이름공간을 선언한다. 이름공간은 이름이 겹치지 않게 관리하기 위한 방법이다. - (2)
-main
은 프로젝트에서 처음으로 실행되는 함수이다.
이제, "Hello, World!"
를 "Hello, Clojure!"
처럼 원하는 문자열로 바꾸고 파일을 저장하자.
$ lein run
다시 실행하면 바꾼 문자열이 찍힌다.
lein run
을 사용하지 않고도 단독으로 실행할 수 있는 jar 파일을 만들어 보자.
$ lein uberjar
빌드가 끝나면 다음처럼 실행할 수 있다.
$ java -jar target/uberjar/my-stuff-0.1.0-SNAPSHOT-standalone.jar
마찬가지로 바꾼 문자열이 찍힌다.
프로젝트 내에서 REPL을 실행하면 그 프로젝트와 연관된 작업을 REPL에서 할 수 있다.
$ lein repl
다음을 실행하자. 그러면 프롬프트가 기존의 user=>
이 아니라 my-stuff.core=>
임을 확인할 수 있다. 이것은 현재 my-stuff.core
라는 이름공간 안에 있음을 의미한다.
my-stuff.core
이름공간 안에 있으므로 그 안에서 정의한 -main
함수를 바로 사용할 수 있다. 실행해 보자.
my-stuff.core=> (-main)
Hello, Clojure!
nil
Hello, Clojure!
는 println
에 의한 화면 출력이고, nil
은 -main
함수의 반환값이다.
미니 방명록 만들기
$ lein new compojure mini-guestbook
여기의 compojure는 웹 프로그램을 만들 때 많이 사용하는 템플릿이다.
$ cd mini-guestbook
$ lein ring server
브라우저에서 새 창이 열리면서 "Hello World" 글자가 나타난다. 최소한의 웹 서버를 만든 것이다!
src/mini_guestbook/handler.clj를 열어보자. 다음과 같다.
(ns mini-guestbook.handler
(:require [compojure.core :refer :all] ; (1)
[compojure.route :as route] ; (1)
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defroutes app-routes ; (2)
(GET "/" [] "Hello World") ; (3)
(route/not-found "Not Found")) ; (4)
(def app
(wrap-defaults app-routes site-defaults))
- (1) Compojure를 불러와서 사용한다. Compojure는 라우팅을 제공하는 라이브러리이다.
- (2) HTTP 라우트를 정의하는 구문이다.
- (3) '/' 경로에 대해서 "Hello World" 문자열로 응답하는 Get 라우트를 만든다.
- (4) 정의되어 있지 않는 주소에 대해서는 "Not Found" 문자열로 응답하는 라우트를 만든다.
사실 Compojure는 Ring에 라우팅 기능만 제공하는 작은 라이브러리이다. Ring은 클로저 웹 어플리케이션의 근간으로, 요청과 응답을 클로저 맵으로 처리하는 라이브러리이다. 기본적인 웹 서버(Jetty)를 포함하고 있고, 다른 종류의 웹 서버도 쉽게 붙일 수 있다. 어떤 웹 서버를 붙여도 Ring은 인터페이스 역활을 해서, 동일한 방식으로 웹 요청을 처리할 수 있다.
"Hello World" 이외에 좀 더 복잡한 HTML을 만들기 위해 우리는 Hiccup이라는 라이브러리를 사용한다. Hiccup은 클로저에서 HTML을 생성하는 가장 간단한 방법으로 클로저 자료구조를 HTML로 변환해 준다. 클로저에서 라이브러리를 사용하려면 project.clj의 :dependencies
에 추가해야 한다. [hiccup "1.0.5"]
를 추가하자. 수정한 project.clj는 다음과 같다.
(defproject mini-guestbook "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.7.0"]
[compojure "1.4.0"]
[ring/ring-defaults "0.1.5"]
[hiccup "1.0.5"]]
:plugins [[lein-ring "0.9.7"]]
:ring {:handler mini-guestbook.handler/app}
:profiles
{:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.0"]]}})
REPL에서 이 라이브러리를 테스트해보자.
$ lein repl
다음처럼 실행한다.
user=> (require '[hiccup.core :as h]) ; (1)
nil
user=> (h/html [:h1 "Hello Word"]) ; (2)
"<h1>Hello Word</h1>"
- (1) 이름공간을 불러오는 또 다른 방법이다.
- (2) 클로저 자료구조를 HTML로 변환했다.
이제, 방명록 화면을 만들기 위해 src/mini_guestbook/handler.clj를 수정하자. 수정한 파일은 다음과 같다.
(ns mini-guestbook.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.core :refer (html)]
[hiccup.form :as form]
[hiccup.page :as page]
[ring.util.anti-forgery :as anti-forgery]))
(defn add-form [] ; (3)
(form/form-to [:post "/add"]
(anti-forgery/anti-forgery-field)
(form/text-area {:rows 2 :cols 30} "message") [:br]
(form/submit-button "전송")))
(defn messages []
'({:message "테스트1"} {:message "테스트2"})) ; (5)
(defn message-list [] ; (4)
[:ul
(map (fn [x] [:li (:message x)])
(messages))])
(defn index [] ; (2)
(html
[:head
[:title "방명록"]]
[:body
[:h1 "방명록"]
(add-form)
[:h2 "메시지"]
(message-list)]))
(defroutes app-routes
(GET "/" [] (index)) ; (1)
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
새로운 라이브러리를 추가했기 때문에 웹 서버를 다시 시작해야 한다. lein ring server
를 실행하여 화면이 어떻게 바뀌었는지 보자.
- (1) 기존 "Hello World" 대신 Hiccup으로 생성한 HTML을 제공하기 위해 index 함수를 호출한다.
- (2) Hiccup으로 전체 HTML을 만든다.
- (3) 방명록 입력 form을 만든다.
- (4) 방명록 메시지를 리스트 형식으로 보여준다.
- (5) 테스트를 위해 messages를 반환해서 미리 방명록 메시지를 보여주도록 했다.
CSS로 화면을 약간 더 이쁘게 만들자. resources/public 디렉토리에 다음 내용의 styles.css 파일을 만들자.
body {
background-color: Cornsilk;
}
textarea {
font-size: 120%;
}
input[type=submit] {
width: 60px;
margin: 5px 0px;
font-size: 120%;
}
h1 {
color: CornflowerBlue;
}
이 css 파일을 HTML에 적용하기 위해 src/mini_guestbook/handler.clj 파일의 index 함수에 (page/include-css "/styles.css")
를 다음처럼 추가하자.
(defn index []
(html
[:head
[:title "방명록"]
(page/include-css "/styles.css")]
[:body
[:h1 "방명록"]
(add-form)
[:h2 "메시지"]
(message-list)]))
브라우저 창을 다시 로딩하자. 화면이 바뀌는 것을 볼 수 있다.
브라우저의 방명록 화면에서 방명록을 작성한 후 전송해보자. 그러면 "Not Found"가 보인다. 이유는 작성한 메시지를 add-form 함수에서 /add 주소에 POST 형식으로 전송했는데, 이에 대응하는 라우트가 아직 없기 때문이다. 코드를 추가하자.
(ns mini-guestbook.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.core :refer (html)]
[hiccup.form :as form]
[hiccup.page :as page]
[ring.util.anti-forgery :as anti-forgery]
[ring.util.response :as resp])) ; (3)
...
(defn add [message]
(println message) ; (2)
(resp/redirect "/")) ; (3)
(defroutes app-routes
(GET "/" [] (index))
(POST "/add" [message] (add message)) ; (1)
(route/not-found "Not Found"))
...
- (1)
/add
경로에 POST 형식의 메시지를 받는 라우트를 추가했다. - (2) 명령창에서 메시지를 찍도록 수정했다.
- (3) 처리가 끝난 후,
/
경로로 다시 가도록 수정했다.
브라우저에서 http://localhost:3000/ 주소의 방명록 화면으로 가서 "안녕하세요"라는 메시지를 전송하면 명령창 화면에 그 메세지가 출력되는 것이 보인다.
$ lein ring server
2015-12-06 15:14:32.729:INFO:oejs.Server:jetty-7.6.13.v20130916
2015-12-06 15:14:32.767:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000
Started server on port 3000
안녕하세요
받은 메시지를 저장하기 위해 데이터베이스를 사용한다. H2라는 작은 SQL 데이터베이스를 사용하여 파일 형식으로 저장한다. 그리고 프로그램에서 H2와 연결하기 위해 clojure.java.jdbc라는 라이브러리를 사용한다. project.clj의 :dependencies
에 관련 라이브러리를 추가하자.
:dependencies [[org.clojure/clojure "1.7.0"]
[compojure "1.4.0"]
[ring/ring-defaults "0.1.5"]
[hiccup "1.0.5"]
[org.clojure/java.jdbc "0.4.2"]
[com.h2database/h2 "1.4.188"]]
REPL에서 데이터베이스의 초기 설정을 하자.
$ lein repl
jdbc 라이브러리를 불러들이자.
user=> (require '[clojure.java.jdbc :as sql])
nil
h2 접속 정보를 h2-db라는 이름으로 만들어둔다.
user=> (def h2-db {:dbtype "h2"
:dbname "./mini_guestbook_h2"})
#'user/h2-db
guestbook 테이블을 만들자. 이때 mini_guestbook_h2.mv.db라는 데이터베이스 파일이 생긴다.
user=> (sql/db-do-commands h2-db
"CREATE TABLE guestbook (message VARCHAR(128))")
(0)
테스트를 위해 메시지 2개를 데이터베이스에 넣자.
user=> (sql/insert! h2-db
:guestbook {:message "hello1"})
(nil)
user=> (sql/insert! h2-db
:guestbook {:message "hello2"})
(nil)
메세지가 잘 들어갔는지 확인해보자.
user=> (sql/query h2-db
"select * from guestbook")
({:message "hello1"} {:message "hello2"})
잘 된다.
다음처럼 수정한다.
(ns mini-guestbook.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.core :refer (html)]
[hiccup.form :as form]
[hiccup.page :as page]
[ring.util.anti-forgery :as anti-forgery]
[ring.util.response :as resp]
[clojure.java.jdbc :as sql])) ; (1)
(def h2-db {:dbtype "h2" ; (2)
:dbname "./mini_guestbook_h2"})
...
(defn messages []
(sql/query h2-db ; (3)
"select * from guestbook"))
...
- (1) jdbc 라이브러리를 불러들인다.
- (2) h2 연결 정보를 h2-db라는 이름으로 만든다.
- (3) 기존 테스트 메시지를 없애고, 데이터베이스에서 메시지를 가져온다.
lein ring server
를 다시 실행하면, 메시지가 기존의 "테스트1", "테스트2"에서 "hello1", "hello2"로 바뀐 것을 확인할 수 있다.
add 함수를 다음처럼 수정한다.
(defn add [message]
(println message)
(sql/insert! h2-db
:guestbook {:message message})
(resp/redirect "/"))
브라우저 창을 다시 로딩한다. 이제 새로운 방명록 메시지를 전송하면 밑에 추가된다.
미니 방명록 완성!
(defproject mini-guestbook "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.7.0"]
[compojure "1.4.0"]
[ring/ring-defaults "0.1.5"]
[hiccup "1.0.5"]
[org.clojure/java.jdbc "0.4.2"]
[com.h2database/h2 "1.4.188"]]
:plugins [[lein-ring "0.9.7"]]
:ring {:handler mini-guestbook.handler/app}
:profiles
{:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.0"]]}})
(ns mini-guestbook.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.core :refer (html)]
[hiccup.form :as form]
[hiccup.page :as page]
[ring.util.anti-forgery :as anti-forgery]
[ring.util.response :as resp]
[clojure.java.jdbc :as sql]))
(def h2-db {:dbtype "h2"
:dbname "./mini_guestbook_h2"})
(defn add-form []
(form/form-to [:post "/add"]
(anti-forgery/anti-forgery-field)
(form/text-area {:rows 2 :cols 30} "message") [:br]
(form/submit-button "전송")))
(defn messages []
(sql/query h2-db
"select * from guestbook"))
(defn message-list []
[:ul
(map (fn [x] [:li (:message x)])
(messages))])
(defn index []
(html
[:head
[:title "방명록"]
(page/include-css "/styles.css")]
[:body
[:h1 "방명록"]
(add-form)
[:h2 "메시지"]
(message-list)]))
(defn add [message]
(println message)
(sql/insert! h2-db
:guestbook {:message message})
(resp/redirect "/"))
(defroutes app-routes
(GET "/" [] (index))
(POST "/add" [message] (add message))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
다음을 참고하여 강의안을 만들었다.
- http://www.braveclojure.com/getting-started/
- http://clojure-doc.org/articles/tutorials/basic_web_development.html
- https://devcenter.heroku.com/articles/clojure-web-application
- http://www.xuan-wu.com/2013-09-21-Basic-Web-Application-in-Clojure
- 프로젝트
- 생성
- 실행
- 수정
- 빌드
- REPL
- 간단한 웹 어플리케이션
- 생성
- 웹서버 실행
- Ring, Compojure
- Hiccup
- 화면 만들기
- 화면 꾸미기
- 라우트 추가
- 데이터베이스 설정
- 데이터베이스 사용
https://docs.google.com/forms/d/17Q7jrxd_gP5PyOHVQWe-8_FFOmAeB6DiOVZdJDLQBbM/viewform