Compojure 설명 (어느 정도)
NB. 저는 Compojure 0.4.1로 작업하고 있습니다 ( 여기 GitHub의 0.4.1 릴리스 커밋입니다).
왜?
의 맨 위에 compojure/core.clj
Compojure의 목적에 대한 유용한 요약이 있습니다.
링 핸들러 생성을위한 간결한 구문.
피상적 인 수준에서 "왜"질문에 대한 모든 것입니다. 좀 더 자세히 알아보기 위해 링 스타일 앱의 작동 방식을 살펴 보겠습니다.
요청이 도착하고 링 사양에 따라 Clojure 맵으로 변환됩니다.
이 맵은 응답을 생성 할 것으로 예상되는 소위 "핸들러 함수"(Clojure 맵이기도 함)로 유입됩니다.
응답 맵은 실제 HTTP 응답으로 변환되어 클라이언트로 다시 전송됩니다.
위의 2 단계는 요청에 사용 된 URI를 검사하고, 쿠키 등을 검사하고, 궁극적으로 적절한 응답에 도달하는 것은 핸들러의 책임이므로 가장 흥미 롭습니다. 분명히이 모든 작업은 잘 정의 된 조각 모음에 포함되어야합니다. 이들은 일반적으로 "기본"핸들러 함수이며이를 래핑하는 미들웨어 함수 모음입니다. Compojure의 목적은 기본 핸들러 함수의 생성을 단순화하는 것입니다.
어떻게?
Compojure는 "루트"라는 개념을 중심으로 구축되었습니다. 이들은 실제로 Clout 라이브러리 (Compojure 프로젝트의 스핀 오프-0.3.x-> 0.4.x 전환에서 별도의 라이브러리로 이동 됨)에 의해 더 깊은 수준에서 구현됩니다 . 경로는 (1) HTTP 메소드 (GET, PUT, HEAD ...), (2) URI 패턴 (Webby Rubyists에게 친숙한 구문으로 지정됨), (3) 요청 맵의 일부를 본문에서 사용 가능한 이름에 바인딩합니다. (4) 유효한 링 응답을 생성해야하는 표현식 본문 (사소하지 않은 경우 일반적으로 별도의 함수에 대한 호출).
간단한 예를 살펴보면 좋은 점이 될 수 있습니다.
(def example-route (GET "/" [] "<html>...</html>"))
REPL에서이를 테스트 해 보겠습니다 (아래 요청 맵은 최소 유효 링 요청 맵입니다).
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
경우 :request-method
였다 :head
대신, 응답이 될 것이다 nil
. 우리는 nil
잠시 후에 여기서 무슨 의미 인지에 대한 질문으로 돌아갈 것입니다 (그러나 유효한 Ring respose가 아닙니다!).
이 예제에서 알 수 있듯이은 example-route
함수일 뿐이며 매우 간단한 것입니다. 요청을보고 처리에 관심이 있는지 ( :request-method
및 검사 를 통해 :uri
) 결정하고, 그렇다면 기본 응답 맵을 반환합니다.
또한 분명한 것은 경로의 몸체가 실제로 적절한 응답 맵을 평가할 필요가 없다는 것입니다. Compojure는 문자열 (위에서 볼 수 있음) 및 기타 여러 객체 유형에 대한 정상적인 기본 처리를 제공합니다. 자세한 내용은 compojure.response/render
multimethod를 참조하십시오 (코드는 여기에서 완전히 자체 문서화 됨).
defroutes
지금 사용해 봅시다 :
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
위에 표시된 예제 요청과 해당 변형에 대한 응답 :request-method :head
은 예상과 같습니다.
의 내부 작업은 example-routes
각 경로가 차례로 시도됩니다. 그들 중 하나가 비 nil
응답을 반환하자마자 그 응답은 전체 example-routes
핸들러 의 반환 값이됩니다 . 추가 편의를, defroutes
-defined 핸들러에 싸여 wrap-params
와 wrap-cookies
암시.
다음은 더 복잡한 경로의 예입니다.
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
이전에 사용 된 빈 벡터 대신 비 구조화 형식에 유의하십시오. 여기서 기본 아이디어는 경로 본문이 요청에 대한 정보에 관심이있을 수 있다는 것입니다. 이것은 항상지도의 형태로 도착하기 때문에 요청에서 정보를 추출하고 경로 본문의 범위에있는 지역 변수에 바인딩하기 위해 연관 분해 양식을 제공 할 수 있습니다.
위의 테스트 :
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
위의 훌륭한 후속 아이디어는 더 복잡한 경로가 assoc
일치 단계에서 요청에 추가 정보를 제공 할 수 있다는 것입니다 .
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
이것은 이전 예제의 요청에 :body
of "foo"
로 응답합니다 .
이 최신 예제에서 두 가지 새로운 점이 있습니다 : "/:fst/*"
비어 있지 않은 바인딩 벡터 [fst]
. 첫 번째는 앞서 언급 한 URI 패턴에 대한 Rails 및 Sinatra와 유사한 구문입니다. URI 세그먼트에 대한 정규식 제약이 지원된다는 점에서 위의 예에서 명백한 것보다 약간 더 정교합니다 (예 : ["/:fst/*" :fst #"[0-9]+"]
경로 :fst
가 위 의 모든 숫자 값만 허용하도록 제공 할 수 있음 ). 두 번째는 :params
그 자체가 맵인 요청 맵 의 항목에서 일치하는 단순화 된 방법입니다 . 요청, 쿼리 문자열 매개 변수 및 양식 매개 변수에서 URI 세그먼트를 추출하는 데 유용합니다. 후자의 요점을 설명하는 예 :
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
질문 텍스트의 예를 살펴보기에 좋은 시간입니다.
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
각 경로를 차례로 분석해 보겠습니다.
(GET "/" [] (workbench))
-를 사용하여 GET
요청을 처리 할 때 :uri "/"
함수를 호출하고 workbench
반환되는 내용을 응답 맵으로 렌더링합니다. (반환 값은 맵일 수도 있지만 문자열 등일 수도 있습니다.)
(POST "/save" {form-params :form-params} (str form-params))
- 미들웨어에서 :form-params
제공하는 요청 맵의 항목입니다 (에서 wrap-params
암시 적으로 포함됨 defroutes
). 응답은으로 대체 되는 표준 {:status 200 :headers {"Content-Type" "text/html"} :body ...}
이 (str form-params)
됩니다 ...
. (조금 특이한 POST
핸들러, 이거 ...)
(GET "/test" [& more] (str "<pre> more "</pre>"))
-예를 들어 {"foo" "1"}
사용자 에이전트가 요청한 경우 맵의 문자열 표현을 에코 백합니다 "/test?foo=1"
.
(GET ["/:filename" :filename #".*"] [filename] ...)
- :filename #".*"
부품이 전혀 작동하지 않습니다 ( #".*"
항상 일치하기 때문에 ). 링 유틸리티 함수 ring.util.response/file-response
를 호출하여 응답을 생성합니다. {:root "./static"}
부분은 어디 파일을 찾을 방법을 알려줍니다.
(ANY "*" [] ...)
-포괄 경로. defroutes
정의되는 핸들러가 항상 유효한 링 응답 맵을 반환하도록하려면 양식 끝에 이러한 경로를 항상 포함하는 것이 좋습니다 (경로 일치 실패로 인해 nil
).
왜 이렇게?
Ring 미들웨어의 한 가지 목적은 요청 맵에 정보를 추가하는 것입니다. 따라서 쿠키 처리 미들웨어는 :cookies
요청에 키를 wrap-params
추가 :query-params
하고 추가 및 / 또는:form-params
쿼리 문자열 / 양식 데이터가있는 경우 등등. (엄밀히 말하면 미들웨어 함수가 추가하는 모든 정보는 전달 된 것이기 때문에 요청 맵에 이미 존재해야합니다. 그들의 임무는 래핑하는 핸들러에서 작업하는 것이 더 편리하도록이를 변환하는 것입니다.) 궁극적으로 "풍부한"요청은 미들웨어에 의해 추가 된 훌륭하게 전처리 된 모든 정보로 요청 맵을 검사하고 응답을 생성하는 기본 핸들러로 전달됩니다. (미들웨어는 여러 "내부"핸들러를 래핑하고 그 중에서 선택하고 래핑 된 핸들러를 호출할지 여부를 결정하는 등 그보다 더 복잡한 작업을 수행 할 수 있습니다. 그러나 이는이 답변의 범위를 벗어납니다.)
차례로 기본 핸들러는 일반적으로 (중요하지 않은 경우) 요청에 대한 몇 가지 정보 항목 만 필요한 경향이있는 함수입니다. (예를 들어 ring.util.response/file-response
대부분의 요청은 신경 쓰지 않고 파일 이름 만 필요합니다.) 따라서 Ring 요청의 관련 부분 만 추출하는 간단한 방법이 필요합니다. Compojure는 그 자체로 특별한 목적의 패턴 매칭 엔진을 제공하는 것을 목표로합니다.