From 7e5d10c0849a82f7873d8864fca834612a092b98 Mon Sep 17 00:00:00 2001 From: kloimhardt Date: Sun, 13 Dec 2020 22:47:42 +0100 Subject: [PATCH] Add features Ring, Reitit, Selmer (#672) * modified deps.edn * start adding ring middleware feature * project.clj * ring added * correct features * add ring response * add reitit * reitit namespace * replace ring's default-store with GraalVM compatible version * patch reitit's tri compiler * patch ring default and response * set ring and reitit flags in uberjar.bat * uberjar.bat again * set ring and reitit features true in uberjat.bat * amend project.clj * add ring.middleware.content-type * add webjars * add muuntaja * deps.edn * add http-response * extend response * add response content-type * remove wrap-reload * remove ring devel * add SELMER * add selmer/render * add ring.util.request namespace * Hint to to guestbook2 branch in README * Hint to guestbook2 branch * set exe to bb-web * set artifact name to babashka-web * set back version to 0.2.2 * set version to 0.2.3 * release version 0.2.2 * clean Readme * clean readme * replace babashka standard readme * list features in readme * extend readme * fix uberjar from merge * fix uberjar.bat from merge * readme * null change * null change 2 * null 3 * zero 4 * zero 5 * zero 6 * Zero 7 * zero 8 * prepare for merge to upstream/master * further prepare * prep * prep for pull * pull prep 3 * cosmetics * clean uberjar script * fix typo in deps.edn Co-authored-by: kloimhardt --- deps.edn | 8 + .../babashka/impl/muuntaja_core.clj | 10 + .../babashka/impl/muuntaja_middleware.clj | 10 + feature-reitit/babashka/impl/reitit_ring.clj | 13 + feature-reitit/reitit/trie.cljc | 452 ++++++++++++++++++ .../impl/ring_middleware_anti_forgery.clj | 13 + .../impl/ring_middleware_content_type.clj | 9 + .../impl/ring_middleware_defaults.clj | 15 + .../babashka/impl/ring_middleware_webjars.clj | 9 + .../babashka/impl/ring_util_http_response.clj | 12 + .../babashka/impl/ring_util_request.clj | 9 + .../babashka/impl/ring_util_response.clj | 11 + feature-ring/ring/middleware/defaults.clj | 117 +++++ feature-ring/ring/util/response.clj | 348 ++++++++++++++ .../babashka/impl/selmer_parser.clj | 11 + project.clj | 13 + script/uberjar | 21 + script/uberjar.bat | 18 + src/babashka/impl/features.clj | 3 + src/babashka/main.clj | 32 ++ 20 files changed, 1134 insertions(+) create mode 100644 feature-reitit/babashka/impl/muuntaja_core.clj create mode 100644 feature-reitit/babashka/impl/muuntaja_middleware.clj create mode 100644 feature-reitit/babashka/impl/reitit_ring.clj create mode 100644 feature-reitit/reitit/trie.cljc create mode 100644 feature-ring/babashka/impl/ring_middleware_anti_forgery.clj create mode 100644 feature-ring/babashka/impl/ring_middleware_content_type.clj create mode 100644 feature-ring/babashka/impl/ring_middleware_defaults.clj create mode 100644 feature-ring/babashka/impl/ring_middleware_webjars.clj create mode 100644 feature-ring/babashka/impl/ring_util_http_response.clj create mode 100644 feature-ring/babashka/impl/ring_util_request.clj create mode 100644 feature-ring/babashka/impl/ring_util_response.clj create mode 100644 feature-ring/ring/middleware/defaults.clj create mode 100644 feature-ring/ring/util/response.clj create mode 100644 feature-selmer/babashka/impl/selmer_parser.clj diff --git a/deps.edn b/deps.edn index c4de84d4..a10cbbef 100644 --- a/deps.edn +++ b/deps.edn @@ -2,6 +2,7 @@ "feature-yaml" "feature-csv" "feature-transit" "feature-java-time" "feature-java-nio" "feature-httpkit-client" "feature-httpkit-server" + "feature-ring" "feature-reitit" "feature-selmer" "feature-lanterna" "sci/src" "babashka.curl/src" "pods/src" "babashka.nrepl/src" @@ -20,6 +21,13 @@ clj-commons/clj-yaml {:mvn/version "0.7.2"} com.cognitect/transit-clj {:mvn/version "1.0.324"} nrepl/bencode {:mvn/version "1.1.0"} + ring/ring-core {:mvn/version "1.8.1"} + ring/ring-defaults {:mvn/version "0.3.2"} + ring-webjars {:mvn/version"0.2.0"} + metosin/reitit {:mvn/version "0.5.5"} + metosin/muuntaja {:mvn/version "0.6.7"} + metosin/ring-http-response {:mvn/version "0.9.1"} + selmer {:mvn/version "1.12.29"} seancorfield/next.jdbc {:mvn/version "1.1.610"} org.postgresql/postgresql {:mvn/version "42.2.18"} org.hsqldb/hsqldb {:mvn/version "2.5.1"} diff --git a/feature-reitit/babashka/impl/muuntaja_core.clj b/feature-reitit/babashka/impl/muuntaja_core.clj new file mode 100644 index 00000000..88cea444 --- /dev/null +++ b/feature-reitit/babashka/impl/muuntaja_core.clj @@ -0,0 +1,10 @@ +(ns babashka.impl.muuntaja-core + (:require [muuntaja.core :as m] + [sci.core :as sci :refer [copy-var]])) + +(def mns (sci/create-ns 'muuntaja.core nil)) + +(def muuntaja-core-namespace + {:obj mns + 'create (copy-var m/create mns) + 'default-options (copy-var m/default-options mns)}) diff --git a/feature-reitit/babashka/impl/muuntaja_middleware.clj b/feature-reitit/babashka/impl/muuntaja_middleware.clj new file mode 100644 index 00000000..682310dd --- /dev/null +++ b/feature-reitit/babashka/impl/muuntaja_middleware.clj @@ -0,0 +1,10 @@ +(ns babashka.impl.muuntaja-middleware + (:require [muuntaja.middleware :as m] + [sci.core :as sci :refer [copy-var]])) + +(def mns (sci/create-ns 'muuntaja.middleware nil)) + +(def muuntaja-middleware-namespace + {:obj mns + 'wrap-format (copy-var m/wrap-format mns) + 'wrap-params (copy-var m/wrap-params mns)}) diff --git a/feature-reitit/babashka/impl/reitit_ring.clj b/feature-reitit/babashka/impl/reitit_ring.clj new file mode 100644 index 00000000..3689235c --- /dev/null +++ b/feature-reitit/babashka/impl/reitit_ring.clj @@ -0,0 +1,13 @@ +(ns babashka.impl.reitit-ring + (:require [reitit.ring :as ring] + [sci.core :as sci :refer [copy-var]])) + +(def rns (sci/create-ns 'reitit.ring nil)) + +(def reitit-ring-namespace + {:obj rns + 'ring-handler (copy-var ring/ring-handler rns) + 'router (copy-var ring/router rns) + 'routes (copy-var ring/routes rns) + 'create-resource-handler (copy-var ring/create-resource-handler rns) + 'create-default-handler (copy-var ring/create-default-handler rns)}) diff --git a/feature-reitit/reitit/trie.cljc b/feature-reitit/reitit/trie.cljc new file mode 100644 index 00000000..940f9d7f --- /dev/null +++ b/feature-reitit/reitit/trie.cljc @@ -0,0 +1,452 @@ +(ns reitit.trie + (:refer-clojure :exclude [compile]) + (:require [clojure.string :as str] + [reitit.exception :as ex]) + #?(:clj (:import [reitit Trie Trie$Match Trie$Matcher] + (java.net URLDecoder)))) + +(defn ^:no-doc into-set [x] + (cond + (or (set? x) (sequential? x)) (set x) + (nil? x) #{} + :else (conj #{} x))) + +(defrecord Wild [value]) +(defrecord CatchAll [value]) +(defrecord Match [params data]) +(defrecord Node [children wilds catch-all params data]) + +(defn wild? [x] (instance? Wild x)) +(defn catch-all? [x] (instance? CatchAll x)) + +(defprotocol Matcher + (match [this i max path]) + (view [this]) + (depth ^long [this]) + (length [this])) + +(defprotocol TrieCompiler + (data-matcher [this params data]) + (static-matcher [this path matcher]) + (wild-matcher [this key end matcher]) + (catch-all-matcher [this key params data]) + (linear-matcher [this matchers ordered?]) + (-pretty [this matcher]) + (-path-matcher [this matcher])) + +(defn- assoc-param [match k v] + (let [params (:params match)] + (assoc match :params (assoc params k v)))) + +;; https://stackoverflow.com/questions/8033655/find-longest-common-prefix +(defn- common-prefix [s1 s2] + (let [max (min (count s1) (count s2))] + (loop [i 0] + (cond + ;; full match + (> i max) + (subs s1 0 max) + ;; partial match + (not= (get s1 i) (get s2 i)) + (if-not (zero? i) (subs s1 0 i)) + ;; recur + :else (recur (inc i)))))) + +(defn- -keyword [s] + (if-let [^long i (str/index-of s "/")] + (keyword (subs s 0 i) (subs s (inc i))) + (keyword s))) + +(defn split-path [s {:keys [syntax] :or {syntax #{:bracket :colon}}}] + (let [bracket? (-> syntax (into-set) :bracket) + colon? (-> syntax (into-set) :colon) + -static (fn [from to] (if-not (= from to) [(subs s from to)])) + -wild (fn [^long from to] [(->Wild (-keyword (subs s (inc from) to)))]) + -catch-all (fn [^long from to] [(->CatchAll (keyword (subs s (inc from) to)))])] + (loop [ss nil, from 0, to 0] + (if (= to (count s)) + (concat ss (-static from to)) + (let [c (get s to)] + (cond + + (and bracket? (= \{ c)) + (let [^long to' (or (str/index-of s "}" to) (ex/fail! ::unclosed-brackets {:path s}))] + (if (= \* (get s (inc to))) + (recur (concat ss (-static from to) (-catch-all (inc to) to')) (long (inc to')) (long (inc to'))) + (recur (concat ss (-static from to) (-wild to to')) (long (inc to')) (long (inc to'))))) + + (and colon? (= \: c)) + (let [^long to' (or (str/index-of s "/" to) (count s))] + (if (= 1 (- to' to)) + (recur ss from (inc to)) + (recur (concat ss (-static from to) (-wild to to')) (long to') (long to')))) + + (and colon? (= \* c)) + (let [to' (count s)] + (recur (concat ss (-static from to) (-catch-all to to')) (long to') (long to'))) + + :else + (recur ss from (inc to)))))))) + +(defn join-path [xs] + (reduce + (fn [s x] + (str s (cond + (string? x) x + (instance? Wild x) (str "{" (-> x :value str (subs 1)) "}") + (instance? CatchAll x) (str "{*" (-> x :value str (subs 1)) "}")))) + "" xs)) + +(defn normalize [s opts] + (-> s (split-path opts) (join-path))) + +;; +;; Conflict Resolution +;; + +(defn- -slice-start [[p1 :as p1s] [p2 :as p2s]] + (let [-split (fn [p] + (if-let [i (and p (str/index-of p "/"))] + [(subs p 0 i) (subs p i)] + [p])) + -slash (fn [cp p] + (cond + (not (string? cp)) [cp] + (and (string? cp) (not= (count cp) (count p))) [(subs p (count cp))] + (and (string? p) (not cp)) (-split p))) + -postcut (fn [[p :as pps]] + (let [^long i (and p (str/index-of p "/"))] + (if (and i (pos? i)) + (concat [(subs p 0 i) (subs p i)] (rest pps)) + pps))) + -tailcut (fn [cp [p :as ps]] (concat (-slash cp p) (rest ps)))] + (if (or (nil? p1) (nil? p2)) + [(-postcut p1s) (-postcut p2s)] + (if-let [cp (and (string? p1) (string? p2) (common-prefix p1 p2))] + [(-tailcut cp p1s) (-tailcut cp p2s)] + [p1s p2s])))) + +(defn- -slice-end [x xs] + (let [i (if (string? x) (str/index-of x "/"))] + (if (and (number? i) (pos? ^long i)) + (concat [(subs x i)] xs) + xs))) + +(defn conflicting-parts? [parts1 parts2] + (let [[[s1 & ss1] [s2 & ss2]] (-slice-start parts1 parts2)] + (cond + (= s1 s2 nil) true + (or (nil? s1) (nil? s2)) false + (or (catch-all? s1) (catch-all? s2)) true + (or (wild? s1) (wild? s2)) (recur (-slice-end s1 ss1) (-slice-end s2 ss2)) + (not= s1 s2) false + :else (recur ss1 ss2)))) + +(defn conflicting-paths? [path1 path2 opts] + (conflicting-parts? (split-path path1 opts) (split-path path2 opts))) + +;; +;; Creating Tries +;; + +(defn- -node [m] + (map->Node (merge {:children {}, :wilds {}, :catch-all {}, :params {}} m))) + +(defn- -insert [node [path & ps] fp params data] + (let [node' (cond + + (nil? path) + (assoc node :data data :params params) + + (instance? Wild path) + (let [next (first ps)] + (if (or (instance? Wild next) (instance? CatchAll next)) + (ex/fail! ::following-parameters {:path fp, :parameters (map :value [path next])}) + (update-in node [:wilds path] (fn [n] (-insert (or n (-node {})) ps fp params data))))) + + (instance? CatchAll path) + (assoc-in node [:catch-all path] (-node {:params params, :data data})) + + (str/blank? path) + (-insert node ps fp params data) + + :else + (or + (reduce + (fn [_ [p n]] + (if-let [cp (common-prefix p path)] + (if (= cp p) + ;; insert into child node + (let [n' (-insert n (conj ps (subs path (count p))) fp params data)] + (reduced (assoc-in node [:children p] n'))) + ;; split child node + (let [rp (subs p (count cp)) + rp' (subs path (count cp)) + n' (-insert (-node {}) ps fp params data) + n'' (-insert (-node {:children {rp n, rp' n'}}) nil nil nil nil)] + (reduced (update node :children (fn [children] + (-> children + (dissoc p) + (assoc cp n''))))))))) + nil (:children node)) + ;; new child node + (assoc-in node [:children path] (-insert (-node {}) ps fp params data))))] + (if-let [child (get-in node' [:children ""])] + ;; optimize by removing empty paths + (-> (merge-with merge (dissoc node' :data) child) + (update :children dissoc "")) + node'))) + +(defn- decode [path start end percent?] + (let [param (subs path start end)] + (if percent? + #?(:cljs (js/decodeURIComponent param) + :clj (URLDecoder/decode + (if (.contains ^String param "+") + (.replace ^String param "+" "%2B") + param) + "UTF-8")) + param))) + +;; +;; Compilers +;; + +(defn clojure-trie-compiler [] + (reify + TrieCompiler + (data-matcher [_ params data] + (let [match (->Match params data)] + (reify Matcher + (match [_ i max _] + (if (= i max) + match)) + (view [_] data) + (depth [_] 1) + (length [_])))) + (static-matcher [_ path matcher] + (let [size (count path)] + (reify Matcher + (match [_ i max p] + (if-not (< ^long max (+ ^long i size)) + (loop [j 0] + (if (= j size) + (match matcher (+ ^long i size) max p) + (if (= (get p (+ ^long i j)) (get path j)) + (recur (inc j))))))) + (view [_] [path (view matcher)]) + (depth [_] (inc (depth matcher))) + (length [_] (count path))))) + (wild-matcher [_ key end matcher] + (reify Matcher + (match [_ i max path] + (if (and (< ^long i ^long max) (not= (get path i) end)) + (loop [percent? false, j ^long i] + (if (= max j) + (if-let [match (match matcher max max path)] + (assoc-param match key (decode path i max percent?))) + (let [c ^char (get path j)] + (condp = c + end (if-let [match (match matcher j max path)] + (assoc-param match key (decode path i j percent?))) + \% (recur true (inc j)) + (recur percent? (inc j)))))))) + (view [_] [key (view matcher)]) + (depth [_] (inc (depth matcher))) + (length [_]))) + (catch-all-matcher [_ key params data] + (let [match (->Match params data)] + (reify Matcher + (match [_ i max path] + (if (<= ^long i ^long max) (assoc-param match key (decode path i max true)))) + (view [_] [key [data]]) + (depth [_] 1) + (length [_])))) + (linear-matcher [_ matchers ordered?] + (let [matchers (vec (if ordered? matchers (reverse (sort-by (juxt depth length) matchers)))) + size (count matchers)] + (reify Matcher + (match [_ i max path] + (loop [j 0] + (if (< j size) + (or (match (get matchers j) i max path) + (recur (inc j)))))) + (view [_] (mapv view matchers)) + (depth [_] (inc ^long (apply max 0 (map depth matchers)))) + (length [_])))) + (-pretty [_ matcher] + (view matcher)) + (-path-matcher [_ matcher] + (fn [path] + (if-let [match (match matcher 0 (count path) path)] + (->Match (:params match) (:data match))))))) + +#?(:clj + (defn java-trie-compiler [] + (reify + TrieCompiler + (data-matcher [_ params data] + (Trie/dataMatcher params data)) + (static-matcher [_ path matcher] + (Trie/staticMatcher ^String path ^Trie$Matcher matcher)) + (wild-matcher [_ key end matcher] + (Trie/wildMatcher key (if end (Character. end)) matcher)) + (catch-all-matcher [_ key params data] + (Trie/catchAllMatcher key params data)) + (linear-matcher [_ matchers ordered?] + (Trie/linearMatcher matchers ordered?)) + (-pretty [_ matcher] + (-> matcher str read-string eval)) + (-path-matcher [_ matcher] + (fn [path] + (if-let [match ^Trie$Match (Trie/lookup ^Trie$Matcher matcher ^String path)] + (->Match (.params match) (.data match)))))))) + +;; +;; Managing Tries +;; + +(defn- map-parameters [keys] + (zipmap keys (repeat nil))) + +#?(:clj + (def record-parameters + "Memoized function to transform parameters into runtime generated Record." + (memoize + (fn [keys] + (if (some qualified-keyword? keys) + (map-parameters keys) + (let [sym (gensym "PathParams") + ctor (symbol (str "map->" sym))] + (binding [*ns* (find-ns 'user)] + (eval `(do (defrecord ~sym ~(mapv (comp symbol name) keys)) (~ctor {})))))))))) + +(defn insert + "Returns a trie with routes added to it." + ([routes] + (insert nil routes)) + ([node routes] + (reduce + (fn [acc [p d]] + (insert acc p d)) + node routes)) + ([node path data] + (insert node path data nil)) + ([node path data {::keys [parameters] :or {parameters map-parameters} :as opts}] + (let [parts (split-path path opts) + params (parameters (->> parts (remove string?) (map :value)))] + (-insert (or node (-node {})) (split-path path opts) path params data)))) + +(defn babashka-message [& _] + (println "Reitit compiler not supported by Babashka")) + +(def compiler babashka-message) + +(def compile babashka-message) + +#_(defn compiler + "Returns a default [[TrieCompiler]]." + [] + #?(:cljs (clojure-trie-compiler) + :clj (java-trie-compiler))) + +#_(defn compile + "Returns a compiled trie, to be used with [[pretty]] or [[path-matcher]]." + ([options] + (compile options (compiler))) + ([options compiler] + (compile options compiler [])) + ([{:keys [data params children wilds catch-all] :or {params {}}} compiler cp] + (let [ends (fn [{:keys [children]}] (or (keys children) ["/"])) + matchers (-> [] + (cond-> data (conj (data-matcher compiler params data))) + (into (for [[p c] children] (static-matcher compiler p (compile c compiler (conj cp p))))) + (into + (for [[p c] wilds] + (let [pv (:value p) + ends (ends c)] + (if (next ends) + (ex/fail! ::multiple-terminators {:terminators ends, :path (join-path (conj cp p))}) + (wild-matcher compiler pv (ffirst ends) (compile c compiler (conj cp pv))))))) + (into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))] + (cond + (> (count matchers) 1) (linear-matcher compiler matchers false) + (= (count matchers) 1) (first matchers) + :else (data-matcher compiler {} nil))))) + +(defn pretty + "Returns a simplified EDN structure of a compiled trie for printing purposes." + ([compiled-trie] + (pretty compiled-trie (compiler))) + ([compiled-trie compiler] + (-pretty compiler compiled-trie))) + +(defn path-matcher + "Returns a function of `path -> Match` from a compiled trie." + ([compiled-trie] + (path-matcher compiled-trie (compiler))) + ([compiled-trie compiler] + (-path-matcher compiler compiled-trie))) + +;; +;; spike +;; + +(comment + (-> + [["/v2/whoami" 1] + ["/v2/users/:user-id/datasets" 2] + ["/v2/public/projects/:project-id/datasets" 3] + ["/v1/public/topics/:topic" 4] + ["/v1/users/:user-id/orgs/:org-id" 5] + ["/v1/search/topics/:term" 6] + ["/v1/users/:user-id/invitations" 7] + ["/v1/users/:user-id/topics" 9] + ["/v1/users/:user-id/bookmarks/followers" 10] + ["/v2/datasets/:dataset-id" 11] + ["/v1/orgs/:org-id/usage-stats" 12] + ["/v1/orgs/:org-id/devices/:client-id" 13] + ["/v1/messages/user/:user-id" 14] + ["/v1/users/:user-id/devices" 15] + ["/v1/public/users/:user-id" 16] + ["/v1/orgs/:org-id/errors" 17] + ["/v1/public/orgs/:org-id" 18] + ["/v1/orgs/:org-id/invitations" 19] + ["/v1/users/:user-id/device-errors" 22] + ["/v2/login" 23] + ["/v1/users/:user-id/usage-stats" 24] + ["/v2/users/:user-id/devices" 25] + ["/v1/users/:user-id/claim-device/:client-id" 26] + ["/v2/public/projects/:project-id" 27] + ["/v2/public/datasets/:dataset-id" 28] + ["/v2/users/:user-id/topics/bulk" 29] + ["/v1/messages/device/:client-id" 30] + ["/v1/users/:user-id/owned-orgs" 31] + ["/v1/topics/:topic" 32] + ["/v1/users/:user-id/bookmark/:topic" 33] + ["/v1/orgs/:org-id/members/:user-id" 34] + ["/v1/users/:user-id/devices/:client-id" 35] + ["/v1/users/:user-id" 36] + ["/v1/orgs/:org-id/devices" 37] + ["/v1/orgs/:org-id/members" 38] + ["/v2/orgs/:org-id/topics" 40] + ["/v1/whoami" 41] + ["/v1/orgs/:org-id" 42] + ["/v1/users/:user-id/api-key" 43] + ["/v2/schemas" 44] + ["/v2/users/:user-id/topics" 45] + ["/v1/orgs/:org-id/confirm-membership/:token" 46] + ["/v2/topics/:topic" 47] + ["/v1/messages/topic/:topic" 48] + ["/v1/users/:user-id/devices/:client-id/reset-password" 49] + ["/v2/topics" 50] + ["/v1/login" 51] + ["/v1/users/:user-id/orgs" 52] + ["/v2/public/messages/dataset/:dataset-id" 53] + ["/v1/topics" 54] + ["/v1/orgs" 55] + ["/v1/users/:user-id/bookmarks" 56] + ["/v1/orgs/:org-id/topics" 57]] + (insert) + (compile) + (pretty))) diff --git a/feature-ring/babashka/impl/ring_middleware_anti_forgery.clj b/feature-ring/babashka/impl/ring_middleware_anti_forgery.clj new file mode 100644 index 00000000..1b1dba5d --- /dev/null +++ b/feature-ring/babashka/impl/ring_middleware_anti_forgery.clj @@ -0,0 +1,13 @@ +(ns babashka.impl.ring-middleware-anti-forgery + (:require [ring.middleware.anti-forgery :as anti-forgery] + [sci.core :as sci :refer [copy-var]])) + +(def ans (sci/create-ns 'ring.middleware.anti-forgery nil)) + +(defn get-anti-forgery-token [] + anti-forgery/*anti-forgery-token*) + +(def ring-middleware-anti-forgery-namespace + {:obj ans + 'wrap-anti-forgery (copy-var anti-forgery/wrap-anti-forgery ans) + 'get-anti-forgery-token (copy-var get-anti-forgery-token ans)}) diff --git a/feature-ring/babashka/impl/ring_middleware_content_type.clj b/feature-ring/babashka/impl/ring_middleware_content_type.clj new file mode 100644 index 00000000..f57287e9 --- /dev/null +++ b/feature-ring/babashka/impl/ring_middleware_content_type.clj @@ -0,0 +1,9 @@ +(ns babashka.impl.ring-middleware-content-type + (:require [ring.middleware.content-type :as content-type] + [sci.core :as sci :refer [copy-var]])) + +(def cns (sci/create-ns 'ring.middleware.content-type nil)) + +(def ring-middleware-content-type-namespace + {:obj cns + 'wrap-content-type (copy-var content-type/wrap-content-type cns)}) diff --git a/feature-ring/babashka/impl/ring_middleware_defaults.clj b/feature-ring/babashka/impl/ring_middleware_defaults.clj new file mode 100644 index 00000000..59d3111c --- /dev/null +++ b/feature-ring/babashka/impl/ring_middleware_defaults.clj @@ -0,0 +1,15 @@ +(ns babashka.impl.ring-middleware-defaults + (:require [ring.middleware.defaults :as defaults] + [ring.middleware.multipart-params] + [ring.middleware.multipart-params.temp-file] + [sci.core :as sci :refer [copy-var]])) + +(alter-var-root #'ring.middleware.multipart-params/default-store (constantly (delay ring.middleware.multipart-params.temp-file/temp-file-store))) + +(def dns (sci/create-ns 'ring.middleware.defaults nil)) + +(def ring-middleware-defaults-namespace + {:obj dns + 'wrap-defaults (copy-var defaults/wrap-defaults dns) + 'api-defaults (copy-var defaults/api-defaults dns) + 'site-defaults (copy-var defaults/site-defaults dns)}) diff --git a/feature-ring/babashka/impl/ring_middleware_webjars.clj b/feature-ring/babashka/impl/ring_middleware_webjars.clj new file mode 100644 index 00000000..b8dddcbf --- /dev/null +++ b/feature-ring/babashka/impl/ring_middleware_webjars.clj @@ -0,0 +1,9 @@ +(ns babashka.impl.ring-middleware-webjars + (:require [ring.middleware.webjars :as webjars] + [sci.core :as sci :refer [copy-var]])) + +(def wns (sci/create-ns 'ring.middleware.webjars nil)) + +(def ring-middleware-webjars-namespace + {:obj wns + 'wrap-webjars (copy-var webjars/wrap-webjars wns)}) diff --git a/feature-ring/babashka/impl/ring_util_http_response.clj b/feature-ring/babashka/impl/ring_util_http_response.clj new file mode 100644 index 00000000..9341a6b4 --- /dev/null +++ b/feature-ring/babashka/impl/ring_util_http_response.clj @@ -0,0 +1,12 @@ +(ns babashka.impl.ring-util-http-response + (:require [ring.util.http-response :as http-response] + [sci.core :as sci :refer [copy-var]])) + +(def hns (sci/create-ns 'ring.util.http-response nil)) + +(def ring-util-http-response-namespace + {:obj hns + 'ok (copy-var http-response/ok hns) + 'content-type (copy-var http-response/content-type hns) + 'bad-request (copy-var http-response/bad-request hns) + 'internal-server-error (copy-var http-response/internal-server-error hns)}) diff --git a/feature-ring/babashka/impl/ring_util_request.clj b/feature-ring/babashka/impl/ring_util_request.clj new file mode 100644 index 00000000..68f4411e --- /dev/null +++ b/feature-ring/babashka/impl/ring_util_request.clj @@ -0,0 +1,9 @@ +(ns babashka.impl.ring-util-request + (:require [ring.util.request :as request] + [sci.core :as sci :refer [copy-var]])) + +(def rns (sci/create-ns 'ring.util.request nil)) + +(def ring-util-request-namespace + {:obj rns + 'body-string (copy-var request/body-string rns)}) diff --git a/feature-ring/babashka/impl/ring_util_response.clj b/feature-ring/babashka/impl/ring_util_response.clj new file mode 100644 index 00000000..60d25c4e --- /dev/null +++ b/feature-ring/babashka/impl/ring_util_response.clj @@ -0,0 +1,11 @@ +(ns babashka.impl.ring-util-response + (:require [ring.util.response :as response] + [sci.core :as sci :refer [copy-var]])) + +(def rns (sci/create-ns 'ring.util.response nil)) + +(def ring-util-response-namespace + {:obj rns + 'response (copy-var response/response rns) + 'resource-data (copy-var response/resource-data rns) + 'header (copy-var response/header rns)}) diff --git a/feature-ring/ring/middleware/defaults.clj b/feature-ring/ring/middleware/defaults.clj new file mode 100644 index 00000000..a570308c --- /dev/null +++ b/feature-ring/ring/middleware/defaults.clj @@ -0,0 +1,117 @@ +(ns ring.middleware.defaults + "Middleware for providing a handler with sensible defaults." + (:require [ring.middleware.x-headers :as x] + [ring.middleware.flash :refer [wrap-flash]] + [ring.middleware.session :refer [wrap-session]] + [ring.middleware.keyword-params :refer [wrap-keyword-params]] + [ring.middleware.nested-params :refer [wrap-nested-params]] + [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] + [ring.middleware.multipart-params :refer [wrap-multipart-params]] + [ring.middleware.params :refer [wrap-params]] + [ring.middleware.cookies :refer [wrap-cookies]] + [ring.middleware.resource :refer [wrap-resource]] + [ring.middleware.file :refer [wrap-file]] + [ring.middleware.not-modified :refer [wrap-not-modified]] + [ring.middleware.content-type :refer [wrap-content-type]] + [ring.middleware.default-charset :refer [wrap-default-charset]] + [ring.middleware.absolute-redirects :refer [wrap-absolute-redirects]] + [ring.middleware.ssl :refer [wrap-ssl-redirect wrap-hsts wrap-forwarded-scheme]] + [ring.middleware.proxy-headers :refer [wrap-forwarded-remote-addr]])) + +(def api-defaults + "A default configuration for a HTTP API." + {:params {:urlencoded true + :keywordize true} + :responses {:not-modified-responses true + :absolute-redirects true + :content-types true + :default-charset "utf-8"}}) + +(def secure-api-defaults + "A default configuration for a HTTP API that's accessed securely over HTTPS." + (-> api-defaults + (assoc-in [:security :ssl-redirect] true) + (assoc-in [:security :hsts] true))) + +(def site-defaults + "A default configuration for a browser-accessible website, based on current + best practice." + {:params {:urlencoded true + :multipart true + :nested true + :keywordize true} + :cookies true + :session {:flash true + :cookie-attrs {:http-only true, :same-site :strict}} + :security {:anti-forgery true + :xss-protection {:enable? true, :mode :block} + :frame-options :sameorigin + :content-type-options :nosniff} + :static {:resources "public"} + :responses {:not-modified-responses true + :absolute-redirects true + :content-types true + :default-charset "utf-8"}}) + +(def secure-site-defaults + "A default configuration for a browser-accessible website that's accessed + securely over HTTPS." + (-> site-defaults + (assoc-in [:session :cookie-attrs :secure] true) + (assoc-in [:session :cookie-name] "secure-ring-session") + (assoc-in [:security :ssl-redirect] true) + (assoc-in [:security :hsts] true))) + +(defn- wrap [handler middleware options] + (if (true? options) + (middleware handler) + (if options + (middleware handler options) + handler))) + +(defn- wrap-multi [handler middleware args] + (wrap handler + (fn [handler args] + (if (coll? args) + (reduce middleware handler args) + (middleware handler args))) + args)) + +(defn- wrap-xss-protection [handler options] + (x/wrap-xss-protection handler (:enable? options true) (dissoc options :enable?))) + +(defn- wrap-x-headers [handler options] + (-> handler + (wrap wrap-xss-protection (:xss-protection options false)) + (wrap x/wrap-frame-options (:frame-options options false)) + (wrap x/wrap-content-type-options (:content-type-options options false)))) + +(defn wrap-defaults + "Wraps a handler in default Ring middleware, as specified by the supplied + configuration map. + + See: api-defaults + site-defaults + secure-api-defaults + secure-site-defaults" + [handler config] + (-> handler + (wrap wrap-anti-forgery (get-in config [:security :anti-forgery] false)) + (wrap wrap-flash (get-in config [:session :flash] false)) + (wrap wrap-session (:session config false)) + (wrap wrap-keyword-params (get-in config [:params :keywordize] false)) + (wrap wrap-nested-params (get-in config [:params :nested] false)) + (wrap wrap-multipart-params (get-in config [:params :multipart] false)) + (wrap wrap-params (get-in config [:params :urlencoded] false)) + (wrap wrap-cookies (get-in config [:cookies] false)) + (wrap wrap-absolute-redirects (get-in config [:responses :absolute-redirects] false)) + (wrap-multi #(wrap-resource %1 %2 config) (get-in config [:static :resources] false)) + (wrap-multi wrap-file (get-in config [:static :files] false)) + (wrap wrap-content-type (get-in config [:responses :content-types] false)) + (wrap wrap-default-charset (get-in config [:responses :default-charset] false)) + (wrap wrap-not-modified (get-in config [:responses :not-modified-responses] false)) + (wrap wrap-x-headers (:security config)) + (wrap wrap-hsts (get-in config [:security :hsts] false)) + (wrap wrap-ssl-redirect (get-in config [:security :ssl-redirect] false)) + (wrap wrap-forwarded-scheme (boolean (:proxy config))) + (wrap wrap-forwarded-remote-addr (boolean (:proxy config))))) diff --git a/feature-ring/ring/util/response.clj b/feature-ring/ring/util/response.clj new file mode 100644 index 00000000..171f5259 --- /dev/null +++ b/feature-ring/ring/util/response.clj @@ -0,0 +1,348 @@ +(ns ring.util.response + "Functions for generating and augmenting response maps." + (:require [clojure.java.io :as io] + [clojure.string :as str] + [ring.util.io :refer [last-modified-date]] + [ring.util.parsing :as parsing] + [ring.util.time :refer [format-date]]) + (:import [java.io File] + [java.util Date] + [java.net URL URLDecoder URLEncoder])) + +(def ^{:added "1.4"} redirect-status-codes + "Map a keyword to a redirect status code." + {:moved-permanently 301 + :found 302 + :see-other 303 + :temporary-redirect 307 + :permanent-redirect 308}) + +(defn redirect + "Returns a Ring response for an HTTP 302 redirect. Status may be + a key in redirect-status-codes or a numeric code. Defaults to 302" + ([url] (redirect url :found)) + ([url status] + {:status (redirect-status-codes status status) + :headers {"Location" url} + :body ""})) + +(defn redirect-after-post + "Returns a Ring response for an HTTP 303 redirect. Deprecated in favor + of using redirect with a :see-other status." + {:deprecated "1.4"} + [url] + {:status 303 + :headers {"Location" url} + :body ""}) + +(defn created + "Returns a Ring response for a HTTP 201 created response." + {:added "1.2"} + ([url] (created url nil)) + ([url body] + {:status 201 + :headers {"Location" url} + :body body})) + +(defn bad-request + "Returns a 400 'bad request' response." + {:added "1.7"} + [body] + {:status 400 + :headers {} + :body body}) + +(defn not-found + "Returns a 404 'not found' response." + {:added "1.1"} + [body] + {:status 404 + :headers {} + :body body}) + +(defn response + "Returns a skeletal Ring response with the given body, status of 200, and no + headers." + [body] + {:status 200 + :headers {} + :body body}) + +(defn status + "Returns an updated Ring response with the given status." + ([status] + {:status status + :headers {} + :body nil}) + ([resp status] + (assoc resp :status status))) + +(defn header + "Returns an updated Ring response with the specified header added." + [resp name value] + (assoc-in resp [:headers name] (str value))) + +(defn- canonical-path ^String [^File file] + (str (.getCanonicalPath file) + (if (.isDirectory file) File/separatorChar))) + +(defn- safe-path? [^String root ^String path] + (.startsWith (canonical-path (File. root path)) + (canonical-path (File. root)))) + +(defn- directory-transversal? + "Check if a path contains '..'." + [^String path] + (-> (str/split path #"/|\\") + (set) + (contains? ".."))) + +(defn- find-file-named [^File dir ^String filename] + (let [path (File. dir filename)] + (if (.isFile path) + path))) + +(defn- find-file-starting-with [^File dir ^String prefix] + (first + (filter + #(.startsWith (.toLowerCase (.getName ^File %)) prefix) + (.listFiles dir)))) + +(defn- find-index-file + "Search the directory for an index file." + [^File dir] + (or (find-file-named dir "index.html") + (find-file-named dir "index.htm") + (find-file-starting-with dir "index."))) + +(defn- safely-find-file [^String path opts] + (if-let [^String root (:root opts)] + (if (or (safe-path? root path) + (and (:allow-symlinks? opts) (not (directory-transversal? path)))) + (File. root path)) + (File. path))) + +(defn- find-file [^String path opts] + (if-let [^File file (safely-find-file path opts)] + (cond + (.isDirectory file) + (and (:index-files? opts true) (find-index-file file)) + (.exists file) + file))) + +(defn- file-data [^File file] + {:content file + :content-length (.length file) + :last-modified (last-modified-date file)}) + +(defn- content-length [resp len] + (if len + (header resp "Content-Length" len) + resp)) + +(defn- last-modified [resp last-mod] + (if last-mod + (header resp "Last-Modified" (format-date last-mod)) + resp)) + +(defn file-response + "Returns a Ring response to serve a static file, or nil if an appropriate + file does not exist. + Options: + :root - take the filepath relative to this root path + :index-files? - look for index.* files in directories (defaults to true) + :allow-symlinks? - allow symlinks that lead to paths outside the root path + (defaults to false)" + ([filepath] + (file-response filepath {})) + ([filepath options] + (if-let [file (find-file filepath options)] + (let [data (file-data file)] + (-> (response (:content data)) + (content-length (:content-length data)) + (last-modified (:last-modified data))))))) + +;; In Clojure 1.5.1, the as-file function does not correctly decode +;; UTF-8 byte sequences. +;; +;; See: http://dev.clojure.org/jira/browse/CLJ-1177 +;; +;; As a work-around, we'll backport the fix from CLJ-1177 into +;; url-as-file. + +(defn- ^File url-as-file [^java.net.URL u] + (-> (.getFile u) + (str/replace \/ File/separatorChar) + (str/replace "+" (URLEncoder/encode "+" "UTF-8")) + (URLDecoder/decode "UTF-8") + io/as-file)) + +(defn content-type + "Returns an updated Ring response with the a Content-Type header corresponding + to the given content-type." + [resp content-type] + (header resp "Content-Type" content-type)) + +(defn find-header + "Looks up a header in a Ring response (or request) case insensitively, + returning the header map entry, or nil if not present." + {:added "1.4"} + [resp ^String header-name] + (->> (:headers resp) + (filter #(.equalsIgnoreCase header-name (key %))) + (first))) + +(defn get-header + "Looks up a header in a Ring response (or request) case insensitively, + returning the value of the header, or nil if not present." + {:added "1.2"} + [resp header-name] + (some-> resp (find-header header-name) val)) + +(defn update-header + "Looks up a header in a Ring response (or request) case insensitively, + then updates the header with the supplied function and arguments in the + manner of update-in." + {:added "1.4"} + [resp header-name f & args] + (let [header-key (or (some-> resp (find-header header-name) key) header-name)] + (update-in resp [:headers header-key] #(apply f % args)))) + +(defn charset + "Returns an updated Ring response with the supplied charset added to the + Content-Type header." + {:added "1.1"} + [resp charset] + (update-header resp "Content-Type" + (fn [content-type] + (-> (or content-type "text/plain") + (str/replace #";\s*charset=[^;]*" "") + (str "; charset=" charset))))) + +(defn get-charset + "Gets the character encoding of a Ring response." + {:added "1.6"} + [resp] + (some-> (get-header resp "Content-Type") + parsing/find-content-type-charset)) + +(defn set-cookie + "Sets a cookie on the response. Requires the handler to be wrapped in the + wrap-cookies middleware." + {:added "1.1"} + [resp name value & [opts]] + (assoc-in resp [:cookies name] (merge {:value value} opts))) + +(defn response? + "True if the supplied value is a valid response map." + {:added "1.1"} + [resp] + (and (map? resp) + (integer? (:status resp)) + (map? (:headers resp)))) + +(defmulti resource-data + "Returns data about the resource specified by url, or nil if an + appropriate resource does not exist. + + The return value is a map with optional values for: + :content - the content of the URL, suitable for use as the :body + of a ring response + :content-length - the length of the :content, nil if not available + :last-modified - the Date the :content was last modified, nil if not + available + + This dispatches on the protocol of the URL as a keyword, and + implementations are provided for :file and :jar. If you are on a + platform where (Class/getResource) returns URLs with a different + protocol, you will need to provide an implementation for that + protocol. + + This function is used internally by url-response." + {:arglists '([url]), :added "1.4"} + (fn [^java.net.URL url] + (keyword (.getProtocol url)))) + +(defmethod resource-data :file + [url] + (if-let [file (url-as-file url)] + (if-not (.isDirectory file) + (file-data file)))) + +(defn- add-ending-slash [^String path] + (if (.endsWith path "/") + path + (str path "/"))) + +(defn- jar-directory? [^java.net.JarURLConnection conn] + (let [jar-file (.getJarFile conn) + entry-name (.getEntryName conn) + dir-entry (.getEntry jar-file (add-ending-slash entry-name))] + (and dir-entry (.isDirectory dir-entry)))) + +(defn- connection-content-length [^java.net.URLConnection conn] + (let [len (.getContentLength conn)] + (if (<= 0 len) len))) + +(defn- connection-last-modified [^java.net.URLConnection conn] + (let [last-mod (.getLastModified conn)] + (if-not (zero? last-mod) + (Date. last-mod)))) + +(defmethod resource-data :jar + [^java.net.URL url] + (let [conn (.openConnection url)] + (if-not (jar-directory? conn) + {:content (.getInputStream conn) + :content-length (connection-content-length conn) + :last-modified (connection-last-modified conn)}))) + +(defn url-response + "Return a response for the supplied URL." + {:added "1.2"} + [^URL url] + (if-let [data (resource-data url)] + (-> (response (:content data)) + (content-length (:content-length data)) + (last-modified (:last-modified data))))) + +(defn- get-resources [path ^ClassLoader loader] + (-> (or loader (.getContextClassLoader (Thread/currentThread))) + (.getResources path) + (enumeration-seq))) + +(defn- safe-file-resource? [{:keys [body]} {:keys [root loader allow-symlinks?]}] + (or allow-symlinks? + (nil? root) + (let [root (.replaceAll (str root) "^/" "")] + (or (str/blank? root) + (let [path (canonical-path body)] + (some #(and (= "file" (.getProtocol ^URL %)) + (.startsWith path (canonical-path (url-as-file %)))) + (get-resources root loader))))))) + +(defn resource-response + "Returns a Ring response to serve a packaged resource, or nil if the + resource does not exist. + Options: + :root - take the resource relative to this root + :loader - resolve the resource in this class loader + :allow-symlinks? - allow symlinks that lead to paths outside the root + classpath directories (defaults to false)" + ([path] + (resource-response path {})) + ([path options] + (let [path (-> (str "/" path) (.replace "//" "/")) + root+path (-> (str (:root options) path) (.replaceAll "^/" "")) + ;; babashka patch + io-resource-fn (or (get-in options [:static :io-resource-fn]) io/resource) + load #(if-let [loader (:loader options)] + (io-resource-fn % loader) + (io-resource-fn %))] + (if-not (directory-transversal? root+path) + (if-let [resource (load root+path)] + (let [response (url-response resource)] + (if (or (not (instance? File (:body response))) + ;; babashka patch + true #_(safe-file-resource? response options)) + response))))))) diff --git a/feature-selmer/babashka/impl/selmer_parser.clj b/feature-selmer/babashka/impl/selmer_parser.clj new file mode 100644 index 00000000..ba09095f --- /dev/null +++ b/feature-selmer/babashka/impl/selmer_parser.clj @@ -0,0 +1,11 @@ +(ns babashka.impl.selmer-parser + (:require [selmer.parser :as parser] + [sci.core :as sci :refer [copy-var]])) + +(def pns (sci/create-ns 'selmer.parser nil)) + +(def selmer-parser-namespace + {:obj pns + 'render-file (copy-var parser/render-file pns) + 'render (copy-var parser/render pns) + 'set-resource-path! (copy-var parser/set-resource-path! pns)}) diff --git a/project.clj b/project.clj index c2554e04..f63ac990 100644 --- a/project.clj +++ b/project.clj @@ -44,6 +44,16 @@ :dependencies [[http-kit "2.5.0"]]} :feature/httpkit-server {:source-paths ["feature-httpkit-server"] :dependencies [[http-kit "2.5.0"]]} + :feature/ring {:source-paths ["feature-ring"] + :dependencies [[ring/ring-core "1.8.1"] + [ring/ring-defaults "0.3.2"] + [ring-webjars "0.2.0"]]} + :feature/reitit {:source-paths ["feature-reitit"] + :dependencies [[metosin/reitit "0.5.5"] + [metosin/muuntaja "0.6.7"] + [metosin/ring-http-response "0.9.1"]]} + :feature/selmer {:source-paths ["feature-selmer"] + :dependencies [[selmer "1.12.29"]]} :feature/lanterna {:source-paths ["feature-lanterna"] :dependencies [[babashka/clojure-lanterna "0.9.8-SNAPSHOT"]]} @@ -58,6 +68,9 @@ :feature/datascript :feature/httpkit-client :feature/httpkit-server + :feature/ring + :feature/reitit + :feature/selmer {:dependencies [[clj-commons/conch "0.9.2"] [com.clojure-goes-fast/clj-async-profiler "0.4.1"] [com.opentable.components/otj-pg-embedded "0.13.3"]]}] diff --git a/script/uberjar b/script/uberjar index d7f93598..0c2f3a3e 100755 --- a/script/uberjar +++ b/script/uberjar @@ -110,6 +110,27 @@ else BABASHKA_LEIN_PROFILES+=",-feature/httpkit-server" fi +if [ "$BABASHKA_FEATURE_RING" = "true" ] +then + BABASHKA_LEIN_PROFILES+=",+feature/ring" +else + BABASHKA_LEIN_PROFILES+=",-feature/ring" +fi + +if [ "$BABASHKA_FEATURE_REITIT" = "true" ] +then + BABASHKA_LEIN_PROFILES+=",+feature/reitit" +else + BABASHKA_LEIN_PROFILES+=",-feature/reitit" +fi + +if [ "$BABASHKA_FEATURE_SELMER" = "true" ] +then + BABASHKA_LEIN_PROFILES+=",+feature/selmer" +else + BABASHKA_LEIN_PROFILES+=",-feature/selmer" +fi + if [ "$BABASHKA_FEATURE_LANTERNA" = "true" ] then BABASHKA_LEIN_PROFILES+=",+feature/lanterna" diff --git a/script/uberjar.bat b/script/uberjar.bat index 830a90e1..002b70d4 100755 --- a/script/uberjar.bat +++ b/script/uberjar.bat @@ -82,6 +82,24 @@ set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/httpkit-server set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/httpkit-server ) +if "%BABASHKA_FEATURE_RING%"=="true" ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/ring +) else ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/ring +) + +if "%BABASHKA_FEATURE_REITIT%"=="true" ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/reitit +) else ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/reitit +) + +if "%BABASHKA_FEATURE_SELMER%"=="true" ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/selmer +) else ( + set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,-feature/selmer +) + if "%BABASHKA_FEATURE_LANTERNA%"=="true" ( set BABASHKA_LEIN_PROFILES=%BABASHKA_LEIN_PROFILES%,+feature/lanterna ) else ( diff --git a/src/babashka/impl/features.clj b/src/babashka/impl/features.clj index c4270534..f6bd0dfb 100644 --- a/src/babashka/impl/features.clj +++ b/src/babashka/impl/features.clj @@ -18,4 +18,7 @@ (def oracledb? (= "true" (System/getenv "BABASHKA_FEATURE_ORACLEDB"))) (def hsqldb? (= "true" (System/getenv "BABASHKA_FEATURE_HSQLDB"))) (def datascript? (= "true" (System/getenv "BABASHKA_FEATURE_DATASCRIPT"))) +(def ring? (= "true" (System/getenv "BABASHKA_FEATURE_RING"))) +(def reitit? (= "true" (System/getenv "BABASHKA_FEATURE_REITIT"))) +(def selmer? (= "true" (System/getenv "BABASHKA_FEATURE_SELMER"))) (def lanterna? (= "true" (System/getenv "BABASHKA_FEATURE_LANTERNA"))) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 0c4896ce..979f4a96 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -90,6 +90,23 @@ (when features/httpkit-server? (require '[babashka.impl.httpkit-server])) +(when features/ring? + (require '[babashka.impl.ring-middleware-defaults] + '[babashka.impl.ring-util-response] + '[babashka.impl.ring-util-request] + '[babashka.impl.ring-middleware-content-type] + '[babashka.impl.ring-middleware-webjars] + '[babashka.impl.ring-middleware-anti-forgery])) + +(when features/reitit? + (require '[babashka.impl.reitit-ring] + '[babashka.impl.ring-util-http-response] + '[babashka.impl.muuntaja-core] + '[babashka.impl.muuntaja-middleware])) + +(when features/selmer? + (require '[babashka.impl.selmer-parser])) + (when features/lanterna? (require '[babashka.impl.lanterna])) @@ -405,6 +422,21 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that features/httpkit-client? (assoc 'org.httpkit.client @(resolve 'babashka.impl.httpkit-client/httpkit-client-namespace) 'org.httpkit.sni-client @(resolve 'babashka.impl.httpkit-client/sni-client-namespace)) features/httpkit-server? (assoc 'org.httpkit.server @(resolve 'babashka.impl.httpkit-server/httpkit-server-namespace)) + + features/ring? (-> (assoc 'ring.middleware.defaults @(resolve 'babashka.impl.ring-middleware-defaults/ring-middleware-defaults-namespace)) + (assoc 'ring.util.response @(resolve 'babashka.impl.ring-util-response/ring-util-response-namespace)) + (assoc 'ring.util.request @(resolve 'babashka.impl.ring-util-request/ring-util-request-namespace)) + (assoc 'ring.middleware.content-type @(resolve 'babashka.impl.ring-middleware-content-type/ring-middleware-content-type-namespace)) + (assoc 'ring.middleware.webjars @(resolve 'babashka.impl.ring-middleware-webjars/ring-middleware-webjars-namespace)) + (assoc 'ring.middleware.anti-forgery @(resolve 'babashka.impl.ring-middleware-anti-forgery/ring-middleware-anti-forgery-namespace))) + + features/reitit? (-> (assoc 'reitit.ring @(resolve 'babashka.impl.reitit-ring/reitit-ring-namespace)) + (assoc 'ring.util.http-response @(resolve 'babashka.impl.ring-util-http-response/ring-util-http-response-namespace)) + (assoc 'muuntaja.core @(resolve 'babashka.impl.muuntaja-core/muuntaja-core-namespace)) + (assoc 'muuntaja.middleware @(resolve 'babashka.impl.muuntaja-middleware/muuntaja-middleware-namespace))) + + features/selmer? (assoc 'selmer.parser @(resolve 'babashka.impl.selmer-parser/selmer-parser-namespace)) + features/lanterna? (assoc 'lanterna.screen @(resolve 'babashka.impl.lanterna/lanterna-screen-namespace) 'lanterna.terminal @(resolve 'babashka.impl.lanterna/lanterna-terminal-namespace) 'lanterna.constants @(resolve 'babashka.impl.lanterna/lanterna-constants-namespace))))