• Clojure Ring

    Ring을 공부해보자

    여기 트리에서는 아래 순서로 정리한다.
    (주요 내용은 모두 다 https://github.com/ring-clojure/ring/wiki - Ring의 위키 페이지에서 가져왔다 )

    • 기본 개념
    • Utility 함수들
    • Middlewares
    • Ring의 Toolchains
  • 기본 개념

    거기에는 세가지 개념이 있다

    • handler
    • middleware
    • adapter
  • 기본 중의 기본 - Request / Response

    Ring에서 http request는 clojure map으로, http response또한 clojure map으로 표현이된다.

    여기 기본중의 기본으로 request에 어떤 내용이 실려서 전달되는지, 그리고 response로 돌려줄 때 어떤 내용을 채워서 줘야하는지 알아보자

  • Utilities

    Ring에서 여러가지 유용한 함수모음들을 제공해준다

    • Response 쉽게 만들기
  • Middlewares

    Ring에서 기본적으로 필요한 middlewares를 제공한다

    • 정적파일들을 serving하는 middleware
    • Content-Type을 추가해주는 middleware
    • Request로 들어온 파라미터 처리해주는 middleware
    • Cookies를 처리하는 middleware
    • Session 기능을 제공한다
    • 파일 uploading 기능
  • Ring’s Tool Chains

    • 파일변경되면 자동으로 서버를 restart하게 해주기
  • handler

    handler는 웹 request를 처리하는 녀석이다

    입력 -> clojure map을 받아서
    출력 -> clojure map을 돌려준다

  • middleware

    handler에다가 무슨 기능을 덧붙이고 싶을 때 사용한다.
    handler가 요청을 처리하기 전/후로 해서 추가로 기능을 처리할 수가 있다

    ring 코드의 많은 부분이 middleware가 차지한다.
    middleware가 없으면 web 개발을 하지 말라는 소리와 같다
    (왜냐하면 너무나 유용한 기능이 많이 있으니까)

  • adapter

    adapter는 http protocol을 알아서 처리해주는 역할을 한다.

    • 소켓으로 들어온 요청을 clojure map으로 변경해서 handler에게 전달
    • clojure handler가 돌려준 response를 다시 http 소켓으로 변경해서 요청한 클라이언트에게 전달

    기본적으로 ring에서는 jetty-adapter를 전달해준다

  • Request

    adapter가 처리해서 주는 request map에는 기본적으로 아래와 같은 항목들이 채워져 있다

    • :server-port - request가 처리되고 있는 port
    • :server-name - 서버 이름 또는 ip
    • :remote-addr - 요청을 마지막으로 보낸 녀석의 ip address
    • :uri - 요청된 URI, 앞에 host 이름을 빠져있다
    • :query-string - 있으면 query string이 보여진다
    • :scheme - 전송 프로토콜 ( :http 아니면 :https ) 둘중에 하나이다
    • :request-method - 요청 방법 (:get, :head, :options, :put, :post, :delete) 얘네들 중에 하나이다
    • :content-type - request body에 대한 mime type이다
    • :content-length - request body에 대한 길이
    • :character-encoding - request body에 사용된 character encoding (만약 사용되어져 있다면)
    • :headers - clojure map으로 되어있는 headers
    • :body - input stream이다
  • Response

    handler가 돌려주는 response는 항상 아래 3개의 항목을 담고 있어야한다

    • :status - HTTP 상태코드
    • :headers - clojure map으로 되어있는 헤더 정보, request 일 때와는 다르게 특별히 대소문자를 중간에서 처리하지 않는다. 흠. 정말????
    • :body - 응답 내용
  • Response 쉽게 만들기

    Ring response는 단순 map이기 때문에 쉽게 만들 수 있지만 utilities 함수를 이용해서 더 빠르게 만들 수 있다. Ring이 제공하는 response 관련 utility 함수들 목록은 아래와 같다

    • response
    • content-type
    • redirect
    • redirect-after-post
    • not-found
    • file-response
    • resource-response
    • status
    • header
    • charset
    • set-cookie
    • response?
  • Static Resources

    일반 파일들을 그대로 제공하는 기능은 두가지 middleware에 의해서 제공된다.

    • wrap-file
    • wrap-resource

    아래 함수는 파일을 serving할 때 추가로 유용한 기능을 제공한다

    • wrap-file-info
  • Content Types

    이 middleware를 사용하면 URI에 있는 파일의 확장자를 가지고 알맞은 mime type을 결정해준다. 추가로 custom mime type을 추가할 수가 있다.

    (use 'ring.middleware.content-type)
    
    (def app
        (wrap-content-type your-handler))
  • Parameter 처리

    웹브라우져에서 서버쪽으로 추가로 필요한 데이터들을 파라미터로 담아서 보내준다. ring.middleware.params/wrap-params는 이 파라미터들을 처리해서 clojure map으로 변경시켜준다.

    • URI에 보내온 파리머터들은 :query-params
    • BODY에 실려서 온 파라미터들은 :form-params
      *:params에는 2개가 merge된 값이 들어있다.
  • 쿠키는 wrap-cookie미들웨어를 사용한다
    :cookies라는 키를 추가한다.

    (use 'ring.middleware.cookies)
    (def app
        (wrap-cookies your-handler))
  • Session 처리

    세션은 wrap-session을 이용한다
    미들웨어 중에서 제일 복잡한 것 중의 하나이다.

    wrap-session은 두가지 일을 한다

    • 입력으로 들어온 map에다가 :session키를 추가하고 session data를 map형태로 제공한다
    • 핸들러가 돌려준 response map에다가 세션관련된 쿠키를 추가한다
  • File Uploading

    파일 올리는 기능은 wrap-multipart-params에서 담당을 한다. 기본적으로 올라온 파일은 임시 폴더에 저장이 되고 한시간 후에 삭제당한다

    (use 'ring.middleware.params
        'ring.middleware.multipart-params)
    
    (def app
        (-> your-handler
            wrap-params
            wrap-multipart-params))
  • 자동 서버 시작 기능

    세가지 방식으로 제작할 수가 잇다.

    1. Lein Ring 사용하기
    2. Ring Serve사용하기
    3. Manual update
  • handler example

     (defn what-is-my-ip [request]
          {:status 
           :headers {"Content-Type" "text/plain"}
           :body (:remote-addr request)})
  • :headers

    헤더는 clojure map으로 주어진다.
    그리고 키 값은 언제나 소문자로 된 string이 주어진다

  • Response’s Body

    http response에 담겨질 수 있는 4가지의 데이터 type이 있다

    • String - 문자열, 담으면 그대로 간다
    • ISeq - Sequence의 각 엘리먼트가 클라이언트에 전달된다. 음 아직 이부분이 명쾌하지 않다.
    • File - 참조되는 파일의 내용이 전달된다
    • InputStream - 입력 stream의 내용이 그대로 전송된다.
  • response

    기본적인 response map을 생성한다

    (response "Hello World")
    =>
    { :status 200
        :headers {}
        :body "Hello World"
    }
  • content-type

    response map에다가 주어진 content-type 헤더를 추가한다

    (-> 
        (response "Hello World")
        (content-type "text/plain"))
    =>
    { :status 200
     :headers { "Content-Type" "text/plain" }
    :body "Hello ,World"}
  • redirect

    Redirection을 시키는 response를 생성한다

    (redirect "http://www.google.com")
    =>
    {:status 302
     :headers {"Location" "http://www.google.com"}
     :body ""}
  • redirect-after-post

    303 Redirection코드를 갖는 map을 생성한다

    (redirect-after-post "http://www.google.com")
    =>
        {:status 303
        :headers {"Location" "http://www.google.com"}
        :body ""}
  • not-found

    not found 에러를 갖는 response를 보여준다

    (not-found "don_t know")
    =>
        {:status 404
        :headers {}
        :body "donr_knwo"]
  • file-response

    파일 시스템에 있는 파일을 돌려주는 response를 만든다

    (file-response "readme.html" {:root "public"})
    =>
        {:status 200
         :headers {}
        :body (io/file "public/readme.html")}
  • resource-response

    jar 파일안에 포함된 리소스 폴더 아래에 있는 파일 내용을 돌려준다.
    (resource-response “readme.html” {:root “public”})
    =>
    {:status 200
    :headers {}
    :body (io/input-stream (io/resource “public/readme.html”))}

    리소스 폴더는 clojure project 폴더 아래에 resource라는 폴더이름으로 존재한다.

  • status

    (-> (response "Hello, World")
        (status 201))
    =>
    {:status 201
     :headers {}
     :body "Hello, World"}
  • Response에 새로운 헤더 값을 붙여서 돌려준다

    (-> (response "Hello, World")
            (header "sample" "123"))
    =>
    {:status 200
     :headers {"sample" "123"}
     :body "Hello, World"}
  • charset

    Header에 있는 Content-Type에다가 charset 항목을 추가한다.
    ???

  • cookie를 설정한다고 하는데, 이거는 잘 모르겠다. 지금은.

  • response?

    주어진 map이 response용으로 작성된 map인지 여부를 체크한다

  • wrap-file

    wrap-file은 시스템 path에 있는 파일을 불러들여서 serving한다

    (use 'ring.middleware/file)
    (def app
        (wrap-file your-handler "/var/www/public"))

    /var/www/public에 요청된 파일이 있다면 그 파일을 먼저 serve한다. 파일이 발견되지 않으면 그 다음 핸들러에게 처리를 넘긴다.

  • wrap-resource

    wrap-resource는 jar파일에 같이 묶여있는 리소스 폴더 아래에 있는 내용을 가지고 serve한다.

    (use 'ring.middleware.resource)
    (def app
        (wrap-resource your-handler "public"))

    리소스 폴더 안에 public 폴더를 root로 해서 요청되는 파일을 serve한다

  • wrap-file-info

    이 함수는 파일에 대한 정보를 이용해서 마지막 수정된 날짜, 그리고 Content-Type을 업데이트해준다.

    대개 wrap-file / wrap-resource와 같이 쓰인다.

    (use 'ring.middleware.resource
        'ring.middleware.file-info)
    
    (def app
        (-> your_handler
            (wrap-resource "public")
            (wrap-file-info)))
  • wrap-params 예제

    (def app 
        (wrap-params your_handler))

    로 지정한다

    {:http-method get
     :uri "/search"
     :query-string "q=clojure"}

    이렇게 들어온 요청이 middleware을 아래와 같이 변경한다

    {:http-method get
     :uri "/search"
     :query-string "q=clojure"
     :query-params {"q" "clojure"}
     :form-params {}
     :params {"q" "clojure"}}
  • wrap-cookies

    쿠키 미들웨어를 추가하면

    {
     :status 200
     :headers {}
     :cookies {"username" {:value "alice}}
    :body ""}

    위의 처럼 cookie라는 항목을 추가한다
    Response 쉽게 만들기에서 보았던 ring.utilities.response/set-cookie 함수를 이용해서 쿠키값을 설정한다

    주의할 점은 헤더에 :cookies를 집어넣은 것이 아니다.
    response map에다가 집어넣은 것이다.

  • wrap-session

    세션값을 읽어오기

    (use 'ring.middleware.session
            'ring.util.response)
    
    (defn handler [{session :session}]
        (response (str "Hello " (:username session))))
    
    (def app
        (wrap-session handler))

    세션값을 업데이트하기

    (defn handler [{session :session}]
        (let [count  (:count session 0)
              session (assoc session :count 
                        (inc count))]
            (-> (response (str  count " times"))
                (assoc :session session))))

    세션을 완전히 제거하기

    response map에다가 :session 키에다가 nil값을 설정한다.

    (assoc session :session nil)

    세션의 속성을 설정하기

    세션 속성 설정은 :cookie-attrs 를 이용한다. :cookies-attrs를 키로하고 값은 clojure map을 갖는다

    (def app
        (wrap-session handler {:cookie-attrs {:max-age 3600 }}))

    wrap-session을 호출할 때 옵션항목을 붙여서 넘겨준다
    session cookies를 secure하게 넘기길 원할 때는 아래와 같이 옵션으로 :secure를 설정한다

    (def app
        (wrap-session handler {:cookie-attrs {:secure true}}))
  • 세션 저장장소

    세션값은 저장되어야한다
    ring에서 기본적으로 2가지 type의 세션 저장소를 제공한다

    • ring.middleware.session.memory/memory-store
    • ring.middleware.session.cookie/cookie-store

    memory store는 메모리에 세션을 저장한다. 또 다른 안은 쿠키 않에 encrypt된 형태로 세션을 저장한다.

    세션에 저장되는 기본 값은 메모리이다.

    하지만 저장소를 다른 것으로 변경할 수 있다.

    (use 'ring.middleware.session.cookie)
    (def app
        (wrap-session handler {:store (cookie-store {:key "a 16-byte secret"})}))
  • Custom Session Storage

    세션 저장소는를 따로 구현하려면 ring.middleware.session.store/SessionStore 프로토콜을 구현해야한다

    (use 'ring.middleware.session.store)
    
    (deftype CustomStore[]
        SessionStore
        (read-session [_ key]
            (read-data key))
        (write-session [_ key data]
            (let [key (or key (generate-new-random-key))]
                (save-data key data)
                key))
        (delete-session [_ key]
            (delete-data key)
            nil)

    write-session함수에서 새로운 세션의 경우 key값이 nil로 온다
    key값을 정교하게 만들어야지 공격을 당했을 때 비밀을 보장할 수가 있다

  • File Uploading Detail

    파일 업로딩을 하게 되면 request에 :multipart-params를 추가하게 되고 :params에 merge를 시킨다

    :mutipart-params에 는 아래와 같은 항목들이 추가된다

     :filename
     :content-type
     :tempfile
     :size

    복수의 파일이 올라오게 되면????
    어떻게 되는 건지는 모르겠당.
    아마도 :filename에 vector형식으로 모여지는 것으로 보인다.
    코드 상으로는…

  • Lein Ring 사용하기

    lein-ring은 leiningen의 플러그인이다.

    설정하기

    설정 방법은 아래와 같다.

    1. project.cli파일에 아래 내용을 추가한다.

       :plugins [[lein-ring "0.8.7"]]
    2. project.cli파일에 :ring이라는 키를 추가하고 거기에 몇가지 설정 관련된 옵션들을 제공한다.

    3. 설정 옵션들에서 필요한 것은 최상위 핸들러 함수를 지정하는 것임.

      :ring { :handler my-project.core/handler }

    서버 실행시키기

    아래와 같이 command line에서 실행하면 서버가 실행되고 웹브라우져를 실행시킨 다음 페이지를 하나 연다.

    lein ring server
  • Ring Serve 사용하기

    이 코드는 더 이상 유지보수가 되지 않는 것으로 보인다. 이거 project.clj에 집어넣으면 hiccup 옛날 버젼을 자꾸 찾는다. ring-devel? 버젼을 옛날 것을 사용한다. 현재 2년 전 소스를 고친것이 마지막으로 변경한 것이되버렸다

    Ring serve를 사용하는 이유는 IDE환경 내에서 서버를 띄우고 repl로 접속해서 이것저것 찔러보려고 할 때 사용한다. (Lein Ring으로 하게 되는 경우 repl을 찔러볼 수가 없다)

    ring-serve라이브러리를 사용해서 작업을 한다. project.clj에 아래 dependencies를 추가한다. 이 라이브러리는 development 단계에서 사용하게 되므로 아래와 같이 dev profile에 넣어준다. (leiningen 2.0이상에서 작동한다)

    :profiles { :dev {:dependencies [[ring-serve "0.1.2"]] } }

    dependency를 추가한 후에 lein deps를 실행해서 필요한 라이브러리를 다운로드받아두자.

    REPL을 사용해서 원하는 handler를 작동시킨다

    user> (require 'your-app.core/handler)
    nil
    user> (use 'ring.util.serve)
    nil
    user> (serve your-app.core/handler)
    Started web server on port 3000

    이제부터 파일을 변경한 후 다시 변경된 파일을 repl로 불러들이면 변경된 내용이 자동으로 적용된다.
    먼저번 lein ring이 더 실제적이다.

  • 수동으로 업데이트하기

    수동으로 하려면 아래와 같은 요건을 만족시켜야한다.

    • Ring adapter가 background thread에서 돌려야한다. agent나 future를 이용하던지. 안그러면 REPL이 먹통이 되어 버린다. 이것은 원하는 바가 아니잖아.
    • Handler함수를 var로 전달시켜서 다시 업데이트가 되어도 참조해 갈 수 있게 만들어라.

    그러니까 아래처럼 하란 말이다.

    user> (defonce server (run-jetty #'handler {:port 8080 :join? false}))

    #'handler가 포인트이다.

{"cards":[{"_id":"35a73663f6dc6e5ed600001a","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":null,"content":"# Clojure Ring #\nRing을 공부해보자\n\n여기 트리에서는 아래 순서로 정리한다. \n(주요 내용은 모두 다 https://github.com/ring-clojure/ring/wiki - Ring의 위키 페이지에서 가져왔다 )\n\n* 기본 개념\n* Utility 함수들\n* Middlewares\n* Ring의 Toolchains\n\n"},{"_id":"35a73bb1f6dc6e5ed6000033","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":null,"content":"## 기본 개념 ##\n거기에는 세가지 개념이 있다\n* handler\n* middleware\n* adapter\n"},{"_id":"35a73cc7f6dc6e5ed6000034","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a73bb1f6dc6e5ed6000033","content":"## handler ##\nhandler는 웹 request를 처리하는 녀석이다\n\n입력 `->` clojure map을 받아서\n출력 `->` clojure map을 돌려준다"},{"_id":"35a7ba97f6dc6e5ed600003c","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a73cc7f6dc6e5ed6000034","content":"## handler example ##\n (defn what-is-my-ip [request]\n {:status \n :headers {\"Content-Type\" \"text/plain\"}\n :body (:remote-addr request)})"},{"_id":"35a740ddf6dc6e5ed6000035","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35a73bb1f6dc6e5ed6000033","content":"## middleware ##\nhandler에다가 무슨 기능을 덧붙이고 싶을 때 사용한다. \nhandler가 요청을 처리하기 전/후로 해서 추가로 기능을 처리할 수가 있다\n\nring 코드의 많은 부분이 middleware가 차지한다. \nmiddleware가 없으면 web 개발을 하지 말라는 소리와 같다\n(왜냐하면 너무나 유용한 기능이 많이 있으니까)"},{"_id":"35a74622f6dc6e5ed6000036","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35a73bb1f6dc6e5ed6000033","content":"## adapter ##\nadapter는 http protocol을 알아서 처리해주는 역할을 한다.\n* 소켓으로 들어온 요청을 clojure map으로 변경해서 handler에게 전달\n* clojure handler가 돌려준 response를 다시 http 소켓으로 변경해서 요청한 클라이언트에게 전달\n\n기본적으로 ring에서는 jetty-adapter를 전달해준다"},{"_id":"35a74a81f6dc6e5ed6000037","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":null,"content":"## 기본 중의 기본 - Request / Response ##\nRing에서 http request는 clojure map으로, http response또한 clojure map으로 표현이된다.\n\n여기 기본중의 기본으로 request에 어떤 내용이 실려서 전달되는지, 그리고 response로 돌려줄 때 어떤 내용을 채워서 줘야하는지 알아보자"},{"_id":"35a74f56f6dc6e5ed6000038","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a74a81f6dc6e5ed6000037","content":"## Request ##\nadapter가 처리해서 주는 request map에는 기본적으로 아래와 같은 항목들이 채워져 있다\n* `:server-port` - request가 처리되고 있는 port\n* `:server-name` - 서버 이름 또는 ip\n* `:remote-addr` - 요청을 마지막으로 보낸 녀석의 ip address\n* `:uri` - 요청된 URI, *앞에 host 이름을 빠져*있다\n* `:query-string` - 있으면 query string이 보여진다\n* `:scheme` - 전송 프로토콜 ( `:http` 아니면 `:https` ) 둘중에 하나이다\n* `:request-method` - 요청 방법 (`:get`, `:head`, `:options`, `:put`, `:post`, `:delete`) 얘네들 중에 하나이다\n* `:content-type` - request body에 대한 mime type이다\n* `:content-length` - request body에 대한 길이\n* `:character-encoding` - request body에 사용된 character encoding (만약 사용되어져 있다면)\n* `:headers` - clojure map으로 되어있는 headers\n* `:body` - input stream이다"},{"_id":"35a7ab8df6dc6e5ed6000039","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a74f56f6dc6e5ed6000038","content":"## :headers ##\n헤더는 clojure map으로 주어진다.\n그리고 키 값은 언제나 **소문자**로 된 string이 주어진다\n"},{"_id":"35a7ad31f6dc6e5ed600003a","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35a74a81f6dc6e5ed6000037","content":"## Response ##\nhandler가 돌려주는 response는 항상 아래 3개의 항목을 담고 있어야한다\n* `:status` - HTTP 상태코드\n* `:headers` - clojure map으로 되어있는 헤더 정보, ** request **일 때와는 다르게 특별히 대소문자를 중간에서 처리하지 않는다. 흠. 정말????\n* `:body` - 응답 내용"},{"_id":"35a7b587f6dc6e5ed600003b","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a7ad31f6dc6e5ed600003a","content":"## Response's Body ##\nhttp response에 담겨질 수 있는 4가지의 데이터 type이 있다\n* `String` - 문자열, 담으면 그대로 간다\n* `ISeq` - Sequence의 각 엘리먼트가 클라이언트에 전달된다. 음 아직 이부분이 명쾌하지 않다.\n* `File` - 참조되는 파일의 내용이 전달된다\n* `InputStream` - 입력 stream의 내용이 그대로 전송된다."},{"_id":"35a7bf87f6dc6e5ed600003d","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":4,"parentId":null,"content":"# Utilities #\nRing에서 여러가지 유용한 함수모음들을 제공해준다\n* Response 쉽게 만들기"},{"_id":"35a7c1d0f6dc6e5ed600003e","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a7bf87f6dc6e5ed600003d","content":"## Response 쉽게 만들기 ##\nRing response는 단순 map이기 때문에 쉽게 만들 수 있지만 utilities 함수를 이용해서 더 빠르게 만들 수 있다. Ring이 제공하는 response 관련 utility 함수들 목록은 아래와 같다\n* response\n* content-type\n* redirect\n* redirect-after-post\n* not-found\n* file-response\n* resource-response\n* status\n* header\n* charset\n* set-cookie\n* response?"},{"_id":"35cc2c285ec35c9384000013","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## response ##\n기본적인 response map을 생성한다\n\n (response \"Hello World\")\n =>\n { :status 200\n :headers {}\n :body \"Hello World\"\n }"},{"_id":"35cc2f435ec35c9384000014","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## content-type ##\nresponse map에다가 주어진 content-type 헤더를 추가한다\n\n (-> \n (response \"Hello World\")\n (content-type \"text/plain\"))\n =>\n { :status 200\n :headers { \"Content-Type\" \"text/plain\" }\n :body \"Hello ,World\"}\n"},{"_id":"35cc36a75ec35c9384000015","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## redirect ##\n\nRedirection을 시키는 response를 생성한다\n\n (redirect \"http://www.google.com\")\n =>\n {:status 302\n :headers {\"Location\" \"http://www.google.com\"}\n :body \"\"}\n\n\n"},{"_id":"35cc3bd95ec35c9384000016","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":4,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## redirect-after-post\n\n303 Redirection코드를 갖는 map을 생성한다\n\n (redirect-after-post \"http://www.google.com\")\n =>\n {:status 303\n :headers {\"Location\" \"http://www.google.com\"}\n :body \"\"}\n"},{"_id":"35cc400e5ec35c9384000017","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":5,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## not-found\n\nnot found 에러를 갖는 response를 보여준다\n\n (not-found \"don_t know\")\n =>\n {:status 404\n :headers {}\n :body \"donr_knwo\"]\n"},{"_id":"35cc98b55ec35c9384000018","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":6,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## file-response\n\n파일 시스템에 있는 파일을 돌려주는 response를 만든다\n\n (file-response \"readme.html\" {:root \"public\"})\n =>\n {:status 200\n :headers {}\n :body (io/file \"public/readme.html\")}\n"},{"_id":"35cc9bae5ec35c9384000019","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":7,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## resource-response\n\njar 파일안에 포함된 리소스 폴더 아래에 있는 파일 내용을 돌려준다.\n (resource-response \"readme.html\" {:root \"public\"})\n =>\n {:status 200\n :headers {}\n :body (io/input-stream (io/resource \"public/readme.html\"))}\n\n리소스 폴더는 clojure project 폴더 아래에 resource라는 폴더이름으로 존재한다. \n"},{"_id":"35cca0215ec35c938400001a","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":8,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## status\n\n (-> (response \"Hello, World\")\n (status 201))\n =>\n {:status 201\n :headers {}\n :body \"Hello, World\"}\n"},{"_id":"35cca33d5ec35c938400001b","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":9,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## header\nResponse에 새로운 헤더 값을 붙여서 돌려준다\n\n (-> (response \"Hello, World\")\n (header \"sample\" \"123\"))\n =>\n {:status 200\n :headers {\"sample\" \"123\"}\n :body \"Hello, World\"}\n"},{"_id":"35cca8b55ec35c938400001c","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":10,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## charset\nHeader에 있는 Content-Type에다가 charset 항목을 추가한다.\n???\n"},{"_id":"35ccaa755ec35c938400001d","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":11,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## set-cookie\n\ncookie를 설정한다고 하는데, 이거는 잘 모르겠다. 지금은.\n"},{"_id":"35ccabf15ec35c938400001e","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":12,"parentId":"35a7c1d0f6dc6e5ed600003e","content":"## response?\n주어진 map이 response용으로 작성된 map인지 여부를 체크한다"},{"_id":"35ccadc45ec35c938400001f","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":5,"parentId":null,"content":"# Middlewares\nRing에서 기본적으로 필요한 middlewares를 제공한다\n\n* 정적파일들을 serving하는 middleware\n* Content-Type을 추가해주는 middleware\n* Request로 들어온 파라미터 처리해주는 middleware\n* Cookies를 처리하는 middleware\n* Session 기능을 제공한다\n* 파일 uploading 기능"},{"_id":"35ccaedf5ec35c9384000020","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35ccadc45ec35c938400001f","content":"## Static Resources\n\n일반 파일들을 그대로 제공하는 기능은 두가지 middleware에 의해서 제공된다.\n* wrap-file\n* wrap-resource\n\n아래 함수는 파일을 serving할 때 추가로 유용한 기능을 제공한다\n* wrap-file-info\n"},{"_id":"35ccb1ce5ec35c9384000021","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35ccaedf5ec35c9384000020","content":"## wrap-file\n\nwrap-file은 시스템 path에 있는 파일을 불러들여서 serving한다\n\n (use 'ring.middleware/file)\n (def app\n (wrap-file your-handler \"/var/www/public\"))\n\n/var/www/public에 요청된 파일이 있다면 그 파일을 먼저 serve한다. 파일이 발견되지 않으면 그 다음 핸들러에게 처리를 넘긴다. \n"},{"_id":"35ccb72c5ec35c9384000022","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35ccaedf5ec35c9384000020","content":"## wrap-resource\nwrap-resource는 jar파일에 같이 묶여있는 리소스 폴더 아래에 있는 내용을 가지고 serve한다. \n\n (use 'ring.middleware.resource)\n (def app\n (wrap-resource your-handler \"public\"))\n\n리소스 폴더 안에 public 폴더를 root로 해서 요청되는 파일을 serve한다"},{"_id":"35ccbbf85ec35c9384000023","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35ccaedf5ec35c9384000020","content":"## wrap-file-info\n이 함수는 파일에 대한 정보를 이용해서 마지막 수정된 날짜, 그리고 Content-Type을 업데이트해준다.\n\n대개 wrap-file / wrap-resource와 같이 쓰인다.\n\n (use 'ring.middleware.resource\n 'ring.middleware.file-info)\n \n (def app\n (-> your_handler\n (wrap-resource \"public\")\n (wrap-file-info)))\n\n"},{"_id":"35ccc2545ec35c9384000024","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35ccadc45ec35c938400001f","content":"## Content Types\n\n이 middleware를 사용하면 URI에 있는 파일의 확장자를 가지고 알맞은 mime type을 결정해준다. 추가로 custom mime type을 추가할 수가 있다.\n\n (use 'ring.middleware.content-type)\n \n (def app\n (wrap-content-type your-handler))\n\n"},{"_id":"35ccdbcf5ec35c9384000025","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35ccadc45ec35c938400001f","content":"## Parameter 처리\n\n웹브라우져에서 서버쪽으로 추가로 필요한 데이터들을 파라미터로 담아서 보내준다. ring.middleware.params/wrap-params는 이 파라미터들을 처리해서 clojure map으로 변경시켜준다.\n\n* URI에 보내온 파리머터들은 `:query-params`로\n* BODY에 실려서 온 파라미터들은 `:form-params`로\n*` :params`에는 2개가 merge된 값이 들어있다.\n\n"},{"_id":"35ccecfe5ec35c9384000026","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35ccdbcf5ec35c9384000025","content":"## wrap-params 예제\n\n (def app \n (wrap-params your_handler))\n로 지정한다\n\n {:http-method get\n :uri \"/search\"\n :query-string \"q=clojure\"}\n\n이렇게 들어온 요청이 middleware을 아래와 같이 변경한다\n\n {:http-method get\n :uri \"/search\"\n :query-string \"q=clojure\"\n :query-params {\"q\" \"clojure\"}\n :form-params {}\n :params {\"q\" \"clojure\"}}\n"},{"_id":"35ccfbea5ec35c9384000027","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":4,"parentId":"35ccadc45ec35c938400001f","content":"## Cookie 처리\n\n쿠키는 `wrap-cookie `미들웨어를 사용한다\n:cookies라는 키를 추가한다.\n\n (use 'ring.middleware.cookies)\n (def app\n (wrap-cookies your-handler))\n\n\n \n"},{"_id":"35cd02035ec35c9384000028","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35ccfbea5ec35c9384000027","content":"## wrap-cookies\n\n쿠키 미들웨어를 추가하면 \n\n {\n :status 200\n :headers {}\n :cookies {\"username\" {:value \"alice}}\n :body \"\"}\n\n위의 처럼 cookie라는 항목을 추가한다\nResponse 쉽게 만들기에서 보았던 ring.utilities.response/set-cookie 함수를 이용해서 쿠키값을 설정한다\n\n주의할 점은 헤더에 :cookies를 집어넣은 것이 아니다.\nresponse map에다가 집어넣은 것이다.\n\n "},{"_id":"35da2de2070c6f967b000029","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":5,"parentId":"35ccadc45ec35c938400001f","content":"## Session 처리\n세션은 `wrap-session`을 이용한다\n미들웨어 중에서 제일 복잡한 것 중의 하나이다.\n\n`wrap-session`은 두가지 일을 한다\n* 입력으로 들어온 map에다가 :session키를 추가하고 session data를 map형태로 제공한다\n* 핸들러가 돌려준 response map에다가 세션관련된 쿠키를 추가한다\n\n"},{"_id":"35da33f1070c6f967b00002a","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35da2de2070c6f967b000029","content":"## wrap-session\n\n### 세션값을 읽어오기\n\n (use 'ring.middleware.session\n 'ring.util.response)\n\n (defn handler [{session :session}]\n (response (str \"Hello \" (:username session))))\n\n (def app\n (wrap-session handler))\n\n### 세션값을 업데이트하기\n\n (defn handler [{session :session}]\n (let [count (:count session 0)\n session (assoc session :count \n (inc count))]\n (-> (response (str count \" times\"))\n (assoc :session session))))\n\n### 세션을 완전히 제거하기\nresponse map에다가 :session 키에다가 nil값을 설정한다.\n\n (assoc session :session nil)\n\n### 세션의 속성을 설정하기\n세션 속성 설정은 `:cookie-attrs` 를 이용한다. `:cookies-attrs`를 키로하고 값은 clojure map을 갖는다\n\n (def app\n (wrap-session handler {:cookie-attrs {:max-age 3600 }}))\n\n`wrap-session`을 호출할 때 옵션항목을 붙여서 넘겨준다\nsession cookies를 secure하게 넘기길 원할 때는 아래와 같이 옵션으로 :secure를 설정한다\n\n (def app\n (wrap-session handler {:cookie-attrs {:secure true}}))\n\n\n\n"},{"_id":"35dbca96070c6f967b00002b","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35da2de2070c6f967b000029","content":"## 세션 저장장소\n세션값은 저장되어야한다\nring에서 기본적으로 2가지 type의 세션 저장소를 제공한다\n* ring.middleware.session.memory/memory-store\n* ring.middleware.session.cookie/cookie-store\n\nmemory store는 메모리에 세션을 저장한다. 또 다른 안은 쿠키 않에 encrypt된 형태로 세션을 저장한다. \n\n세션에 저장되는 기본 값은 메모리이다. \n\n하지만 저장소를 다른 것으로 변경할 수 있다.\n\n (use 'ring.middleware.session.cookie)\n (def app\n (wrap-session handler {:store (cookie-store {:key \"a 16-byte secret\"})}))\n\n\n"},{"_id":"35f6fceb74ed040df50000d0","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35da2de2070c6f967b000029","content":"## Custom Session Storage\n\n세션 저장소는를 따로 구현하려면 `ring.middleware.session.store/SessionStore` 프로토콜을 구현해야한다\n\n (use 'ring.middleware.session.store)\n \n (deftype CustomStore[]\n SessionStore\n (read-session [_ key]\n (read-data key))\n (write-session [_ key data]\n (let [key (or key (generate-new-random-key))]\n (save-data key data)\n key))\n (delete-session [_ key]\n (delete-data key)\n nil)\n\n`write-session`함수에서 새로운 세션의 경우 key값이 nil로 온다\nkey값을 정교하게 만들어야지 공격을 당했을 때 비밀을 보장할 수가 있다\n\n"},{"_id":"35f70a1574ed040df50000d1","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":6,"parentId":"35ccadc45ec35c938400001f","content":"## File Uploading\n\n파일 올리는 기능은 `wrap-multipart-params`에서 담당을 한다. 기본적으로 올라온 파일은 임시 폴더에 저장이 되고 한시간 후에 삭제당한다\n\n (use 'ring.middleware.params\n 'ring.middleware.multipart-params)\n \n (def app\n (-> your-handler\n wrap-params\n wrap-multipart-params))\n\n"},{"_id":"35f7c3594af377d13700002e","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35f70a1574ed040df50000d1","content":"## File Uploading Detail\n\n파일 업로딩을 하게 되면 request에 `:multipart-params`를 추가하게 되고 `:params`에 merge를 시킨다\n\n`:mutipart-params`에 는 아래와 같은 항목들이 추가된다\n\n :filename\n :content-type\n :tempfile\n :size\n\n\n복수의 파일이 올라오게 되면???? \n어떻게 되는 건지는 모르겠당.\n아마도 :filename에 vector형식으로 모여지는 것으로 보인다.\n코드 상으로는...\n\n"},{"_id":"35f7d8cc4af377d13700002f","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":6,"parentId":null,"content":"# Ring's Tool Chains\n\n* 파일변경되면 자동으로 서버를 restart하게 해주기\n"},{"_id":"35f7da5b4af377d137000030","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35f7d8cc4af377d13700002f","content":"## 자동 서버 시작 기능\n\n세가지 방식으로 제작할 수가 잇다.\n\n1. Lein Ring 사용하기\n2. Ring Serve사용하기\n3. Manual update\n\n\n"},{"_id":"35f7dca44af377d137000031","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":1,"parentId":"35f7da5b4af377d137000030","content":"## Lein Ring 사용하기\n\nlein-ring은 leiningen의 플러그인이다. \n\n### 설정하기\n\n설정 방법은 아래와 같다.\n\n1. project.cli파일에 아래 내용을 추가한다.\n\n :plugins [[lein-ring \"0.8.7\"]]\n\n2. project.cli파일에 `:ring`이라는 키를 추가하고 거기에 몇가지 설정 관련된 옵션들을 제공한다.\n3. 설정 옵션들에서 필요한 것은 최상위 핸들러 함수를 지정하는 것임.\n\n :ring { :handler my-project.core/handler }\n\n\n### 서버 실행시키기\n아래와 같이 command line에서 실행하면 서버가 실행되고 웹브라우져를 실행시킨 다음 페이지를 하나 연다.\n\n lein ring server\n\n"},{"_id":"3601c41e4af377d137000032","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":2,"parentId":"35f7da5b4af377d137000030","content":"## Ring Serve 사용하기\n\n**이 코드는 더 이상 유지보수가 되지 않는 것으로 보인다. 이거 project.clj에 집어넣으면 hiccup 옛날 버젼을 자꾸 찾는다. ring-devel? 버젼을 옛날 것을 사용한다. 현재 2년 전 소스를 고친것이 마지막으로 변경한 것이되버렸다**\n\nRing serve를 사용하는 이유는 IDE환경 내에서 서버를 띄우고 repl로 접속해서 이것저것 찔러보려고 할 때 사용한다. *(Lein Ring으로 하게 되는 경우 repl을 찔러볼 수가 없다)*\n\n`ring-serve `라이브러리를 사용해서 작업을 한다. project.clj에 아래 dependencies를 추가한다. 이 라이브러리는 development 단계에서 사용하게 되므로 아래와 같이 dev profile에 넣어준다. (*leiningen 2.0이상에서 작동한다*)\n\n :profiles { :dev {:dependencies [[ring-serve \"0.1.2\"]] } }\n\ndependency를 추가한 후에 `lein deps`를 실행해서 필요한 라이브러리를 다운로드받아두자.\n\nREPL을 사용해서 원하는 handler를 작동시킨다\n\n user> (require 'your-app.core/handler)\n nil\n user> (use 'ring.util.serve)\n nil\n user> (serve your-app.core/handler)\n Started web server on port 3000\n\n이제부터 파일을 변경한 후 다시 변경된 파일을 repl로 불러들이면 변경된 내용이 자동으로 적용된다.\n먼저번 lein ring이 더 실제적이다.\n"},{"_id":"36032bf54af377d137000033","treeId":"35a73651f6dc6e5ed6000017","seq":1,"position":3,"parentId":"35f7da5b4af377d137000030","content":"## 수동으로 업데이트하기\n\n수동으로 하려면 아래와 같은 요건을 만족시켜야한다.\n\n* Ring adapter가 background thread에서 돌려야한다. agent나 future를 이용하던지. 안그러면 REPL이 먹통이 되어 버린다. 이것은 원하는 바가 아니잖아.\n* Handler함수를 var로 전달시켜서 다시 업데이트가 되어도 참조해 갈 수 있게 만들어라.\n\n그러니까 아래처럼 하란 말이다.\n\n user> (defonce server (run-jetty #'handler {:port 8080 :join? false}))\n\n`#'handler`가 포인트이다.\n\n"}],"tree":{"_id":"35a73651f6dc6e5ed6000017","name":"Ring Clojure","publicUrl":"ring-clojure"}}