mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 00:11:11 +00:00
Merge pull request #206 from metosin/bye-bye-beutiful
Back linear-router with linear scan on segments
This commit is contained in:
commit
d468d1f858
6 changed files with 40 additions and 47 deletions
|
|
@ -4,7 +4,8 @@
|
||||||
### `reitit-core`
|
### `reitit-core`
|
||||||
|
|
||||||
* `reitit.core/Expand` can be extended, fixes [#201](https://github.com/metosin/reitit/issues/201).
|
* `reitit.core/Expand` can be extended, fixes [#201](https://github.com/metosin/reitit/issues/201).
|
||||||
* new snappy Java-backed `SegmentTrie` routing algorithm and data structure, making wildcard routing ~2x faster on the JVM
|
* new snappy Java-backed `SegmentTrie` routing algorithm and data structure backing `reitit.core/segment-router`, making wildcard routing 2x faster on the JVM
|
||||||
|
* `reitit.core/linear-router` uses the segment router behind the scenes, 2-4x faster on the JVM too.
|
||||||
|
|
||||||
### `reitit-ring`
|
### `reitit-ring`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,10 @@ public class SegmentTrie {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Matcher scanner(List<Matcher> matchers) {
|
||||||
|
return new LinearMatcher(matchers);
|
||||||
|
}
|
||||||
|
|
||||||
public static Match lookup(Matcher matcher, String path) {
|
public static Match lookup(Matcher matcher, String path) {
|
||||||
return matcher.match(0, split(path), new Match());
|
return matcher.match(0, split(path), new Match());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
(mapv (comp vec (partial take 2)) routes))
|
(mapv (comp vec (partial take 2)) routes))
|
||||||
|
|
||||||
(defn route-info [route]
|
(defn route-info [route]
|
||||||
(select-keys (impl/create route) [:path :path-parts :path-params :result :data]))
|
(impl/create route))
|
||||||
|
|
||||||
(defprotocol Router
|
(defprotocol Router
|
||||||
(router-name [this])
|
(router-name [this])
|
||||||
|
|
@ -177,11 +177,12 @@
|
||||||
f #(if-let [path (impl/path-for route %)]
|
f #(if-let [path (impl/path-for route %)]
|
||||||
(->Match p data result (impl/url-decode-coll %) path)
|
(->Match p data result (impl/url-decode-coll %) path)
|
||||||
(->PartialMatch p data result % path-params))]
|
(->PartialMatch p data result % path-params))]
|
||||||
[(conj pl route)
|
[(conj pl (-> (segment/insert nil p (->Match p data result nil nil)) (segment/compile)))
|
||||||
(if name (assoc nl name f) nl)]))
|
(if name (assoc nl name f) nl)]))
|
||||||
[[] {}]
|
[[] {}]
|
||||||
compiled-routes)
|
compiled-routes)
|
||||||
lookup (impl/fast-map nl)
|
lookup (impl/fast-map nl)
|
||||||
|
scanner (segment/scanner pl)
|
||||||
routes (uncompile-routes compiled-routes)]
|
routes (uncompile-routes compiled-routes)]
|
||||||
^{:type ::router}
|
^{:type ::router}
|
||||||
(reify
|
(reify
|
||||||
|
|
@ -197,11 +198,10 @@
|
||||||
(route-names [_]
|
(route-names [_]
|
||||||
names)
|
names)
|
||||||
(match-by-path [_ path]
|
(match-by-path [_ path]
|
||||||
(reduce
|
(if-let [match (segment/lookup scanner path)]
|
||||||
(fn [_ ^Route route]
|
(-> (:data match)
|
||||||
(if-let [path-params ((:matcher route) path)]
|
(assoc :path-params (:path-params match))
|
||||||
(reduced (->Match (:path route) (:data route) (:result route) (impl/url-decode-coll path-params) path))))
|
(assoc :path path))))
|
||||||
nil pl))
|
|
||||||
(match-by-name [_ name]
|
(match-by-name [_ name]
|
||||||
(if-let [match (impl/fast-get lookup name)]
|
(if-let [match (impl/fast-get lookup name)]
|
||||||
(match nil)))
|
(match nil)))
|
||||||
|
|
|
||||||
|
|
@ -66,18 +66,16 @@
|
||||||
(let [key (keyword token)]
|
(let [key (keyword token)]
|
||||||
(-> out
|
(-> out
|
||||||
(update-in [:path-parts] conj key)
|
(update-in [:path-parts] conj key)
|
||||||
(update-in [:path-params] conj key)
|
(update-in [:path-params] conj key))))
|
||||||
(assoc-in [:path-constraints key] "([^/]+)"))))
|
|
||||||
#"^\*(.*)$" :>> (fn [[_ token]]
|
#"^\*(.*)$" :>> (fn [[_ token]]
|
||||||
(let [key (keyword token)]
|
(let [key (keyword token)]
|
||||||
(-> out
|
(-> out
|
||||||
(update-in [:path-parts] conj key)
|
(update-in [:path-parts] conj key)
|
||||||
(update-in [:path-params] conj key)
|
(update-in [:path-params] conj key))))
|
||||||
(assoc-in [:path-constraints key] "(.*)"))))
|
|
||||||
(update-in out [:path-parts] conj string)))
|
(update-in out [:path-parts] conj string)))
|
||||||
|
|
||||||
(defn- parse-path
|
(defn- parse-path
|
||||||
([pattern] (parse-path {:path-parts [] :path-params [] :path-constraints {}} pattern))
|
([pattern] (parse-path {:path-parts [] :path-params #{}} pattern))
|
||||||
([accumulated-info pattern]
|
([accumulated-info pattern]
|
||||||
(if-let [m (re-matches #"/(.*)" pattern)]
|
(if-let [m (re-matches #"/(.*)" pattern)]
|
||||||
(let [[_ path] m]
|
(let [[_ path] m]
|
||||||
|
|
@ -86,45 +84,21 @@
|
||||||
(str/split path #"/")))
|
(str/split path #"/")))
|
||||||
(throw (ex-info "Routes must start from the root, so they must begin with a '/'" {:pattern pattern})))))
|
(throw (ex-info "Routes must start from the root, so they must begin with a '/'" {:pattern pattern})))))
|
||||||
|
|
||||||
;; TODO: is this correct?
|
|
||||||
(defn- re-quote [x]
|
|
||||||
#?(:clj (Pattern/quote x)
|
|
||||||
:cljs (str/replace x #"([.?*+^$[\\]\\\\(){}|-])" "\\$1")))
|
|
||||||
|
|
||||||
(defn- path-regex [{:keys [path-parts path-constraints] :as route}]
|
|
||||||
(let [[pp & pps] path-parts
|
|
||||||
path-parts (if (and (seq pps) (string? pp) (empty? pp)) pps path-parts)]
|
|
||||||
(re-pattern
|
|
||||||
(apply str
|
|
||||||
(interleave (repeat "/")
|
|
||||||
(map #(or (get path-constraints %) (re-quote %))
|
|
||||||
path-parts))))))
|
|
||||||
|
|
||||||
(defn- path-matcher [route]
|
|
||||||
(let [{:keys [path-re path-params]} route]
|
|
||||||
(fn [path]
|
|
||||||
(when-let [m (re-matches path-re path)]
|
|
||||||
(zipmap path-params (rest m))))))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; Routing (c) Metosin
|
;; Routing (c) Metosin
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defrecord Route [path matcher path-parts path-params data result])
|
(defrecord Route [path path-parts path-params data result])
|
||||||
|
|
||||||
(defn create [[path data result]]
|
(defn create [[path data result]]
|
||||||
(let [path #?(:clj (.intern ^String path) :cljs path)]
|
(let [path #?(:clj (.intern ^String path) :cljs path)
|
||||||
(as-> (parse-path path) $
|
{:keys [path-parts path-params]} (parse-path path)]
|
||||||
(assoc $ :path-re (path-regex $))
|
(map->Route
|
||||||
(merge $ {:path path
|
{:path-params path-params
|
||||||
:matcher (if (contains-wilds? path)
|
:path-parts path-parts
|
||||||
(path-matcher $)
|
:path path
|
||||||
#(if (#?(:clj .equals, :cljs =) path %) {}))
|
:result result
|
||||||
:result result
|
:data data})))
|
||||||
:data data})
|
|
||||||
(dissoc $ :path-re :path-constraints)
|
|
||||||
(update $ :path-params set)
|
|
||||||
(map->Route $))))
|
|
||||||
|
|
||||||
(defn wild-route? [[path]]
|
(defn wild-route? [[path]]
|
||||||
(contains-wilds? path))
|
(contains-wilds? path))
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,14 @@
|
||||||
#?(:cljs trie
|
#?(:cljs trie
|
||||||
:clj (.matcher ^SegmentTrie (or trie (SegmentTrie.)))))
|
:clj (.matcher ^SegmentTrie (or trie (SegmentTrie.)))))
|
||||||
|
|
||||||
|
(defn scanner [compiled-tries]
|
||||||
|
"Returns a new compiled trie that does linear scan on the given compiled tries on [[lookup]]."
|
||||||
|
#?(:cljs (reify
|
||||||
|
Segment
|
||||||
|
(-lookup [_ ps params]
|
||||||
|
(some (fn [trie] (-lookup trie ps params)) compiled-tries)))
|
||||||
|
:clj (SegmentTrie/scanner compiled-tries)))
|
||||||
|
|
||||||
(defn lookup [trie path]
|
(defn lookup [trie path]
|
||||||
"Looks the path from a Segment Trie. Returns a [[Match]] or `nil`."
|
"Looks the path from a Segment Trie. Returns a [[Match]] or `nil`."
|
||||||
#?(:cljs (if-let [match (-lookup trie (impl/segments path) {})]
|
#?(:cljs (if-let [match (-lookup trie (impl/segments path) {})]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
[io.pedestal.http.route.definition.table :as table]
|
[io.pedestal.http.route.definition.table :as table]
|
||||||
[io.pedestal.http.route.map-tree :as map-tree]
|
[io.pedestal.http.route.map-tree :as map-tree]
|
||||||
[io.pedestal.http.route.router :as pedestal]))
|
[io.pedestal.http.route.router :as pedestal]
|
||||||
|
[reitit.core :as r]))
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; start repl with `lein perf repl`
|
;; start repl with `lein perf repl`
|
||||||
|
|
@ -551,6 +552,7 @@
|
||||||
router (reitit/router routes)
|
router (reitit/router routes)
|
||||||
reitit-f #(reitit/match-by-path router (:uri %))
|
reitit-f #(reitit/match-by-path router (:uri %))
|
||||||
reitit-ring-f (ring/ring-handler (ring/router opensensors-routes))
|
reitit-ring-f (ring/ring-handler (ring/router opensensors-routes))
|
||||||
|
reitit-ring-linear-f (ring/ring-handler (ring/router opensensors-routes {:router r/linear-router}))
|
||||||
reitit-ring-fast-f (ring/ring-handler (ring/router opensensors-routes) nil {:inject-router? false, :inject-match? false})
|
reitit-ring-fast-f (ring/ring-handler (ring/router opensensors-routes) nil {:inject-router? false, :inject-match? false})
|
||||||
bidi-f #(bidi/match-route opensensors-bidi-routes (:uri %))
|
bidi-f #(bidi/match-route opensensors-bidi-routes (:uri %))
|
||||||
calfpath-macros-f opensensors-calfpath-macro-handler
|
calfpath-macros-f opensensors-calfpath-macro-handler
|
||||||
|
|
@ -581,6 +583,10 @@
|
||||||
;; 385ns (java-segment-router, no injects)
|
;; 385ns (java-segment-router, no injects)
|
||||||
(b! "reitit-ring-fast" reitit-ring-fast-f)
|
(b! "reitit-ring-fast" reitit-ring-fast-f)
|
||||||
|
|
||||||
|
;; 2553ns (linear-router)
|
||||||
|
;; 630ns (segment-router-backed)
|
||||||
|
(b! "reitit-ring-linear" reitit-ring-linear-f)
|
||||||
|
|
||||||
;; 2137ns
|
;; 2137ns
|
||||||
(b! "calfpath-walker" calfpath-walker-f)
|
(b! "calfpath-walker" calfpath-walker-f)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue