From 0b8e6da4e372ec97563a8a6b8da9f32f9cc9f2ad Mon Sep 17 00:00:00 2001 From: Philipp Meier Date: Sun, 30 Aug 2015 21:46:28 +0200 Subject: [PATCH 1/5] Get rid of accept-...-exists? decision points. The decision points that decide on the existence of the Accept-... headers have been there just because of technical reasons and did not had any practical application. In fact they cluterred the decision graph and added noise. --- CHANGES.markdown | 5 ++ src/liberator/conneg.clj | 2 +- src/liberator/core.clj | 111 +++++++++++++++++--------------------- test/test_defresource.clj | 34 ++++++------ 4 files changed, 70 insertions(+), 82 deletions(-) diff --git a/CHANGES.markdown b/CHANGES.markdown index 625ce21..fe6dfec 100644 --- a/CHANGES.markdown +++ b/CHANGES.markdown @@ -2,6 +2,11 @@ # Unreleased +* Refactoring of content negotiation, get rid of internal decision points + :accept-exists?, :accept-language-exists?, :accept-encoding-exists?, + :accept-charset-exists? Empty Accept-* headers are treatened as + absent. + # New in 0.14.1 * Improved highlighting of tracing view diff --git a/src/liberator/conneg.clj b/src/liberator/conneg.clj index b193ff0..7b55687 100644 --- a/src/liberator/conneg.clj +++ b/src/liberator/conneg.clj @@ -134,7 +134,7 @@ x))) (defn stringify [type] - (reduce str (interpose "/" type))) + (string/join "/" type)) (defn best-allowed-content-type "Return the first type in the Accept header that is acceptable. diff --git a/src/liberator/core.clj b/src/liberator/core.clj index d355b7b..b729498 100644 --- a/src/liberator/core.clj +++ b/src/liberator/core.clj @@ -49,6 +49,7 @@ (defn map-values [f m] (persistent! (reduce-kv (fn [out-m k v] (assoc! out-m k (f v))) (transient {}) m))) + (defn request-method-in [& methods] #(some #{(:request-method (:request %))} methods)) @@ -387,79 +388,65 @@ (defhandler handle-not-acceptable 406 "No acceptable resource available.") -(defdecision encoding-available? - (fn [ctx] - (when-let [encoding (conneg/best-allowed-encoding - (get-in ctx [:request :headers "accept-encoding"]) - ((get-in ctx [:resource :available-encodings]) ctx))] - {:representation {:encoding encoding}})) - - processable? handle-not-acceptable) - (defmacro try-header [header & body] `(try ~@body (catch ProtocolException e# (throw (ProtocolException. (format "Malformed %s header" ~header) e#))))) -(defdecision accept-encoding-exists? (partial header-exists? "accept-encoding") - encoding-available? processable?) - -(defdecision charset-available? - #(try-header "Accept-Charset" - (when-let [charset (conneg/best-allowed-charset - (get-in % [:request :headers "accept-charset"]) - ((get-in context [:resource :available-charsets]) context))] - (if (= charset "*") - true - {:representation {:charset charset}}))) - accept-encoding-exists? handle-not-acceptable) - -(defdecision accept-charset-exists? (partial header-exists? "accept-charset") - charset-available? accept-encoding-exists?) - - -(defdecision language-available? - #(try-header "Accept-Language" - (when-let [lang (conneg/best-allowed-language - (get-in % [:request :headers "accept-language"]) - ((get-in context [:resource :available-languages]) context))] - (if (= lang "*") - true - {:representation {:language lang}}))) - accept-charset-exists? handle-not-acceptable) - -(defdecision accept-language-exists? (partial header-exists? "accept-language") - language-available? accept-charset-exists?) - -(defn negotiate-media-type [context] +(defn- negotiate-encoding [{:keys [request resource] :as context}] + (try-header "Accept-Encoding" + (let [accept (get-in request [:headers "accept-encoding"])] + (or (empty? accept) + (when-let [encoding (conneg/best-allowed-encoding + accept + ((:available-encodings resource) context))] + {:representation {:encoding encoding}})))) ) + +(defdecision encoding-available? negotiate-encoding + processable? handle-not-acceptable) + +(defn- negotiate-charset [{:keys [request resource] :as context}] + (try-header "Accept-Charset" + (let [accept (get-in request [:headers "accept-charset"])] + (or (empty? accept) + (when-let [charset (conneg/best-allowed-charset + accept + ((get resource :available-charsets) context))] + {:representation {:charset charset}}))))) + +(defdecision charset-available? negotiate-charset + encoding-available? handle-not-acceptable) + +(defn negotiate-language [{:keys [request resource] :as context}] + (try-header "Accept-Language" + (let [accept (get-in request [:headers "accept-language"])] + (if-let [lang (conneg/best-allowed-language + (if-not (empty? accept) accept "*" ) + ((get resource :available-languages) context))] + (or (= "*" lang) {:representation {:language lang}}) + (empty? accept))))) + +(defdecision language-available? negotiate-language + charset-available? handle-not-acceptable) + +(defn negotiate-media-type [{:keys [request resource] :as context}] (try-header "Accept" - (when-let [type (conneg/best-allowed-content-type - (get-in context [:request :headers "accept"]) - ((get-in context [:resource :available-media-types] (constantly "text/html")) context))] - {:representation {:media-type (conneg/stringify type)}}))) + (let [accept (get-in request [:headers "accept"])] + (if-let [type (conneg/best-allowed-content-type + (if-not (empty? accept) accept "*/*") + ((get resource :available-media-types) context))] + {:representation {:media-type (conneg/stringify type)}} + ;; if there's no accept headers and we cannot negotiate a + ;; media type then continue + (empty? accept))))) (defdecision media-type-available? negotiate-media-type - accept-language-exists? handle-not-acceptable) - -(defdecision accept-exists? - #(if (header-exists? "accept" %) - true - ;; "If no Accept header field is present, then it is assumed that the - ;; client accepts all media types" [p100] - ;; in this case we do content-type negotiation using */* as the accept - ;; specification - (if-let [type (liberator.conneg/best-allowed-content-type - "*/*" - ((get-in context [:resource :available-media-types]) context))] - [false {:representation {:media-type (liberator.conneg/stringify type)}}] - false)) - media-type-available? - accept-language-exists?) + language-available? handle-not-acceptable) (defhandler handle-options 200 nil) -(defdecision is-options? #(= :options (:request-method (:request %))) handle-options accept-exists?) +(defdecision is-options? #(= :options (:request-method (:request %))) handle-options media-type-available?) (defhandler handle-request-entity-too-large 413 "Request entity too large.") (defdecision valid-entity-length? is-options? handle-request-entity-too-large) @@ -519,8 +506,6 @@ :method-allowed? (test-request-method :allowed-methods) :malformed? false - ;; :encoding-available? true - ;; :charset-available? true :authorized? true :allowed? true :valid-content-header? true diff --git a/test/test_defresource.clj b/test/test_defresource.clj index 0bdfa9b..1b09098 100644 --- a/test/test_defresource.clj +++ b/test/test_defresource.clj @@ -56,34 +56,32 @@ :handle-ok (str request)) (facts "about defresource" - (fact "its simple form should behave as it always has" + (fact "its simple form should use unnegotiated plain text as default content type" (without-param {:request-method :get}) - => {:headers {"Content-Type" "text/plain;charset=UTF-8"}, :body "The text is test", :status 200} + => {:headers {"Content-Type" "text/plain;charset=UTF-8"} + :body "The text is test" :status 200} ((parameter "a test") {:request-method :get}) - => {:headers {"Vary" "Accept", "Content-Type" "application/xml;charset=UTF-8"}, :body "The text is a test", :status 200}) - (fact "when provided a standard config, it should add this to the keyword list" + => {:headers {"Vary" "Accept", "Content-Type" "application/xml;charset=UTF-8"} + :body "The text is a test" :status 200}) + (fact "when provided a standard config it should add this to the keyword list" (with-options {:request-method :get}) - => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"}, :body "The text is this", :status 200} + => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"} + :body "The text is this" :status 200} ((with-options-and-params "something") {:request-method :get}) - => {:headers {"Vary" "Accept", "Content-Type" "application/xml;charset=UTF-8"}, :body "The text is something", :status 200}) + => {:headers {"Vary" "Accept", "Content-Type" "application/xml;charset=UTF-8"} + :body "The text is something" :status 200}) (fact "it should also work with a function providing the standard config" ((with-options-parametrized-config "application/json" "a poem") {:request-method :get}) - => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"}, :body "The text is a poem", :status 200}) + => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"} + :body "The text is a poem" :status 200}) (fact "it should work with only a standard config" (with-options-only {:request-method :get}) - => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"}, :body "OK", :status 200}) + => {:headers {"Vary" "Accept", "Content-Type" "application/json;charset=UTF-8"} + :body "OK" :status 200}) (fact "should allow multi methods as handlers" (with-multimethod {:request-method :get}) - => {:headers {"Content-Type" "text/plain;charset=UTF-8"}, :body "with-multimethod", :status 200}) - (fact "should allow multi methods as decisions" - (with-decisions-multimethod {:request-method :get :service-available? :available}) - => {:headers {"Content-Type" "text/plain;charset=UTF-8"}, :body "with-service-available?-multimethod", :status 200}) - (fact "should allow multi methods as decisions alternate path" - (with-decisions-multimethod {:request-method :get :service-available? :not-available}) - => {:headers {"Content-Type" "text/plain;charset=UTF-8"}, :body "Service not available.", :status 503}) - (fact "should allow 'request' to be used as a resource parameter name, this was a bug at a time." - (:body ((non-anamorphic-request "test") {:request-method :get})) - => "test")) + => {:headers {"Content-Type" "text/plain;charset=UTF-8"} + :body "with-multimethod" :status 200})) (def fn-with-options From fe09c65058755c028f373dde0d954f117f784727 Mon Sep 17 00:00:00 2001 From: Philipp Meier Date: Mon, 31 Aug 2015 13:09:31 +0200 Subject: [PATCH 2/5] Remove internal decisions for conditional requests Remove *-valid-date? and *-exists and more internal decision points which did not make sense to be customized. The decision logic has been incorporated into the implementation into the actual decision points. Removes * if-match-star * if-modified-since-exists? * if-modified-since-valid-date? * if-unmodified-since-exists? * if-unmodified-since-valid-date? * if-none-match-exists? * if-none-match-star? * if-match-exists? * if-match-star? --- src/liberator/core.clj | 102 +++++++++++++---------------------------- 1 file changed, 33 insertions(+), 69 deletions(-) diff --git a/src/liberator/core.clj b/src/liberator/core.clj index b729498..78d40ad 100644 --- a/src/liberator/core.clj +++ b/src/liberator/core.clj @@ -44,8 +44,6 @@ (doseq [l *loggers*] (l category values))) -(declare if-none-match-exists?) - (defn map-values [f m] (persistent! (reduce-kv (fn [out-m k v] (assoc! out-m k (f v))) (transient {}) m))) @@ -190,12 +188,6 @@ `(defn ~name [context#] (run-handler '~name ~status ~message context#))) -(defn header-exists? [header context] - (get-in context [:request :headers header])) - -(defn if-match-star [context] - (= "*" (get-in context [:request :headers "if-match"]))) - (defn =method [method context] (= (get-in context [:request :request-method]) method)) @@ -281,7 +273,7 @@ (defhandler handle-precondition-failed 412 "Precondition failed.") (defdecision if-match-star-exists-for-missing? - if-match-star + (fn [context] (= "*" (get-in context [:request :headers "if-match"]))) handle-precondition-failed method-put?) @@ -312,78 +304,50 @@ method-patch?) (defdecision modified-since? - (fn [context] - (let [last-modified (gen-last-modified context)] - [(and last-modified (.after last-modified (::if-modified-since-date context))) - {::last-modified last-modified}])) + (fn [{:keys [request] :as context}] + (let [modified-since (parse-http-date (get-in request [:headers "if-modified-since"]))] + (or (nil? modified-since) + (let [last-modified (gen-last-modified context)] + [(and last-modified (.after last-modified modified-since)) + {::last-modified last-modified}])))) method-delete? handle-not-modified) -(defdecision if-modified-since-valid-date? - (fn [context] - (if-let [date (parse-http-date (get-in context [:request :headers "if-modified-since"]))] - {::if-modified-since-date date})) - modified-since? - method-delete?) - -(defdecision if-modified-since-exists? - (partial header-exists? "if-modified-since") - if-modified-since-valid-date? - method-delete?) - (defdecision etag-matches-for-if-none? - (fn [context] - (let [etag (gen-etag context)] - [(= (get-in context [:request :headers "if-none-match"]) etag) - {::etag etag}])) - if-none-match? - if-modified-since-exists?) - -(defdecision if-none-match-star? - #(= "*" (get-in % [:request :headers "if-none-match"])) + (fn [{:keys [request] :as context}] + (if-let [if-none-match (get-in context [:request :headers "if-none-match"])] + (let [etag (gen-etag context)] + [(#{"*" etag} if-none-match) + {::etag etag}]))) if-none-match? - etag-matches-for-if-none?) - -(defdecision if-none-match-exists? (partial header-exists? "if-none-match") - if-none-match-star? if-modified-since-exists?) + modified-since?) (defdecision unmodified-since? - (fn [context] - (let [last-modified (gen-last-modified context)] - [(and last-modified - (.after last-modified - (::if-unmodified-since-date context))) - {::last-modified last-modified}])) + (fn [{:keys [request] :as context}] + (when-let [unmodified-since (parse-http-date (get-in request [:headers "if-unmodified-since"]))] + (let [last-modified (gen-last-modified context)] + [(and last-modified (.after last-modified unmodified-since)) + {::last-modified last-modified}]))) handle-precondition-failed - if-none-match-exists?) - -(defdecision if-unmodified-since-valid-date? - (fn [context] - (when-let [date (parse-http-date (get-in context [:request :headers "if-unmodified-since"]))] - {::if-unmodified-since-date date})) - unmodified-since? - if-none-match-exists?) + etag-matches-for-if-none?) -(defdecision if-unmodified-since-exists? (partial header-exists? "if-unmodified-since") - if-unmodified-since-valid-date? if-none-match-exists?) +(defn- match-etag-for-existing [{:keys [request resource] :as context}] + (let [if-match (get-in request [:headers "if-match"])] + (or (empty? if-match) + (= "*" if-match) + (let [etag (gen-etag context)] + [(= etag if-match) + {::etag etag}])))) (defdecision etag-matches-for-if-match? - (fn [context] - (let [etag (gen-etag context)] - [(= etag (get-in context [:request :headers "if-match"])) - {::etag etag}])) - if-unmodified-since-exists? + match-etag-for-existing + unmodified-since? handle-precondition-failed) -(defdecision if-match-star? - if-match-star if-unmodified-since-exists? etag-matches-for-if-match?) - -(defdecision if-match-exists? (partial header-exists? "if-match") - if-match-star? if-unmodified-since-exists?) - -(defdecision exists? if-match-exists? if-match-star-exists-for-missing?) +(defdecision exists? etag-matches-for-if-match? if-match-star-exists-for-missing?) (defhandler handle-unprocessable-entity 422 "Unprocessable entity.") + (defdecision processable? exists? handle-unprocessable-entity) (defhandler handle-not-acceptable 406 "No acceptable resource available.") @@ -412,7 +376,7 @@ (or (empty? accept) (when-let [charset (conneg/best-allowed-charset accept - ((get resource :available-charsets) context))] + ((:available-charsets resource) context))] {:representation {:charset charset}}))))) (defdecision charset-available? negotiate-charset @@ -423,7 +387,7 @@ (let [accept (get-in request [:headers "accept-language"])] (if-let [lang (conneg/best-allowed-language (if-not (empty? accept) accept "*" ) - ((get resource :available-languages) context))] + ((:available-languages resource) context))] (or (= "*" lang) {:representation {:language lang}}) (empty? accept))))) @@ -435,7 +399,7 @@ (let [accept (get-in request [:headers "accept"])] (if-let [type (conneg/best-allowed-content-type (if-not (empty? accept) accept "*/*") - ((get resource :available-media-types) context))] + ((:available-media-types resource) context))] {:representation {:media-type (conneg/stringify type)}} ;; if there's no accept headers and we cannot negotiate a ;; media type then continue From b5d328bf7bd3b86fede46af6da02d02e66a24bc3 Mon Sep 17 00:00:00 2001 From: Philipp Meier Date: Mon, 31 Aug 2015 13:38:18 +0200 Subject: [PATCH 3/5] Dim internal decisions in graph --- src/liberator/graph.clj | 24 +- src/liberator/trace.svg | 1146 ++++++++++++++++----------------------- 2 files changed, 487 insertions(+), 683 deletions(-) diff --git a/src/liberator/graph.clj b/src/liberator/graph.clj index 03b2264..6197708 100644 --- a/src/liberator/graph.clj +++ b/src/liberator/graph.clj @@ -10,13 +10,21 @@ (defn to-graph [[& args]] (condp = (first args) 'defdecision - (let [[name then else] (apply extract args)] - (format (str "\"%s\" [id = \"%s\"] \n " - "\"%s\" -> \"%s\" [label = \"true\", id = \"%s\"] \n" - "\"%s\" -> \"%s\" [label = \"false\", id = \"%s\"]\n") - name (clean-id name) - name then (clean-id (str name "_" then)) - name else (clean-id (str name "_" else)))) + (let [[name then else] (apply extract args) + internal? (#{"is-options?" + "method-put?" + "method-delete?" + "method-patch?" + "post-to-existing?" + "post-to-missing?" + "post-to-gone?" + "put-to-existing?"} (str name))] + (format (str "\"%s\" [id = \"%s\" %s] \n " + "\"%s\" -> \"%s\" [label = \"true\", id = \"%s\"] \n" + "\"%s\" -> \"%s\" [label=\"false\", id = \"%s\"]\n") + name (clean-id name) (if internal? "style=\"filled\" fillcolor=\"#CCCCCC\"" "") + name then (clean-id (str name "_" then)) + name else (clean-id (str name "_" else )))) 'defaction (let [[_ name then] args] (format (str "\"%s\"[shape=\"ellipse\" id = \"%s\"];\n" @@ -78,7 +86,7 @@ (let [{:keys [nodes handlers actions]} (parse-source-definitions)] (->> nodes (map to-graph) - (filter identity) + (remove nil?) (concat (rank-handler-groups handlers)) (concat (rank-same (remove #{'initialize-context} actions))) (apply str) diff --git a/src/liberator/trace.svg b/src/liberator/trace.svg index 56dde90..9fa3a25 100644 --- a/src/liberator/trace.svg +++ b/src/liberator/trace.svg @@ -4,1157 +4,953 @@ - - + + %3 - + post! - -post! + +post! post-redirect? - -post-redirect? + +post-redirect? post!->post-redirect? - - + + patch! - -patch! + +patch! respond-with-entity? - -respond-with-entity? + +respond-with-entity? patch!->respond-with-entity? - - + + put! - -put! + +put! new? - -new? + +new? put!->new? - - + + delete! - -delete! + +delete! delete-enacted? - -delete-enacted? + +delete-enacted? delete!->delete-enacted? - - + + handle-see-other - -303 -see-other + +303 +see-other handle-multiple-representations - -300 -multiple-representations + +300 +multiple-representations handle-moved-permanently - -301 -moved-permanently + +301 +moved-permanently handle-moved-temporarily - -307 -moved-temporarily + +307 +moved-temporarily handle-not-modified - -304 -not-modified + +304 +not-modified handle-ok - -200 -ok + +200 +ok handle-no-content - -204 -no-content + +204 +no-content handle-created - -201 -created + +201 +created handle-accepted - -202 -accepted + +202 +accepted handle-options - -200 -options + +200 +options handle-not-found - -404 -not-found + +404 +not-found handle-gone - -410 -gone + +410 +gone handle-conflict - -409 -conflict + +409 +conflict handle-precondition-failed - -412 -precondition-failed + +412 +precondition-failed handle-unprocessable-entity - -422 -unprocessable-entity + +422 +unprocessable-entity handle-not-acceptable - -406 -not-acceptable + +406 +not-acceptable handle-request-entity-too-large - -413 -request-entity-too-large + +413 +request-entity-too-large handle-unsupported-media-type - -415 -unsupported-media-type + +415 +unsupported-media-type handle-forbidden - -403 -forbidden + +403 +forbidden handle-unauthorized - -401 -unauthorized + +401 +unauthorized handle-malformed - -400 -malformed + +400 +malformed handle-method-not-allowed - -405 -method-not-allowed + +405 +method-not-allowed handle-uri-too-long - -414 -uri-too-long + +414 +uri-too-long handle-not-implemented - -501 -not-implemented + +501 +not-implemented handle-unknown-method - -501 -unknown-method + +501 +unknown-method handle-service-not-available - -503 -service-not-available + +503 +service-not-available handle-exception - -500 -exception + +500 +exception multiple-representations? - -multiple-representations? + +multiple-representations? multiple-representations?->handle-multiple-representations - - -true + + +true multiple-representations?->handle-ok - - -false + + +false respond-with-entity?->handle-no-content - - -false + + +false respond-with-entity?->multiple-representations? - - -true + + +true new?->handle-created - - -true + + +true new?->respond-with-entity? - - -false + + +false post-redirect?->handle-see-other - - -true + + +true post-redirect?->new? - - -false + + +false can-post-to-missing? - -can-post-to-missing? + +can-post-to-missing? can-post-to-missing?->post! - - -true + + +true can-post-to-missing?->handle-not-found - - -false + + +false post-to-missing? - -post-to-missing? + +post-to-missing? post-to-missing?->handle-not-found - - -false + + +false post-to-missing?->can-post-to-missing? - - -true + + +true can-post-to-gone? - -can-post-to-gone? + +can-post-to-gone? can-post-to-gone?->post! - - -true + + +true can-post-to-gone?->handle-gone - - -false + + +false post-to-gone? - -post-to-gone? + +post-to-gone? post-to-gone?->handle-gone - - -false + + +false post-to-gone?->can-post-to-gone? - - -true + + +true moved-temporarily? - -moved-temporarily? + +moved-temporarily? moved-temporarily?->handle-moved-temporarily - - -true + + +true moved-temporarily?->post-to-gone? - - -false + + +false moved-permanently? - -moved-permanently? + +moved-permanently? moved-permanently?->handle-moved-permanently - - -true + + +true moved-permanently?->moved-temporarily? - - -false + + +false existed? - -existed? + +existed? existed?->post-to-missing? - - -false + + +false existed?->moved-permanently? - - -true + + +true conflict? - -conflict? + +conflict? conflict?->put! - - -false + + +false conflict?->handle-conflict - - -true + + +true can-put-to-missing? - -can-put-to-missing? + +can-put-to-missing? can-put-to-missing?->handle-not-implemented - - -false + + +false can-put-to-missing?->conflict? - - -true + + +true put-to-different-url? - -put-to-different-url? + +put-to-different-url? put-to-different-url?->handle-moved-permanently - - -true + + +true put-to-different-url?->can-put-to-missing? - - -false + + +false method-put? - -method-put? + +method-put? method-put?->existed? - - -false + + +false method-put?->put-to-different-url? - - -true + + +true if-match-star-exists-for-missing? - -if-match-star-exists-for-missing? + +if-match-star-exists-for-missing? if-match-star-exists-for-missing?->handle-precondition-failed - - -true + + +true if-match-star-exists-for-missing?->method-put? - - -false + + +false if-none-match? - -if-none-match? + +if-none-match? if-none-match?->handle-not-modified - - -true + + +true if-none-match?->handle-precondition-failed - - -false + + +false put-to-existing? - -put-to-existing? + +put-to-existing? put-to-existing?->multiple-representations? - - -false + + +false put-to-existing?->conflict? - - -true + + +true post-to-existing? - -post-to-existing? + +post-to-existing? post-to-existing?->post! - - -true + + +true post-to-existing?->put-to-existing? - - -false + + +false delete-enacted?->handle-accepted - - -false + + +false delete-enacted?->respond-with-entity? - - -true + + +true method-patch? - -method-patch? + +method-patch? method-patch?->patch! - - -true + + +true method-patch?->post-to-existing? - - -false + + +false method-delete? - -method-delete? + +method-delete? method-delete?->delete! - - -true + + +true method-delete?->method-patch? - - -false + + +false modified-since? - -modified-since? + +modified-since? modified-since?->handle-not-modified - - -false + + +false modified-since?->method-delete? - - -true - - -if-modified-since-valid-date? - -if-modified-since-valid-date? - - -if-modified-since-valid-date?->method-delete? - - -false - - -if-modified-since-valid-date?->modified-since? - - -true - - -if-modified-since-exists? - -if-modified-since-exists? - - -if-modified-since-exists?->method-delete? - - -false - - -if-modified-since-exists?->if-modified-since-valid-date? - - -true + + +true etag-matches-for-if-none? - -etag-matches-for-if-none? + +etag-matches-for-if-none? etag-matches-for-if-none?->if-none-match? - - -true - - -etag-matches-for-if-none?->if-modified-since-exists? - - -false - - -if-none-match-star? - -if-none-match-star? - - -if-none-match-star?->if-none-match? - - -true - - -if-none-match-star?->etag-matches-for-if-none? - - -false - - -if-none-match-exists? - -if-none-match-exists? - - -if-none-match-exists?->if-modified-since-exists? - - -false - - -if-none-match-exists?->if-none-match-star? - - -true + + +true + + +etag-matches-for-if-none?->modified-since? + + +false unmodified-since? - -unmodified-since? + +unmodified-since? unmodified-since?->handle-precondition-failed - - -true - - -unmodified-since?->if-none-match-exists? - - -false - - -if-unmodified-since-valid-date? - -if-unmodified-since-valid-date? - - -if-unmodified-since-valid-date?->if-none-match-exists? - - -false - - -if-unmodified-since-valid-date?->unmodified-since? - - -true - - -if-unmodified-since-exists? - -if-unmodified-since-exists? - - -if-unmodified-since-exists?->if-none-match-exists? - - -false - - -if-unmodified-since-exists?->if-unmodified-since-valid-date? - - -true + + +true + + +unmodified-since?->etag-matches-for-if-none? + + +false etag-matches-for-if-match? - -etag-matches-for-if-match? + +etag-matches-for-if-match? etag-matches-for-if-match?->handle-precondition-failed - - -false - - -etag-matches-for-if-match?->if-unmodified-since-exists? - - -true - - -if-match-star? - -if-match-star? - - -if-match-star?->if-unmodified-since-exists? - - -true - - -if-match-star?->etag-matches-for-if-match? - - -false - - -if-match-exists? - -if-match-exists? - - -if-match-exists?->if-unmodified-since-exists? - - -false - - -if-match-exists?->if-match-star? - - -true + + +false + + +etag-matches-for-if-match?->unmodified-since? + + +true exists? - -exists? + +exists? exists?->if-match-star-exists-for-missing? - - -false + + +false - -exists?->if-match-exists? - - -true + +exists?->etag-matches-for-if-match? + + +true processable? - -processable? + +processable? processable?->handle-unprocessable-entity - - -false + + +false processable?->exists? - - -true + + +true encoding-available? - -encoding-available? + +encoding-available? encoding-available?->handle-not-acceptable - - -false + + +false encoding-available?->processable? - - -true - - -accept-encoding-exists? - -accept-encoding-exists? - - -accept-encoding-exists?->processable? - - -false - - -accept-encoding-exists?->encoding-available? - - -true + + +true charset-available? - -charset-available? + +charset-available? charset-available?->handle-not-acceptable - - -false - - -charset-available?->accept-encoding-exists? - - -true - - -accept-charset-exists? - -accept-charset-exists? - - -accept-charset-exists?->accept-encoding-exists? - - -false - - -accept-charset-exists?->charset-available? - - -true + + +false + + +charset-available?->encoding-available? + + +true language-available? - -language-available? + +language-available? language-available?->handle-not-acceptable - - -false - - -language-available?->accept-charset-exists? - - -true - - -accept-language-exists? - -accept-language-exists? - - -accept-language-exists?->accept-charset-exists? - - -false - - -accept-language-exists?->language-available? - - -true + + +false + + +language-available?->charset-available? + + +true media-type-available? - -media-type-available? + +media-type-available? media-type-available?->handle-not-acceptable - - -false - - -media-type-available?->accept-language-exists? - - -true - - -accept-exists? - -accept-exists? - - -accept-exists?->accept-language-exists? - - -false - - -accept-exists?->media-type-available? - - -true + + +false + + +media-type-available?->language-available? + + +true is-options? - -is-options? + +is-options? is-options?->handle-options - - -true + + +true - -is-options?->accept-exists? - - -false + +is-options?->media-type-available? + + +false valid-entity-length? - -valid-entity-length? + +valid-entity-length? valid-entity-length?->handle-request-entity-too-large - - -false + + +false valid-entity-length?->is-options? - - -true + + +true known-content-type? - -known-content-type? + +known-content-type? known-content-type?->handle-unsupported-media-type - - -false + + +false known-content-type?->valid-entity-length? - - -true + + +true valid-content-header? - -valid-content-header? + +valid-content-header? valid-content-header?->handle-not-implemented - - -false + + +false valid-content-header?->known-content-type? - - -true + + +true allowed? - -allowed? + +allowed? allowed?->handle-forbidden - - -false + + +false allowed?->valid-content-header? - - -true + + +true authorized? - -authorized? + +authorized? authorized?->handle-unauthorized - - -false + + +false authorized?->allowed? - - -true + + +true malformed? - -malformed? + +malformed? malformed?->handle-malformed - - -true + + +true malformed?->authorized? - - -false + + +false method-allowed? - -method-allowed? + +method-allowed? method-allowed?->handle-method-not-allowed - - -false + + +false method-allowed?->malformed? - - -true + + +true uri-too-long? - -uri-too-long? + +uri-too-long? uri-too-long?->handle-uri-too-long - - -true + + +true uri-too-long?->method-allowed? - - -false + + +false known-method? - -known-method? + +known-method? known-method?->handle-unknown-method - - -false + + +false known-method?->uri-too-long? - - -true + + +true service-available? - -service-available? + +service-available? service-available?->handle-service-not-available - - -false + + +false service-available?->known-method? - - -true + + +true initialize-context - -initialize-context + +initialize-context initialize-context->service-available? - - + + From 571f86750a33ef8a247fbcf4a3055464aad279c1 Mon Sep 17 00:00:00 2001 From: Philipp Meier Date: Fri, 22 Apr 2016 13:19:59 +0200 Subject: [PATCH 4/5] Make graph more compact --- src/liberator/graph.clj | 9 +- src/liberator/trace.svg | 1152 +++++++++++++++++++-------------------- 2 files changed, 583 insertions(+), 578 deletions(-) diff --git a/src/liberator/graph.clj b/src/liberator/graph.clj index 6197708..b54a3b2 100644 --- a/src/liberator/graph.clj +++ b/src/liberator/graph.clj @@ -22,7 +22,7 @@ (format (str "\"%s\" [id = \"%s\" %s] \n " "\"%s\" -> \"%s\" [label = \"true\", id = \"%s\"] \n" "\"%s\" -> \"%s\" [label=\"false\", id = \"%s\"]\n") - name (clean-id name) (if internal? "style=\"filled\" fillcolor=\"#CCCCCC\"" "") + name (clean-id name) (if internal? "shape=\"octagon\" style=\"filled\" fillcolor=\"#CCCCCC\"" "") name then (clean-id (str name "_" then)) name else (clean-id (str name "_" else )))) 'defaction @@ -57,6 +57,7 @@ (defn rank-handler-groups [handlers] (->> handlers (group-by (fn [[name status]] (int (/ status 100)))) + (remove #(#{4 5} (first %))) vals (map (fn [sg] (map first sg))) (map rank-same) @@ -71,6 +72,8 @@ decisions (->> nodes (filter #(= 'defdecision (first %))) (map second)) + conneg-decisions (filter #(.endsWith (name %) "available?") + decisions) handlers (->> nodes (filter #(= 'defhandler (first %))) (map (fn [[_ name status _]] [name status]))) @@ -79,16 +82,18 @@ (map second))] {:nodes nodes :decisions decisions + :conneg-decisions conneg-decisions :handlers handlers :actions actions})) (defn generate-graph-dot [] - (let [{:keys [nodes handlers actions]} (parse-source-definitions)] + (let [{:keys [nodes conneg-decisions handlers actions]} (parse-source-definitions)] (->> nodes (map to-graph) (remove nil?) (concat (rank-handler-groups handlers)) (concat (rank-same (remove #{'initialize-context} actions))) + (concat (rank-same (remove #{'initialize-context} conneg-decisions))) (apply str) (format (str "digraph{\nid=\"trace\"; size=\"1000,1000\"; page=\"1000,1000\";\n\n" "edge[fontname=\"sans-serif\"]\n" diff --git a/src/liberator/trace.svg b/src/liberator/trace.svg index 9fa3a25..ca12666 100644 --- a/src/liberator/trace.svg +++ b/src/liberator/trace.svg @@ -4,953 +4,953 @@ - - + + %3 - + + +encoding-available? + +encoding-available? + + +processable? + +processable? + + +encoding-available?->processable? + + +true + + +handle-not-acceptable + +406 +not-acceptable + + +encoding-available?->handle-not-acceptable + + +false + + +charset-available? + +charset-available? + + +charset-available?->encoding-available? + + +true + + +charset-available?->handle-not-acceptable + + +false + + +language-available? + +language-available? + + +language-available?->charset-available? + + +true + + +language-available?->handle-not-acceptable + + +false + + +media-type-available? + +media-type-available? + + +media-type-available?->language-available? + + +true + + +media-type-available?->handle-not-acceptable + + +false + + +service-available? + +service-available? + + +known-method? + +known-method? + + +service-available?->known-method? + + +true + + +handle-service-not-available + +503 +service-not-available + + +service-available?->handle-service-not-available + + +false + post! - -post! + +post! post-redirect? - -post-redirect? + +post-redirect? post!->post-redirect? - - + + patch! - -patch! + +patch! respond-with-entity? - -respond-with-entity? + +respond-with-entity? patch!->respond-with-entity? - - + + put! - -put! + +put! new? - -new? + +new? put!->new? - - + + delete! - -delete! + +delete! delete-enacted? - -delete-enacted? + +delete-enacted? delete!->delete-enacted? - - + + handle-see-other - -303 -see-other + +303 +see-other handle-multiple-representations - -300 -multiple-representations + +300 +multiple-representations handle-moved-permanently - -301 -moved-permanently + +301 +moved-permanently handle-moved-temporarily - -307 -moved-temporarily + +307 +moved-temporarily handle-not-modified - -304 -not-modified + +304 +not-modified handle-ok - -200 -ok + +200 +ok handle-no-content - -204 -no-content + +204 +no-content handle-created - -201 -created + +201 +created handle-accepted - -202 -accepted + +202 +accepted handle-options - -200 -options - - -handle-not-found - -404 -not-found - - -handle-gone - -410 -gone - - -handle-conflict - -409 -conflict - - -handle-precondition-failed - -412 -precondition-failed - - -handle-unprocessable-entity - -422 -unprocessable-entity - - -handle-not-acceptable - -406 -not-acceptable - - -handle-request-entity-too-large - -413 -request-entity-too-large - - -handle-unsupported-media-type - -415 -unsupported-media-type - - -handle-forbidden - -403 -forbidden - - -handle-unauthorized - -401 -unauthorized - - -handle-malformed - -400 -malformed - - -handle-method-not-allowed - -405 -method-not-allowed - - -handle-uri-too-long - -414 -uri-too-long - - -handle-not-implemented - -501 -not-implemented - - -handle-unknown-method - -501 -unknown-method - - -handle-service-not-available - -503 -service-not-available - - -handle-exception - -500 -exception + +200 +options multiple-representations? - -multiple-representations? + +multiple-representations? multiple-representations?->handle-multiple-representations - - -true + + +true multiple-representations?->handle-ok - - -false + + +false respond-with-entity?->handle-no-content - - -false + + +false respond-with-entity?->multiple-representations? - - -true + + +true new?->handle-created - - -true + + +true new?->respond-with-entity? - - -false + + +false post-redirect?->handle-see-other - - -true + + +true post-redirect?->new? - - -false + + +false + + +handle-not-found + +404 +not-found + + +handle-gone + +410 +gone can-post-to-missing? - -can-post-to-missing? + +can-post-to-missing? can-post-to-missing?->post! - - -true + + +true can-post-to-missing?->handle-not-found - - -false + + +false post-to-missing? - -post-to-missing? + +post-to-missing? post-to-missing?->handle-not-found - - -false + + +false post-to-missing?->can-post-to-missing? - - -true + + +true can-post-to-gone? - -can-post-to-gone? + +can-post-to-gone? can-post-to-gone?->post! - - -true + + +true can-post-to-gone?->handle-gone - - -false + + +false post-to-gone? - -post-to-gone? + +post-to-gone? post-to-gone?->handle-gone - - -false + + +false post-to-gone?->can-post-to-gone? - - -true + + +true moved-temporarily? - -moved-temporarily? + +moved-temporarily? moved-temporarily?->handle-moved-temporarily - - -true + + +true moved-temporarily?->post-to-gone? - - -false + + +false moved-permanently? - -moved-permanently? + +moved-permanently? moved-permanently?->handle-moved-permanently - - -true + + +true moved-permanently?->moved-temporarily? - - -false + + +false existed? - -existed? + +existed? existed?->post-to-missing? - - -false + + +false existed?->moved-permanently? - - -true + + +true + + +handle-conflict + +409 +conflict conflict? - -conflict? + +conflict? conflict?->put! - - -false + + +false conflict?->handle-conflict - - -true + + +true + + +handle-not-implemented + +501 +not-implemented can-put-to-missing? - -can-put-to-missing? - - -can-put-to-missing?->handle-not-implemented - - -false + +can-put-to-missing? can-put-to-missing?->conflict? - - -true + + +true + + +can-put-to-missing?->handle-not-implemented + + +false put-to-different-url? - -put-to-different-url? + +put-to-different-url? put-to-different-url?->handle-moved-permanently - - -true + + +true put-to-different-url?->can-put-to-missing? - - -false + + +false method-put? - -method-put? + +method-put? method-put?->existed? - - -false + + +false method-put?->put-to-different-url? - - -true + + +true + + +handle-precondition-failed + +412 +precondition-failed if-match-star-exists-for-missing? - -if-match-star-exists-for-missing? - - -if-match-star-exists-for-missing?->handle-precondition-failed - - -true + +if-match-star-exists-for-missing? if-match-star-exists-for-missing?->method-put? - - -false + + +false + + +if-match-star-exists-for-missing?->handle-precondition-failed + + +true if-none-match? - -if-none-match? + +if-none-match? if-none-match?->handle-not-modified - - -true + + +true if-none-match?->handle-precondition-failed - - -false + + +false put-to-existing? - -put-to-existing? + +put-to-existing? put-to-existing?->multiple-representations? - - -false + + +false put-to-existing?->conflict? - - -true + + +true post-to-existing? - -post-to-existing? + +post-to-existing? post-to-existing?->post! - - -true + + +true post-to-existing?->put-to-existing? - - -false + + +false delete-enacted?->handle-accepted - - -false + + +false delete-enacted?->respond-with-entity? - - -true + + +true method-patch? - -method-patch? + +method-patch? method-patch?->patch! - - -true + + +true method-patch?->post-to-existing? - - -false + + +false method-delete? - -method-delete? + +method-delete? method-delete?->delete! - - -true + + +true method-delete?->method-patch? - - -false + + +false modified-since? - -modified-since? + +modified-since? modified-since?->handle-not-modified - - -false + + +false modified-since?->method-delete? - - -true + + +true etag-matches-for-if-none? - -etag-matches-for-if-none? + +etag-matches-for-if-none? etag-matches-for-if-none?->if-none-match? - - -true + + +true etag-matches-for-if-none?->modified-since? - - -false + + +false unmodified-since? - -unmodified-since? + +unmodified-since? unmodified-since?->handle-precondition-failed - - -true + + +true unmodified-since?->etag-matches-for-if-none? - - -false + + +false etag-matches-for-if-match? - -etag-matches-for-if-match? + +etag-matches-for-if-match? etag-matches-for-if-match?->handle-precondition-failed - - -false + + +false etag-matches-for-if-match?->unmodified-since? - - -true + + +true exists? - -exists? + +exists? exists?->if-match-star-exists-for-missing? - - -false + + +false exists?->etag-matches-for-if-match? - - -true + + +true - -processable? - -processable? - - -processable?->handle-unprocessable-entity - - -false + +handle-unprocessable-entity + +422 +unprocessable-entity processable?->exists? - - -true - - -encoding-available? - -encoding-available? - - -encoding-available?->handle-not-acceptable - - -false - - -encoding-available?->processable? - - -true - - -charset-available? - -charset-available? - - -charset-available?->handle-not-acceptable - - -false - - -charset-available?->encoding-available? - - -true - - -language-available? - -language-available? - - -language-available?->handle-not-acceptable - - -false - - -language-available?->charset-available? - - -true - - -media-type-available? - -media-type-available? - - -media-type-available?->handle-not-acceptable - - -false + + +true - -media-type-available?->language-available? - - -true + +processable?->handle-unprocessable-entity + + +false is-options? - -is-options? + +is-options? + + +is-options?->media-type-available? + + +false is-options?->handle-options - - -true + + +true - -is-options?->media-type-available? - - -false + +handle-request-entity-too-large + +413 +request-entity-too-large valid-entity-length? - -valid-entity-length? + +valid-entity-length? + + +valid-entity-length?->is-options? + + +true valid-entity-length?->handle-request-entity-too-large - - -false + + +false - -valid-entity-length?->is-options? - - -true + +handle-unsupported-media-type + +415 +unsupported-media-type known-content-type? - -known-content-type? - - -known-content-type?->handle-unsupported-media-type - - -false + +known-content-type? known-content-type?->valid-entity-length? - - -true + + +true + + +known-content-type?->handle-unsupported-media-type + + +false valid-content-header? - -valid-content-header? + +valid-content-header? valid-content-header?->handle-not-implemented - - -false + + +false valid-content-header?->known-content-type? - - -true + + +true + + +handle-forbidden + +403 +forbidden allowed? - -allowed? + +allowed? + + +allowed?->valid-content-header? + + +true allowed?->handle-forbidden - - -false + + +false - -allowed?->valid-content-header? - - -true + +handle-unauthorized + +401 +unauthorized authorized? - -authorized? + +authorized? + + +authorized?->allowed? + + +true authorized?->handle-unauthorized - - -false + + +false - -authorized?->allowed? - - -true + +handle-malformed + +400 +malformed malformed? - -malformed? + +malformed? + + +malformed?->authorized? + + +false malformed?->handle-malformed - - -true + + +true - -malformed?->authorized? - - -false + +handle-method-not-allowed + +405 +method-not-allowed method-allowed? - -method-allowed? + +method-allowed? + + +method-allowed?->malformed? + + +true method-allowed?->handle-method-not-allowed - - -false + + +false - -method-allowed?->malformed? - - -true + +handle-uri-too-long + +414 +uri-too-long uri-too-long? - -uri-too-long? - - -uri-too-long?->handle-uri-too-long - - -true + +uri-too-long? uri-too-long?->method-allowed? - - -false + + +false - -known-method? - -known-method? + +uri-too-long?->handle-uri-too-long + + +true - -known-method?->handle-unknown-method - - -false + +handle-unknown-method + +501 +unknown-method known-method?->uri-too-long? - - -true - - -service-available? - -service-available? - - -service-available?->handle-service-not-available - - -false + + +true - -service-available?->known-method? - - -true + +known-method?->handle-unknown-method + + +false initialize-context - -initialize-context + +initialize-context initialize-context->service-available? - - + + + + +handle-exception + +500 +exception From 14d738d627bb6a64f9a32845f59724819782ed95 Mon Sep 17 00:00:00 2001 From: Philipp Meier Date: Fri, 5 Aug 2016 11:04:20 +0200 Subject: [PATCH 5/5] Improve highlighting of trace view, dims untaken path. --- src/liberator/dev.clj | 17 ++++++++--------- src/liberator/trace.css | 9 ++++++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/liberator/dev.clj b/src/liberator/dev.clj index 7b01f4a..3c08e87 100644 --- a/src/liberator/dev.clj +++ b/src/liberator/dev.clj @@ -79,12 +79,12 @@ (format "svg.getElementById(\"%s\").setAttribute(\"class\", svg.getElementById(\"%s\").getAttribute(\"class\") + \" %s\");" id id (if (result->bool r1) "hl-true" "hl-false")))) (map vector log (rest log)))) - + "};" "setTimeout(function(){insertStyle()}, 500);" "setTimeout(function(){insertStyle()}, 1000);" "setTimeout(function(){insertStyle()}, 5000);" - + ""])] [:body [:a {:href mount-url} "List of all traces"] @@ -99,10 +99,10 @@ [:ol (map (fn [[l [n r]]] [:li (h l) ": " (h n) " " (if (nil? r) [:em "nil"] (h (pr-str r)))]) log)] [:div {:style "text-align: center;"} - [:object {:id "trace" :data (str mount-url "trace.svg") :width "90%" + [:object {:id "trace" :data (str mount-url "trace.svg") :width "100%" :style "border: 1px solid #666;"}]] - + [:h3 "Full Request"] [:pre [:tt (h (with-out-str (clojure.pprint/pprint r)))]]]) "application/json" @@ -123,7 +123,7 @@ (defresource list-handler :available-media-types ["text/html"] - :handle-ok (fn [_] + :handle-ok (fn [_] (html5 [:head [:title "Liberator Request Traces"]] @@ -168,11 +168,11 @@ :available-media-types ["text/css"] :handle-ok "#x-liberator-trace { display:block; - + position:absolute; top:0; right:0; - + margin-top: 1em; margin-right: 1em; padding: 0 1em; @@ -192,7 +192,6 @@ (defn- wrap-trace-ui [handler] (let [base-url (with-slash mount-url)] (routes - ;; (fn [_] (GET (str base-url "trace.svg") [] (fn [_] trace-svg)) (ANY (str base-url "styles.css") [] styles) (ANY [(str base-url ":id") :id #".+"] [id] #((log-handler id) %)) @@ -226,7 +225,7 @@ :ui - Include link to a resource that dumps the current request :header - Include full trace in response header" [handler & opts] - (-> + (-> (fn [request] (let [request-log (atom [])] (binding [*current-id* (next-id)] diff --git a/src/liberator/trace.css b/src/liberator/trace.css index d66adbf..8394609 100644 --- a/src/liberator/trace.css +++ b/src/liberator/trace.css @@ -1,3 +1,10 @@ +.node:not(.hl-false):not(.hl-true) polygon { fill: #f8f8f8; stroke: #dddddd; } +.node ellipse { fill: #f8f8f8; stroke: #dddddd; } +.node text { fill: #999999; } +.edge path { stroke: #999999; stroke-width: 1;} +.edge text { fill: #999999; } +.edge polygon { fill: #999999; stroke: #999999 } + .node.hl-true:not([id^="handle"]) polygon { fill: #ccffcc; stroke: #00dd00; } .node.hl-true polygon { stroke-width: 3;} .node.hl-true text { fill: #003300; } @@ -10,4 +17,4 @@ .node.hl-false text { fill: #330000; } .edge.hl-false path { stroke: #dd0000; stroke-width: 3;} .edge.hl-false polygon { fill: #dd0000; stroke: #dd0000; stroke-width: 3;} -.edge.hl-false text { fill: #dd0000; } \ No newline at end of file +.edge.hl-false text { fill: #dd0000; }