• #lib-noir

    Noir web framework이 지원 중단되고, 유용한 코드들을 뽑아서 라이브러리 형태로 빠져나왔다. 이 라이브러리는 유지보수가 계속 되고 있다.

    API문서는 http://yogthos.github.io/lib-noir/ 여기에서 확인한다

  • #lib-noir로 할 수 있는 일들

    1. 쿠키관리
    2. 세션관리
    3. 캐쉬관리
    4. 암호관리
    5. io관련 함수들
    6. response 함수들
    7. 정적인 리소스들 관리와 파일 업로딩
    8. 특별한 종류의 응답처리와 redirection
    9. 입력값들 점검
    10. content caching
    11. route filter하고 redirection하는 것들
    12. password hashing
  • #lib-noir에서 제공하는 미들웨어들
    lib-noir에서 몇가지 재밌는 미들웨어들을 제공한다

    1. wrap-access-rules
    2. wrap-canonical-host
    3. wrap-force-ssl
    4. wrap-rewrites
    5. wrap-strip-trailing-slash
  • #설치

    설치는 당근 leiningen으로 한다. project.clj파일을 열고 :dependencies키 아래에다가

    [lib-noir "0.6.9"]

    다했으면 lein deps를 실행해서 다운로드 받아둔다

  • #쿠키관리

    쿠키 관리는 noir.cookies namespace안에 정의되어 있다.
    쿠키 관리를 위해서 쿠키 관련 미들웨어를 추가한다.

    (-> your_handler
        (wrap-noir-cookies))

    미들웨어가 설치된 후로 각각 핸들러에서 쿠키관련 데이터들은

    1. get
    2. put!
    3. get-signed
    4. put-singed!

    함수들을 이용해서 접근할 수 있다

  • #세션관리

    세션관리는 noir.session namespace안에 구현되어있다.

    세션을 통해서 상태라는 개념을 HTTP위에다가 덧입힌다. 기본적으로 memory-store를 사용해서 상태를 저장하지만 :session-store 옵션을 사용해서 다른 세션 store를 사용할 수가 있다

    지원하는 함수는 2가지 종류로 갈린다. 하나는 session 그리고 다른 하나는 flash (한 request에 대해서만 유용하고 그 다음 request에 대해서는 사라지게 된다) 이다.

    session관련 함수는 아래와 같다.

    1. assoc-in! (assoc-in! ks v)
    2. clear! (clear) 싹 지워버려
    3. get (get k) or (get k default)
    4. get! 이것은 값을 읽고 삭제해버린다. (get! k) or (get! k default)
    5. get-in (get-in ks) 또는 (get-in ks defualt)
    6. get-in! get!와 똑같다.
    7. put! (put! k v) 값을 넣는다
    8. remove! (remove! k) 값을 삭제
    9. (swap! f & args) 함수 (f args)를 호출해서 리턴한 값을 session과 바꿔치깋나다. 쬐금 이상하다
    10. (update-in! ks f & args)

    flash관련 함수는 아래와 같다

    1. (flash-get k) (flash-get k not-found) flash로 저장된 값을 읽어온다
    2. (flash-put! k v) flash에 값을 저장한다

    noir.session 아래에 2개의 미들웨어를 제공한다.

    1. wrap-noir-session
    2. wrap-noir-flash - Flash를 지원해주는 미들웨어이다. (ring의 Flash 미들웨어를 기반으로 작성되었다.) 이 미들웨어를 쓰면 반드시 세션을 먼저 지원해주어야한다.
      • 순서가 very 중요
  • #Content Caching
    noir.util.cache 네임스페이스 아래에 캐쉬관련된 기능을 제공한다. 여기에서 이야기하는 캐쉬는 메모리에다가 잠간 저장해두는 캐쉬를 애기한다. clojure가 내려가면 완전히 날라가는 것들이다.

    말그대로 캐쉬인 것이다. 올바른 데이터는 반드시 어딘가에 잘 저장해두어야한다. 그러면 강력한 저항에 부딪히게 될 것이다. ㅋㅋㅋ

    1. (cache! id content) 컨텐트에 해당하는 가능한 캐쉬가 있는지 확인한다. 캐쉬가 없으면 제거한다.
    2. (clear!) 캐쉬를 완전히 날려버린다.
    3. (invalidate! id) 해당 id를 캐쉬에서 제거해버린다
    4. (set-size! items) 크기를 변경한다. 이 함수가 희한한 것은 왜 size를 주지않고 clojure map을 주는가 하는 것이다. 키값으로 [:options :size]가 있어야한다. (즉, map에 map이 있어야한다. )
    5. (set-timeout! seconds) 이 시간보다 크게되면 캐쉬를 삭제한다. 기본값으로는 timeout이 적용이 안되어있다. 따라서 사이즈가 버텨주는 한 무한정으로 남아있다.
  • 암호관리

    보통 웹 상에서 보안이 필요한 부분은 데이터를 암호화해서 저장하거나 비교한다. 바로 이 라이브러리가 이것을 위해서 필요한 녀석이다.

    기본적으로 암호와 하는 함수, 그리고 비교하는 함수 둘을 제공한다

    1. (compare raw encrypted)
    2. (encrypt salt raw) 아니면 `(encrypt raw)
  • #io 관련 함수들
    noir.io아래에 있는 함수들은 아래와 같다

    1. (get-resource relative-path)
    2. (resource-path)
    3. (slurp-resource path) path에 해당하는 파일을 읽어서 문자열로 돌려준다
    4. (upload-file relative-path {:keys [tempfile size filename size]})
  • Response함수들

    Response를 작성할때 도움이 되는 함수들을 돌려준다. 신기하게도 이 함수들은 response를 따로 파라미터로 받지 않고

    1. (content-type ctype content) Content-Type을 설정하고 body도 content로 바꾼다.
    2. (edn data) data는 clojure data를 넣어주면 content-type을 자동으로 설정한다
    3. (empty) 200상태코드이지만 빈 body를 돌려줌
    4. (json content) Content를 JSON형태로 변환된 response를 만들어준다. content type도 자동으로 application/json으로 만들어준다
    5. (jsonp function-name content) Content를 JSON으로 만들고 주어진 이름으로 호출하도록 만든 body를 갖도록 한다.
    6. (redirect url)
      (redirect url type)
      (redirect url type request)
      Ring에 있는 redirection하는 것보다 더 디테일한 옵션을 제공해준다
    7. (set-headers headers content) 헤더와 content를 추가로 받는다
    8. (status code content) 상태코드와 컨텐트 받아서 response를 만들어준다
    9. (xml content) XML형태로 content-type을 설정해서 response를 만들어준다.
  • #wrap-access-rules
    (wrap-access-rules handler rules)
    이 미들웨어는 핸들러에 특별한 rule을 붙여넣고 특별한 조건이 들어맞으면 핸들러를 실행하게 한다.

    예를 들어 사용자 인증이된 상태에서만, 접근가능한 페이지를 보여준다던지 하는 일종의 비밀페이지들을 관리할 때 사용한다

    이 미들웨어와 같이 짝을 맞춰서 사용하는 것이 noir.util.route/restricted 매크로이다. rule을 적용하기 위해서는 맞추어 써야한다. 만약 그룹으로 묶어서 페이지가 필요하다면 noir.util.route/def-restricted-routes를 사용한다.

  • #쿠키관리 예제

    get / put! / get-signed / put-signed! 이 함수들 모두 cookie에 접속하는데 dynamic binding feature를 이용해서 접근한다. 그래서 코드에서 별도로 request map을 참조하는 것 없이 내부에서 알아서 한다. dynamic binding은 thread local storage와 동일하게 작동한다는 것을 다시 한번 상기하자

    (ns ln00.handler
      (:use compojure.core)
      (:require [compojure.handler :as handler]
                [compojure.route :as route]
                [noir.cookies :as ncookies]
                ))
    
    (defn cookie-handler [req]
      (let [counter (ncookies/get :counter 1)
            counter (Integer/parseInt counter)]
        (do
          (ncookies/put! :counter (inc counter))
          (str "COOKIE HANDLER SAMPLE: counter = " counter)
          )))
    
    (defroutes app-routes
      (GET "/c" [] cookie-handler)
      (GET "/" [] "Hello World Test")
      (route/resources "/")
      (route/not-found "Not Found"))
    
    (def app
      (->
        (handler/site app-routes)
        (ncookies/wrap-noir-cookies)
        ))
  • #세션관리 예제 - Session

    세션에다가 counter를 저장하고 하나씩 늘리는 예제이다. 여기서 cookie하고 쓸때 다른 점은 counter값이 언제나 integer로 나온다는 것이다. 따로 parseInt를 호출해줄 필요가 없다.

    (ns ln00.handler
      (:use compojure.core)
      (:require [compojure.handler :as handler]
                [compojure.route :as route]
                [noir.session :as nsession]
                ))
    
    (defn session-handler [req]
      (let [cnt (nsession/get :counter 0)
            nxt (inc cnt)
            nxt (nsession/put! :counter nxt)]
        (str "SESSION: current counter " cnt))
      )
    
    (defroutes app-routes
      (GET "/" [] "Hello World Test")
      (GET "/s" [] session-handler)
      (route/resources "/")
      (route/not-found "Not Found"))
    
    (def app
      (->
        (handler/site app-routes)
        (nsession/wrap-noir-session)
        ))
  • #세션관리예제 - Flash

    미들웨어를 놓는 순서가 중요하다. 먼저 flash, 그리고 그 다음에 세션을 넣도록.

    (ns ln00.handler
      (:use compojure.core)
      (:require [compojure.handler :as handler]
                [compojure.route :as route]
                [noir.session :as nsession]
                ))
    
    (def NOT-FOUND "not-found")
    (defn flash-handler [req]
      (let [cnt (nsession/flash-get :flash NOT-FOUND)]
        (do
          (if (= cnt NOT-FOUND)
            (nsession/flash-put! :flash "FIND"))
          (str "FLASH: current counter " cnt)))
      )
    
    (defroutes app-routes
      (GET "/" [] "Hello World Test")
      (GET "/f" [] flash-handler)
      (route/resources "/")
      (route/not-found "Not Found"))
    
    (def app
      (->
        (handler/site app-routes)
        (nsession/wrap-noir-flash)
        (nsession/wrap-noir-session)
        ))
  • 캐쉬사용 예

    (ns ln00.handler
      (:use compojure.core)
      (:require [compojure.handler :as handler]
                [compojure.route :as route]
                [noir.util.cache :as cache]
                ))
    
    (def NOT-FOUND "not-found")
    
    (defn super-duper-calc []
      (str "CURRENT TIME: " (.getTime (new java.util.Date))))
    
    (defn cache-handler [req]
      (cache/cache! :super-complicated (super-duper-calc)))
    
    (defroutes app-routes
      (GET "/" [] "Hello World Test")
      (GET "/c" [] cache-handler)
      (route/resources "/")
      (route/not-found "Not Found"))
    
    (def app
      (->
        (handler/site app-routes)))

    위의 예제를 실행한 후에 http://localhost:3000/c로 접속하면 현재 시간을 돌려준다. 하지만 이후의 접속에서는 계쏙 같은 값을 보여준다. 왜냐면 이미 캐쉬가 되었기 때문에 다시 값을 넣지 않는다.

  • 암호관리 사용 예

    ln00.handler=> (require '[noir.util.crypt :as crypt])
    nil
    
    ;; salt하면 추가로 암호화 할 때 사용할 문자열을 생성해준다.
    ln00.handler=> (crypt/gen-salt 10)
    "$2a$10$CrfjC9JITz/o3dcohYEPne"
    
    ;; 암호와 시작
    ln00.handler=> (crypt/encrypt "abc")
    "$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNG"
    
    ;; 비교 시작
    ln00.handler=> (crypt/compare "abc" "$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNG")
    true
    
    ;; 여기에서 마지막의 대문자 G를 소문자 g로 바꾸어보았다.
    ;; 일부러 실패하려고
    ln00.handler=> (crypt/compare "abc" "$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNg")
    false
  • io 관련 코드 예제 - PART I

    get-resource함수는 resource 아래에 있는 public폴더를 기준으로 상대경로에 있는 파일녀석에 대한 URL 개체를 돌려준다. (URL은 자바 object이다. http://docs.oracle.com/javase/7/docs/api/java/net/URL.html 가면 설명이 있다.)

    지정한 파일이 있어야지만 알맞은 URL객체를 돌려준다. 그렇지 않으면 nil이다.

    (require '[noir.io :as io])
    (io/get-resource "/index.html")
    => #<URL file:/C:/prjs/samples/cljstudy/ln00/resources/public/index.html>

    (resource-path)는 시스템에서 resource 폴더의 경로가 어디인지를 알려준다.

    ln00.handler=> (io/resource-path)
    "/C:/prjs/samples/cljstudy/ln00/resources/public%5c"
  • io 관련 코드 예제 - PART II

    (slurp-resource path)는 당근 slurp이 있으니까 resource 폴더 아래에 있는 리소스에대한 내용을 문자열로 돌려준다.

    ln00.handler=> (io/slurp-resource "/index.html")
    "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title></title>\r\n</head>\r\n<body>\r\n\r\n</body>\r\n</html>"

    upload-file함수는 tempfile로 주어진 file을 resource-file-path/relative-path/filename으로 복사한다. 말이 업로드이지 사실은 카피이다. 하지만 resource folder로 아래로 복사해주는 기능은 정말 괜찮다.

  • #Response함수들 예제 I

    (content-type ctype content) 컨텐트 타입과 컨텐트를 받아서 각각 헤더와 body에 집어넣는다. response map을 생성해낸다.

    ln00.handler=> (noir.response/content-type "application/json" "{}")
    {:headers {"Content-Type" "application/json"}, :body "{}"}

    (edn data) 예제

    ln00.handler=> (noir.response/edn {:thanks "a lot"})
    {:headers {"Content-Type" "application/edn; charset=utf-8"}, :body "{:thanks \"a lot\"}"}

    (empty) 예제

    ln00.handler=> (noir.response/empty)
    {:status 200, :body ""}
  • #Response함수들 예제 II

    (json content) 예제

    ln00.handler=> (noir.response/json {:thanks "wonderful"})
    {:headers {"Content-Type" "application/json; charset=utf-8"}, :body "{\"thanks\":\"wonderful\"}"}

    (jsonp function-name content) 예제

    JSONP는 same-origin-policy를 극복하기 위해서 많이들 써먹는 방법이다. 함수 이름을 어떤것으로 할지는 보통 jsonp를 요청할 때 query parameter에 알아서 잘 넣은다음에 알려준다.

    ln00.handler=> (noir.response/jsonp "dummy-func" [1 2 3])
    {:headers {"Content-Type" "application/json; charset=utf-8"}, :body "dummy-func([1,2,3]);"}

    (redirect url)예제

    ln00.handler=> (noir.response/redirect "http://www.google.com")
    {:status 302, :headers {"Location" "http://www.google.com"}, :body ""}
    ln00.handler=> (noir.response/redirect "http://www.google.com" :permanent)
    {:status 301, :headers {"Location" "http://www.google.com"}, :body ""}
    ln00.handler=> (noir.response/redirect "http://www.google.com" :found)
    {:status 302, :headers {"Location" "http://www.google.com"}, :body ""}
    ln00.handler=> (noir.response/redirect "http://www.google.com" :see-other)
    {:status 303, :headers {"Location" "http://www.google.com"}, :body ""}
  • #Respnse함수들 예제 III

    (set-headers headers content) 함수는 헤더맵과 컨텐트를 받아서 clojure map을 만들어 준다.

    ln00.handler=> (noir.response/set-headers {"Content-Type" "application/json"} "abc")
    {:headers {"Content-Type" "application/json"}, :body "abc"}

    (status code content) 예제

    ln00.handler=> (noir.response/status 201 "created - ")
    {:status 201, :body "created - "}

    (xml content) 예제

    여기서 주는 컨텐트는 문자열이다. jsonedn의 경우에는 알아서 바꾸어주었는데 xml은 그렇지가 않다.

    ln00.handler=> (noir.response/xml "<hello>world</hello>")
    {:headers {"Content-Type" "text/xml; charset=utf-8"}, :body "<hello>world</hello>"}
  • #wrap-access-rules 예제

    (ns ln00.handler
      (:use compojure.core)
      (:require [compojure.handler :as handler]
                [compojure.route :as route]
                [noir.session :as session]
                [noir.util.middleware :as nm]
                [noir.util.route :as nroute]
                ))
    
    (def NOT-FOUND "not-found")
    
    ;; 세션에다가 user id를 저장해둔다
    (defn register-user [req id]
        (session/put! :user-id id)
        (str "user " id " is successfully registered")
        )
    
    ;; profile에 접근한다
    (defn access-profile [req id]
      (str "Hello " id ". You love Clojure"))
    
    ;; profile에 접근할 때 오직 사용자가 jay인 사람만
    ;; 접근 가능하게 한다. 물론 여기를 세션으로 처리한다면
    ;; 특별한 세션 id가 있는 경우로 제한할 수가 있다
    (defn rule-user-access [req]
      (if-let [uid (session/get :user-id)]
        (= uid "jay")
        ))
    
    (defroutes app-routes
      (GET "/" [] "Access rule sample")
      (GET "/user/:id" [id :as r] (register-user r id))
      ;; 접근을 제한시킨다. 이때 noir.util.route/restricted 매크로를 사용한다
      (GET "/user/:id/profile" [id :as r] (nroute/restricted (access-profile r id)))
      (GET "/unauthorized" [] "UNAUTHORIZED access checked. ONLY jay can access profile")
      (route/resources "/")
      (route/not-found "Not Found"))
    
    (def app
      (->
        (handler/site app-routes)
        (session/wrap-noir-session)
        (nm/wrap-access-rules [{:redirect "/unauthorized"
                                :rule rule-user-access}])
        ))
{"cards":[{"_id":"3638383d271f22c75400000c","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":null,"content":"#lib-noir\n\nNoir web framework이 지원 중단되고, 유용한 코드들을 뽑아서 라이브러리 형태로 빠져나왔다. 이 라이브러리는 유지보수가 계속 되고 있다.\n\nAPI문서는 http://yogthos.github.io/lib-noir/ 여기에서 확인한다"},{"_id":"36383caa271f22c75400000e","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"3638383d271f22c75400000c","content":"#설치\n\n설치는 당근 leiningen으로 한다. project.clj파일을 열고 :dependencies키 아래에다가 \n\n [lib-noir \"0.6.9\"]\n\n다했으면 lein deps를 실행해서 다운로드 받아둔다"},{"_id":"36383a36271f22c75400000d","treeId":"3638382e271f22c754000009","seq":1,"position":2,"parentId":null,"content":"#lib-noir로 할 수 있는 일들\n1. 쿠키관리\n2. 세션관리\n3. 캐쉬관리\n4. 암호관리\n5. io관련 함수들\n6. response 함수들\n2. 정적인 리소스들 관리와 파일 업로딩\n3. 특별한 종류의 응답처리와 redirection\n4. 입력값들 점검\n5. content caching\n6. route filter하고 redirection하는 것들\n7. password hashing\n\n\n"},{"_id":"36384822271f22c75400000f","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"36383a36271f22c75400000d","content":"#쿠키관리\n\n쿠키 관리는 `noir.cookies` namespace안에 정의되어 있다.\n쿠키 관리를 위해서 쿠키 관련 미들웨어를 추가한다.\n\n (-> your_handler\n (wrap-noir-cookies))\n\n미들웨어가 설치된 후로 각각 핸들러에서 쿠키관련 데이터들은 \n\n1. get\n2. put!\n3. get-signed\n4. put-singed!\n\n\n함수들을 이용해서 접근할 수 있다\n"},{"_id":"3639918e201f28fd34000019","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"36384822271f22c75400000f","content":"#쿠키관리 예제\n\n`get` / `put!` / `get-signed` / `put-signed!` 이 함수들 모두 cookie에 접속하는데 dynamic binding feature를 이용해서 접근한다. 그래서 코드에서 별도로 request map을 참조하는 것 없이 내부에서 알아서 한다. dynamic binding은 thread local storage와 동일하게 작동한다는 것을 다시 한번 상기하자\n\n (ns ln00.handler\n (:use compojure.core)\n (:require [compojure.handler :as handler]\n [compojure.route :as route]\n [noir.cookies :as ncookies]\n ))\n\n (defn cookie-handler [req]\n (let [counter (ncookies/get :counter 1)\n counter (Integer/parseInt counter)]\n (do\n (ncookies/put! :counter (inc counter))\n (str \"COOKIE HANDLER SAMPLE: counter = \" counter)\n )))\n\n (defroutes app-routes\n (GET \"/c\" [] cookie-handler)\n (GET \"/\" [] \"Hello World Test\")\n (route/resources \"/\")\n (route/not-found \"Not Found\"))\n \n (def app\n (->\n (handler/site app-routes)\n (ncookies/wrap-noir-cookies)\n ))\n"},{"_id":"363848b5271f22c754000010","treeId":"3638382e271f22c754000009","seq":1,"position":2,"parentId":"36383a36271f22c75400000d","content":"#세션관리\n\n세션관리는 `noir.session` namespace안에 구현되어있다. \n\n세션을 통해서 **상태**라는 개념을 HTTP위에다가 덧입힌다. 기본적으로 memory-store를 사용해서 상태를 저장하지만 :session-store 옵션을 사용해서 다른 세션 store를 사용할 수가 있다\n\n지원하는 함수는 2가지 종류로 갈린다. 하나는 session 그리고 다른 하나는 flash *(한 request에 대해서만 유용하고 그 다음 request에 대해서는 사라지게 된다)* 이다. \n\nsession관련 함수는 아래와 같다.\n1. assoc-in! `(assoc-in! ks v)`\n2. clear! `(clear)` 싹 지워버려\n3. get `(get k) or (get k default)`\n4. get! 이것은 값을 읽고 삭제해버린다. `(get! k)` or `(get! k default)`\n5. get-in `(get-in ks)` 또는 `(get-in ks defualt)`\n6. get-in! get!와 똑같다. \n7. put! (put! k v) 값을 넣는다\n8. remove! (remove! k) 값을 삭제\n9. `(swap! f & args)` 함수 (f args)를 호출해서 리턴한 값을 session과 바꿔치깋나다. 쬐금 이상하다\n10. (update-in! ks f & args)\n\nflash관련 함수는 아래와 같다\n1. `(flash-get k)` `(flash-get k not-found)` flash로 저장된 값을 읽어온다\n2. `(flash-put! k v)` flash에 값을 저장한다\n\n\n\n## `noir.session` 아래에 2개의 미들웨어를 제공한다.\n1. `wrap-noir-session`\n2. `wrap-noir-flash` - Flash를 지원해주는 미들웨어이다. (ring의 Flash 미들웨어를 기반으로 작성되었다.) 이 미들웨어를 쓰면 반드시 세션을 먼저 지원해주어야한다.\n * 순서가 very 중요\n\n\n\n\n"},{"_id":"3639c3d9201f28fd3400001b","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"363848b5271f22c754000010","content":"#세션관리 예제 - Session\n\n세션에다가 counter를 저장하고 하나씩 늘리는 예제이다. 여기서 cookie하고 쓸때 다른 점은 counter값이 언제나 integer로 나온다는 것이다. 따로 `parseInt`를 호출해줄 필요가 없다.\n\n```\n(ns ln00.handler\n (:use compojure.core)\n (:require [compojure.handler :as handler]\n [compojure.route :as route]\n [noir.session :as nsession]\n ))\n\n(defn session-handler [req]\n (let [cnt (nsession/get :counter 0)\n nxt (inc cnt)\n nxt (nsession/put! :counter nxt)]\n (str \"SESSION: current counter \" cnt))\n )\n\n(defroutes app-routes\n (GET \"/\" [] \"Hello World Test\")\n (GET \"/s\" [] session-handler)\n (route/resources \"/\")\n (route/not-found \"Not Found\"))\n\n(def app\n (->\n (handler/site app-routes)\n (nsession/wrap-noir-session)\n ))\n\n```\n\n"},{"_id":"3639ecce201f28fd3400001c","treeId":"3638382e271f22c754000009","seq":1,"position":2,"parentId":"363848b5271f22c754000010","content":"#세션관리예제 - Flash\n\n미들웨어를 놓는 순서가 중요하다. 먼저 flash, 그리고 그 다음에 세션을 넣도록.\n\n```\n(ns ln00.handler\n (:use compojure.core)\n (:require [compojure.handler :as handler]\n [compojure.route :as route]\n [noir.session :as nsession]\n ))\n\n(def NOT-FOUND \"not-found\")\n(defn flash-handler [req]\n (let [cnt (nsession/flash-get :flash NOT-FOUND)]\n (do\n (if (= cnt NOT-FOUND)\n (nsession/flash-put! :flash \"FIND\"))\n (str \"FLASH: current counter \" cnt)))\n )\n\n(defroutes app-routes\n (GET \"/\" [] \"Hello World Test\")\n (GET \"/f\" [] flash-handler)\n (route/resources \"/\")\n (route/not-found \"Not Found\"))\n\n(def app\n (->\n (handler/site app-routes)\n (nsession/wrap-noir-flash)\n (nsession/wrap-noir-session)\n ))\n\n```\n"},{"_id":"364324b8f503357b4f00003f","treeId":"3638382e271f22c754000009","seq":1,"position":3,"parentId":"36383a36271f22c75400000d","content":"#Content Caching\n`noir.util.cache` 네임스페이스 아래에 캐쉬관련된 기능을 제공한다. 여기에서 이야기하는 캐쉬는 메모리에다가 잠간 저장해두는 캐쉬를 애기한다. clojure가 내려가면 완전히 날라가는 것들이다.\n\n말그대로 캐쉬인 것이다. 올바른 데이터는 반드시 어딘가에 잘 저장해두어야한다. 그러면 강력한 저항에 부딪히게 될 것이다. ㅋㅋㅋ\n\n1. `(cache! id content)` 컨텐트에 해당하는 가능한 캐쉬가 있는지 확인한다. 캐쉬가 없으면 제거한다.\n2. `(clear!)` 캐쉬를 완전히 날려버린다.\n3. `(invalidate! id)` 해당 id를 캐쉬에서 제거해버린다\n4. `(set-size! items)` 크기를 변경한다. 이 함수가 희한한 것은 왜 size를 주지않고 clojure map을 주는가 하는 것이다. 키값으로 [:options :size]가 있어야한다. (즉, map에 map이 있어야한다. )\n5. `(set-timeout! seconds)` 이 시간보다 크게되면 캐쉬를 삭제한다. 기본값으로는 `timeout`이 적용이 **안되어있다**. 따라서 사이즈가 버텨주는 한 무한정으로 남아있다."},{"_id":"36433382f503357b4f000040","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"364324b8f503357b4f00003f","content":"# 캐쉬사용 예\n\n```\n(ns ln00.handler\n (:use compojure.core)\n (:require [compojure.handler :as handler]\n [compojure.route :as route]\n [noir.util.cache :as cache]\n ))\n\n(def NOT-FOUND \"not-found\")\n\n(defn super-duper-calc []\n (str \"CURRENT TIME: \" (.getTime (new java.util.Date))))\n\n(defn cache-handler [req]\n (cache/cache! :super-complicated (super-duper-calc)))\n\n(defroutes app-routes\n (GET \"/\" [] \"Hello World Test\")\n (GET \"/c\" [] cache-handler)\n (route/resources \"/\")\n (route/not-found \"Not Found\"))\n\n(def app\n (->\n (handler/site app-routes)))\n\n```\n\n위의 예제를 실행한 후에 http://localhost:3000/c로 접속하면 현재 시간을 돌려준다. 하지만 이후의 접속에서는 계쏙 같은 값을 보여준다. 왜냐면 이미 캐쉬가 되었기 때문에 다시 값을 넣지 않는다.\n"},{"_id":"3646e36a21824a3ac8000041","treeId":"3638382e271f22c754000009","seq":1,"position":4,"parentId":"36383a36271f22c75400000d","content":"# 암호관리\n보통 웹 상에서 보안이 필요한 부분은 데이터를 암호화해서 저장하거나 비교한다. 바로 이 라이브러리가 이것을 위해서 필요한 녀석이다.\n\n기본적으로 암호와 하는 함수, 그리고 비교하는 함수 둘을 제공한다\n\n1. `(compare raw encrypted)`\n2. `(encrypt salt raw)` 아니면 `(encrypt raw)\n\n"},{"_id":"36470c1d21824a3ac8000042","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"3646e36a21824a3ac8000041","content":"# 암호관리 사용 예\n\n```\nln00.handler=> (require '[noir.util.crypt :as crypt])\nnil\n\n;; salt하면 추가로 암호화 할 때 사용할 문자열을 생성해준다.\nln00.handler=> (crypt/gen-salt 10)\n\"$2a$10$CrfjC9JITz/o3dcohYEPne\"\n\n;; 암호와 시작\nln00.handler=> (crypt/encrypt \"abc\")\n\"$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNG\"\n\n;; 비교 시작\nln00.handler=> (crypt/compare \"abc\" \"$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNG\")\ntrue\n\n;; 여기에서 마지막의 대문자 G를 소문자 g로 바꾸어보았다.\n;; 일부러 실패하려고\nln00.handler=> (crypt/compare \"abc\" \"$2a$10$J1eVCc5swEng.1TKP.QrUesyS8q0UCMAvNLTt087FytqXE2EkFSNg\")\nfalse\n```\n\n"},{"_id":"36470e9521824a3ac8000043","treeId":"3638382e271f22c754000009","seq":1,"position":5,"parentId":"36383a36271f22c75400000d","content":"#io 관련 함수들\nnoir.io아래에 있는 함수들은 아래와 같다\n\n1. `(get-resource relative-path)`\n2. `(resource-path)`\n3. `(slurp-resource path)` path에 해당하는 파일을 읽어서 문자열로 돌려준다\n4. `(upload-file relative-path {:keys [tempfile size filename size]})`\n"},{"_id":"36471a9d21824a3ac8000044","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"36470e9521824a3ac8000043","content":"# io 관련 코드 예제 - PART I\n\n`get-resource`함수는 resource 아래에 있는 public폴더를 기준으로 상대경로에 있는 파일녀석에 대한 URL 개체를 돌려준다. (**URL은 자바 object이다. http://docs.oracle.com/javase/7/docs/api/java/net/URL.html 가면 설명이 있다.**)\n\n지정한 파일이 있어야지만 알맞은 URL객체를 돌려준다. 그렇지 않으면 nil이다.\n\n```\n(require '[noir.io :as io])\n(io/get-resource \"/index.html\")\n=> #<URL file:/C:/prjs/samples/cljstudy/ln00/resources/public/index.html>\n```\n\n`(resource-path)`는 시스템에서 resource 폴더의 경로가 어디인지를 알려준다.\n```\nln00.handler=> (io/resource-path)\n\"/C:/prjs/samples/cljstudy/ln00/resources/public%5c\"\n```\n\n\n\n"},{"_id":"364728e021824a3ac8000045","treeId":"3638382e271f22c754000009","seq":1,"position":2,"parentId":"36470e9521824a3ac8000043","content":"# io 관련 코드 예제 - PART II\n\n`(slurp-resource path)`는 당근 slurp이 있으니까 resource 폴더 아래에 있는 리소스에대한 내용을 문자열로 돌려준다.\n\n```\nln00.handler=> (io/slurp-resource \"/index.html\")\n\"<!DOCTYPE html>\\r\\n<html>\\r\\n<head>\\r\\n <title></title>\\r\\n</head>\\r\\n<body>\\r\\n\\r\\n</body>\\r\\n</html>\"\n```\n\n`upload-file`함수는 tempfile로 주어진 file을 resource-file-path/relative-path/filename으로 복사한다. 말이 업로드이지 사실은 카피이다. 하지만 resource folder로 아래로 복사해주는 기능은 정말 괜찮다.\n\n"},{"_id":"36473b9021824a3ac8000046","treeId":"3638382e271f22c754000009","seq":1,"position":6,"parentId":"36383a36271f22c75400000d","content":"# Response함수들\nResponse를 작성할때 도움이 되는 함수들을 돌려준다. 신기하게도 이 함수들은 response를 따로 파라미터로 받지 않고\n\n1. `(content-type ctype content)` Content-Type을 설정하고 body도 content로 바꾼다.\n2. `(edn data)` data는 clojure data를 넣어주면 content-type을 자동으로 설정한다\n3. `(empty)` 200상태코드이지만 빈 body를 돌려줌\n4. `(json content)` Content를 JSON형태로 변환된 response를 만들어준다. content type도 자동으로 application/json으로 만들어준다\n5. `(jsonp function-name content)` Content를 JSON으로 만들고 주어진 이름으로 호출하도록 만든 body를 갖도록 한다. \n6. `(redirect url)`\n`(redirect url type)`\n`(redirect url type request)`\nRing에 있는 redirection하는 것보다 더 디테일한 옵션을 제공해준다\n7. `(set-headers headers content)` 헤더와 content를 추가로 받는다\n8. `(status code content)` 상태코드와 컨텐트 받아서 response를 만들어준다\n9. `(xml content)` XML형태로 content-type을 설정해서 response를 만들어준다."},{"_id":"364745eb21824a3ac8000047","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"36473b9021824a3ac8000046","content":"#Response함수들 예제 I\n\n`(content-type ctype content)` 컨텐트 타입과 컨텐트를 받아서 각각 헤더와 body에 집어넣는다. response map을 생성해낸다.\n\n```\nln00.handler=> (noir.response/content-type \"application/json\" \"{}\")\n{:headers {\"Content-Type\" \"application/json\"}, :body \"{}\"}\n```\n\n`(edn data)` 예제\n```\nln00.handler=> (noir.response/edn {:thanks \"a lot\"})\n{:headers {\"Content-Type\" \"application/edn; charset=utf-8\"}, :body \"{:thanks \\\"a lot\\\"}\"}\n```\n\n`(empty)` 예제\n```\nln00.handler=> (noir.response/empty)\n{:status 200, :body \"\"}\n```\n\n"},{"_id":"36477623a64d1e92bf000018","treeId":"3638382e271f22c754000009","seq":1,"position":2,"parentId":"36473b9021824a3ac8000046","content":"#Response함수들 예제 II\n\n`(json content)` 예제\n\n```\nln00.handler=> (noir.response/json {:thanks \"wonderful\"})\n{:headers {\"Content-Type\" \"application/json; charset=utf-8\"}, :body \"{\\\"thanks\\\":\\\"wonderful\\\"}\"}\n```\n\n\n`(jsonp function-name content)` 예제\n\nJSONP는 same-origin-policy를 극복하기 위해서 많이들 써먹는 방법이다. 함수 이름을 어떤것으로 할지는 보통 jsonp를 요청할 때 query parameter에 알아서 잘 넣은다음에 알려준다.\n\n```\nln00.handler=> (noir.response/jsonp \"dummy-func\" [1 2 3])\n{:headers {\"Content-Type\" \"application/json; charset=utf-8\"}, :body \"dummy-func([1,2,3]);\"}\n```\n\n`(redirect url)`예제\n```\nln00.handler=> (noir.response/redirect \"http://www.google.com\")\n{:status 302, :headers {\"Location\" \"http://www.google.com\"}, :body \"\"}\nln00.handler=> (noir.response/redirect \"http://www.google.com\" :permanent)\n{:status 301, :headers {\"Location\" \"http://www.google.com\"}, :body \"\"}\nln00.handler=> (noir.response/redirect \"http://www.google.com\" :found)\n{:status 302, :headers {\"Location\" \"http://www.google.com\"}, :body \"\"}\nln00.handler=> (noir.response/redirect \"http://www.google.com\" :see-other)\n{:status 303, :headers {\"Location\" \"http://www.google.com\"}, :body \"\"}\n```\n"},{"_id":"3647999ca64d1e92bf000019","treeId":"3638382e271f22c754000009","seq":1,"position":3,"parentId":"36473b9021824a3ac8000046","content":"#Respnse함수들 예제 III\n\n`(set-headers headers content)` 함수는 헤더맵과 컨텐트를 받아서 clojure map을 만들어 준다.\n\n```\nln00.handler=> (noir.response/set-headers {\"Content-Type\" \"application/json\"} \"abc\")\n{:headers {\"Content-Type\" \"application/json\"}, :body \"abc\"}\n```\n\n`(status code content)` 예제\n```\nln00.handler=> (noir.response/status 201 \"created - \")\n{:status 201, :body \"created - \"}\n```\n\n`(xml content)` 예제\n\n여기서 주는 컨텐트는 문자열이다. `json`과 `edn`의 경우에는 알아서 바꾸어주었는데 xml은 그렇지가 않다.\n\n```\nln00.handler=> (noir.response/xml \"<hello>world</hello>\")\n{:headers {\"Content-Type\" \"text/xml; charset=utf-8\"}, :body \"<hello>world</hello>\"}\n```"},{"_id":"3650491deaaf8bff5d00001a","treeId":"3638382e271f22c754000009","seq":1,"position":3,"parentId":null,"content":"#lib-noir에서 제공하는 미들웨어들\nlib-noir에서 몇가지 재밌는 미들웨어들을 제공한다\n\n1. wrap-access-rules\n2. wrap-canonical-host\n3. wrap-force-ssl\n4. wrap-rewrites\n5. wrap-strip-trailing-slash\n\n"},{"_id":"36504c87eaaf8bff5d00001b","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"3650491deaaf8bff5d00001a","content":"#wrap-access-rules\n`(wrap-access-rules handler rules)`\n이 미들웨어는 핸들러에 특별한 rule을 붙여넣고 특별한 조건이 들어맞으면 핸들러를 실행하게 한다.\n\n예를 들어 사용자 인증이된 상태에서만, 접근가능한 페이지를 보여준다던지 하는 일종의 비밀페이지들을 관리할 때 사용한다\n\n이 미들웨어와 같이 짝을 맞춰서 사용하는 것이 `noir.util.route/restricted` 매크로이다. rule을 적용하기 위해서는 맞추어 써야한다. 만약 그룹으로 묶어서 페이지가 필요하다면 `noir.util.route/def-restricted-routes`를 사용한다."},{"_id":"36505507eaaf8bff5d00001c","treeId":"3638382e271f22c754000009","seq":1,"position":1,"parentId":"36504c87eaaf8bff5d00001b","content":"#wrap-access-rules 예제\n\n```\n(ns ln00.handler\n (:use compojure.core)\n (:require [compojure.handler :as handler]\n [compojure.route :as route]\n [noir.session :as session]\n [noir.util.middleware :as nm]\n [noir.util.route :as nroute]\n ))\n\n(def NOT-FOUND \"not-found\")\n\n;; 세션에다가 user id를 저장해둔다\n(defn register-user [req id]\n (session/put! :user-id id)\n (str \"user \" id \" is successfully registered\")\n )\n\n;; profile에 접근한다\n(defn access-profile [req id]\n (str \"Hello \" id \". You love Clojure\"))\n\n;; profile에 접근할 때 오직 사용자가 jay인 사람만\n;; 접근 가능하게 한다. 물론 여기를 세션으로 처리한다면\n;; 특별한 세션 id가 있는 경우로 제한할 수가 있다\n(defn rule-user-access [req]\n (if-let [uid (session/get :user-id)]\n (= uid \"jay\")\n ))\n\n(defroutes app-routes\n (GET \"/\" [] \"Access rule sample\")\n (GET \"/user/:id\" [id :as r] (register-user r id))\n ;; 접근을 제한시킨다. 이때 noir.util.route/restricted 매크로를 사용한다\n (GET \"/user/:id/profile\" [id :as r] (nroute/restricted (access-profile r id)))\n (GET \"/unauthorized\" [] \"UNAUTHORIZED access checked. ONLY jay can access profile\")\n (route/resources \"/\")\n (route/not-found \"Not Found\"))\n\n(def app\n (->\n (handler/site app-routes)\n (session/wrap-noir-session)\n (nm/wrap-access-rules [{:redirect \"/unauthorized\"\n :rule rule-user-access}])\n ))\n\n```"}],"tree":{"_id":"3638382e271f22c754000009","name":"lib-noir","publicUrl":"lib-noir"}}