diff --git a/modules/reitit-core/src/reitit/impl.cljc b/modules/reitit-core/src/reitit/impl.cljc index 6f954807..0a29f490 100644 --- a/modules/reitit-core/src/reitit/impl.cljc +++ b/modules/reitit-core/src/reitit/impl.cljc @@ -1,15 +1,3 @@ -; Copyright 2013 Relevance, Inc. -; Copyright 2014-2016 Cognitect, Inc. - -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0) -; which can be found in the file epl-v10.html at the root of this distribution. -; -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; -; You must not remove this notice, or any other, from this software. - (ns ^:no-doc reitit.impl #?(:cljs (:require-macros [reitit.impl])) (:require [clojure.string :as str] @@ -19,17 +7,22 @@ (java.util HashMap Map) (java.net URLEncoder URLDecoder)))) -(defn map-kv - "Applies a function to every value of a map. - +(defn maybe-map-values + "Applies a function to every value of a map, updates the value if not nil. Also works on vectors. Maintains key for maps, order for vectors." [f coll] (reduce-kv - (fn [m k v] - (assoc m k (f v))) - (empty coll) + (fn [coll k v] + (if-some [v' (f v)] + (assoc coll k v') + coll)) + coll coll)) +;; +;; https://github.com/pedestal/pedestal/blob/master/route/src/io/pedestal/http/route/prefix_tree.clj +;; + (defn wild? [s] (contains? #{\: \*} (first (str s)))) @@ -191,17 +184,19 @@ #?(:clj (str/replace s #"[^A-Za-z0-9\!'\(\)\*_~.-]+" percent-encode) :cljs (js/encodeURIComponent s)))) -(defn url-decode [s] +(defn maybe-url-decode [s] (if s #?(:clj (if (.contains ^String s "%") (URLDecoder/decode (if (.contains ^String s "+") (.replace ^String s "+" "%2B") s) - "UTF-8") - s) + "UTF-8")) :cljs (js/decodeURIComponent s)))) +(defn url-decode [s] + (or (maybe-url-decode s) s)) + (defn form-encode [s] (if s #?(:clj (URLEncoder/encode ^String s "UTF-8") @@ -217,7 +212,7 @@ (defn url-decode-coll "URL-decodes maps and vectors" [coll] - (map-kv url-decode coll)) + (maybe-map-values maybe-url-decode coll)) (defprotocol IntoString (into-string [_])) @@ -251,7 +246,7 @@ (defn path-params "Convert parameters' values into URL-encoded strings, suitable for URL paths" [params] - (map-kv #(url-encode (into-string %)) params)) + (maybe-map-values #(url-encode (into-string %)) params)) (defn query-string "shallow transform of query parameters into query string" @@ -270,7 +265,7 @@ (goog/inherits ~type ~base-type) ~@(map - (fn [method] - `(set! (.. ~type -prototype ~(symbol (str "-" (first method)))) - (fn ~@(rest method)))) - methods))) + (fn [method] + `(set! (.. ~type -prototype ~(symbol (str "-" (first method)))) + (fn ~@(rest method)))) + methods))) diff --git a/modules/reitit-core/src/reitit/segment.cljc b/modules/reitit-core/src/reitit/segment.cljc index 929cf28b..0675a8c4 100644 --- a/modules/reitit-core/src/reitit/segment.cljc +++ b/modules/reitit-core/src/reitit/segment.cljc @@ -15,16 +15,16 @@ (-lookup [_ _ _])) (defn- -catch-all [children catch-all path-params p ps] - (if catch-all - (-lookup - (impl/fast-get children catch-all) - nil - (assoc path-params catch-all (str/join "/" (cons p ps)))))) + (-lookup + (impl/fast-get children catch-all) + nil + (assoc path-params catch-all (str/join "/" (cons p ps))))) (defn- segment ([] (segment {} #{} nil nil)) ([children wilds catch-all match] - (let [children' (impl/fast-map children)] + (let [children' (impl/fast-map children) + wilds? (seq wilds)] ^{:type ::segment} (reify Segment @@ -40,8 +40,8 @@ (if (nil? p) (when match (assoc match :path-params path-params)) (or (-lookup (impl/fast-get children' p) ps path-params) - (some #(-lookup (impl/fast-get children' %) ps (assoc path-params % p)) wilds) - (-catch-all children' catch-all path-params p ps)))))))) + (if wilds? (some #(-lookup (impl/fast-get children' %) ps (assoc path-params % p)) wilds)) + (if catch-all (-catch-all children' catch-all path-params p ps))))))))) (defn insert [root path data] (-insert (or root (segment)) (impl/segments path) (map->Match {:data data}))) diff --git a/perf-test/clj/reitit/go_perf_test.clj b/perf-test/clj/reitit/go_perf_test.clj index f29f78fc..88e546c3 100644 --- a/perf-test/clj/reitit/go_perf_test.clj +++ b/perf-test/clj/reitit/go_perf_test.clj @@ -2,8 +2,7 @@ (:require [criterium.core :as cc] [reitit.perf-utils :refer :all] [reitit.ring :as ring] - [clojure.string :as str] - [reitit.core :as r])) + [clojure.string :as str])) ;; ;; start repl with `lein perf repl` @@ -313,20 +312,23 @@ ;; 40ns (httprouter) ;; 140ns + ;; 120ns (faster decode params) (let [req (map->Req {:request-method :get, :uri "/user/repos"})] - (title "static") - (assert (= {:status 200, :body "/user/repos"} (app req))) - (cc/quick-bench (app req))) + (title "static") + (assert (= {:status 200, :body "/user/repos"} (app req))) + (cc/quick-bench (app req))) ;; 160ns (httprouter) ;; 990ns + ;; 830ns (faster decode params) (let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})] - (title "param") - (assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req))) - (cc/quick-bench (app req))) + (title "param") + (assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req))) + (cc/quick-bench (app req))) ;; 30µs (httprouter) ;; 190µs + ;; 160µs (faster decode params) (let [requests (mapv route->req routes)] (title "all") (cc/quick-bench diff --git a/perf-test/clj/reitit/impl_perf_test.clj b/perf-test/clj/reitit/impl_perf_test.clj index 1add9f05..bce2abc2 100644 --- a/perf-test/clj/reitit/impl_perf_test.clj +++ b/perf-test/clj/reitit/impl_perf_test.clj @@ -2,7 +2,8 @@ (:require [criterium.core :as cc] [reitit.perf-utils :refer :all] [ring.util.codec] - [reitit.impl]) + [reitit.impl] + [reitit.impl :as impl]) (:import (java.net URLDecoder URLEncoder))) ;; @@ -163,8 +164,30 @@ "1"]] (test! f s)))) +(defn url-encode-coll! [] + + (suite "url-encode-coll") + + ;; 740ns + (test "something to decode") + (test! impl/url-decode-coll + {:a "aja%20hiljaa+sillalla" + :b "aja_hiljaa_sillalla" + :c "1+1" + :d "1"}) + + ;; 124ns + ;; 50ns (maybe-map-values) + (test "nothing to decode") + (test! impl/url-decode-coll + {:a "aja+20hiljaa+sillalla" + :b "aja_hiljaa_sillalla" + :c "1+1" + :d "1"})) + (comment (url-decode!) (url-encode!) (form-decode!) - (form-encode!)) + (form-encode!) + (url-encode-coll!)) diff --git a/perf-test/clj/reitit/opensensors_perf_test.clj b/perf-test/clj/reitit/opensensors_perf_test.clj index 9ca7734c..62f07928 100644 --- a/perf-test/clj/reitit/opensensors_perf_test.clj +++ b/perf-test/clj/reitit/opensensors_perf_test.clj @@ -440,6 +440,7 @@ ;; 723ns (segment-router) ;; 702ns (before path-parameters) ;; 806ns (decode path-parameters) + ;; 735ns (maybe-map-values) (b! "reitit-ring" reitit-ring-f) ;; 2821ns diff --git a/perf-test/clj/reitit/prefix_tree_perf_test.clj b/perf-test/clj/reitit/prefix_tree_perf_test.clj index f920aadf..57d78175 100644 --- a/perf-test/clj/reitit/prefix_tree_perf_test.clj +++ b/perf-test/clj/reitit/prefix_tree_perf_test.clj @@ -80,39 +80,36 @@ (defn bench! [] - ;; 2.3ms - (cc/quick-bench - (dotimes [_ 1000] - (p/lookup pedestal-tree "/v1/orgs/1/topics"))) - - ;; 3.1ms - ;; 2.5ms (string equals) - ;; 2.5ms (protocol) - ;; 2.3ms (nil childs) - ;; 2.0ms (rando impros) - ;; 1.9ms (wild & catch shortcuts) - ;; 1.5ms (inline child fetching) - ;; 1.5ms (WildNode also backtracks) - ;; 1.4ms (precalculate segment-size) - ;; 1.3ms (fast-map) - ;; 1.3ms (dissoc wild & catch-all from children) - ;; 1.3ms (reified protocols) - ;; 0.8ms (flattened matching) - ;; 0.8ms (return route-data) - ;; 0.8ms (fix payloads) + ;; 2.3µs #_(cc/quick-bench - (dotimes [_ 1000] - (trie/lookup reitit-tree "/v1/orgs/1/topics" {}))) + (p/lookup pedestal-tree "/v1/orgs/1/topics")) - ;; 0.9ms (initial) - ;; 0.5ms (protocols) - ;; 1.0ms (with path params) - ;; 1.0ms (Match records) - ;; 0.63ms (Single sweep path params) - ;; 0.51ms (Cleanup) + ;; 3.1µs + ;; 2.5µs (string equals) + ;; 2.5µs (protocol) + ;; 2.3µs (nil childs) + ;; 2.0µs (rando impros) + ;; 1.9µs (wild & catch shortcuts) + ;; 1.5µs (inline child fetching) + ;; 1.5µs (WildNode also backtracks) + ;; 1.4µs (precalculate segment-size) + ;; 1.3µs (fast-map) + ;; 1.3µs (dissoc wild & catch-all from children) + ;; 1.3µs (reified protocols) + ;; 0.8µs (flattened matching) + ;; 0.8µs (return route-data) + ;; 0.8µs (fix payloads) + #_(cc/quick-bench + (trie/lookup reitit-tree "/v1/orgs/1/topics" {})) + + ;; 0.9µs (initial) + ;; 0.5µs (protocols) + ;; 1.0µs (with path paraµs) + ;; 1.0µs (Match records) + ;; 0.63µs (Single sweep path paraµs) + ;; 0.51µs (Cleanup) (cc/quick-bench - (dotimes [_ 1000] - (segment/lookup reitit-segment "/v1/orgs/1/topics")))) + (segment/lookup reitit-segment "/v1/orgs/1/topics"))) (comment (bench!))