diff --git a/examples/pedestal/.gitignore b/examples/pedestal/.gitignore new file mode 100644 index 00000000..c53038ec --- /dev/null +++ b/examples/pedestal/.gitignore @@ -0,0 +1,11 @@ +/target +/classes +/checkouts +pom.xml +pom.xml.asc +*.jar +*.class +/.lein-* +/.nrepl-port +.hgignore +.hg/ diff --git a/examples/pedestal/README.md b/examples/pedestal/README.md new file mode 100644 index 00000000..281db0cd --- /dev/null +++ b/examples/pedestal/README.md @@ -0,0 +1,17 @@ +# reitit-http example with pedestal + +## Usage + +```clj +> lein repl +(start) +``` + +Go with browser to: + +* http://localhost:3000/api/sync - synchronous +* http://localhost:3000/api/async - with core.async + +## License + +Copyright © 2018 Metosin Oy diff --git a/examples/pedestal/project.clj b/examples/pedestal/project.clj new file mode 100644 index 00000000..42d045f8 --- /dev/null +++ b/examples/pedestal/project.clj @@ -0,0 +1,7 @@ +(defproject ring-example "0.1.0-SNAPSHOT" + :description "Reitit-http with pedestal" + :dependencies [[org.clojure/clojure "1.9.0"] + [io.pedestal/pedestal.service "0.5.4"] + [io.pedestal/pedestal.jetty "0.5.4"] + [metosin/reitit "0.2.0-SNAPSHOT"]] + :repl-options {:init-ns example.server}) diff --git a/examples/pedestal/src/example/server.clj b/examples/pedestal/src/example/server.clj new file mode 100644 index 00000000..4b325981 --- /dev/null +++ b/examples/pedestal/src/example/server.clj @@ -0,0 +1,63 @@ +(ns example.server + (:require [io.pedestal.http] + [clojure.core.async :as a] + [reitit.pedestal :as pedestal] + [reitit.http :as http] + [reitit.ring :as ring])) + +(defn interceptor [x] + {:enter (fn [ctx] (println ">>" x) ctx) + :leave (fn [ctx] (println "<<" x) ctx)}) + +(defn handler [_] + (println "handler") + {:status 200, + :body "pong"}) + +(def async-handler + {:enter (fn [{:keys [request] :as ctx}] + (a/go + (assoc ctx :response (handler request))))}) + +(def routing-interceptor + (pedestal/routing-interceptor + (http/router + ["/api" + {:interceptors [[interceptor :api] + [interceptor :apa]]} + + ["/sync" + {:interceptors [[interceptor :sync]] + :get {:interceptors [[interceptor :get]] + :handler handler}}] + + ["/async" + {:interceptors [[interceptor :async]] + :get {:interceptors [[interceptor :get] async-handler]}}]] + {:data {:interceptors [[interceptor :router]]}}) + (ring/create-default-handler) + {:interceptors [[interceptor :top]]})) + +(defonce server (atom nil)) + +(defn start [] + (when @server + (io.pedestal.http/stop @server) + (println "server stopped")) + (-> {:env :prod + :io.pedestal.http/routes [] + :io.pedestal.http/resource-path "/public" + :io.pedestal.http/type :jetty + :io.pedestal.http/port 3000} + (merge {:env :dev + :io.pedestal.http/join? false + :io.pedestal.http/allowed-origins {:creds true :allowed-origins (constantly true)}}) + (pedestal/default-interceptors routing-interceptor) + io.pedestal.http/dev-interceptors + io.pedestal.http/create-server + io.pedestal.http/start + (->> (reset! server))) + (println "server running in port 3000")) + +(comment + (start)) diff --git a/examples/pedestal/src/reitit/pedestal.clj b/examples/pedestal/src/reitit/pedestal.clj new file mode 100644 index 00000000..7d8de821 --- /dev/null +++ b/examples/pedestal/src/reitit/pedestal.clj @@ -0,0 +1,38 @@ +(ns reitit.pedestal + (:require [io.pedestal.interceptor.chain :as chain] + [io.pedestal.interceptor :as interceptor] + [io.pedestal.http :as http] + [reitit.interceptor] + [reitit.http]) + (:import (reitit.interceptor Executor))) + +(def pedestal-executor + (reify + Executor + (queue [_ interceptors] + (->> interceptors + (map (fn [{:keys [::interceptor/handler] :as interceptor}] + (or handler interceptor))) + (map interceptor/interceptor))) + (enqueue [_ context interceptors] + (chain/enqueue context interceptors)))) + +(defn routing-interceptor + ([router] + (routing-interceptor router nil)) + ([router default-handler] + (routing-interceptor router default-handler nil)) + ([router default-handler {:keys [interceptors]}] + (interceptor/interceptor + (reitit.http/routing-interceptor + router + default-handler + {:executor pedestal-executor + :interceptors interceptors})))) + +(defn default-interceptors [spec router] + (-> spec + (assoc ::http/routes []) + (http/default-interceptors) + (update ::http/interceptors (comp vec butlast)) + (update ::http/interceptors conj router))) diff --git a/modules/reitit-core/src/reitit/interceptor.cljc b/modules/reitit-core/src/reitit/interceptor.cljc index a5139c0a..c2d7e674 100644 --- a/modules/reitit-core/src/reitit/interceptor.cljc +++ b/modules/reitit-core/src/reitit/interceptor.cljc @@ -18,7 +18,10 @@ (execute [this interceptors request] [this interceptors request respond raise] - "executes the interceptor chain")) + "executes the interceptor chain with a request") + (enqueue + [this context interceptors] + "enqueues the interceptors into the queue")) (defn context [request] (map->Context {:request request})) diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index 373df4db..1befcff7 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -61,6 +61,33 @@ (let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)] (r/router data opts)))) +(defn routing-interceptor + "A Pedestal-style routing interceptor that enqueus the interceptors into context." + [router default-handler {:keys [interceptors executor]}] + (let [default-handler (or default-handler (fn ([_]))) + default-interceptors (->> interceptors + (map #(interceptor/into-interceptor % nil (r/options router)))) + default-queue (interceptor/queue executor default-interceptors)] + {:name ::router + :enter (fn [{:keys [request] :as context}] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + endpoint (-> match :result method) + interceptors (or (:queue endpoint) (:interceptors endpoint)) + request (-> request + (impl/fast-assoc :path-params path-params) + (impl/fast-assoc ::r/match match) + (impl/fast-assoc ::r/router router)) + context (assoc context :request request) + queue (interceptor/queue executor (concat default-interceptors interceptors))] + (interceptor/enqueue executor context queue)) + (interceptor/enqueue executor context default-queue))) + :leave (fn [context] + (if-not (:response context) + (assoc context :response (default-handler (:request context))) + context))})) + (defn ring-handler "Creates a ring-handler out of a http-router, a default ring-handler and options map, with the following keys: diff --git a/project.clj b/project.clj index f09c32d6..9b1e5c84 100644 --- a/project.clj +++ b/project.clj @@ -81,7 +81,8 @@ :dependencies [[compojure "1.6.1"] [ring/ring-defaults "0.3.2"] [ikitommi/immutant-web "3.0.0-alpha1"] - [io.pedestal/pedestal.route "0.5.4"] + [io.pedestal/pedestal.service "0.5.4"] + [io.pedestal/pedestal.jetty "0.5.4"] [org.clojure/core.async "0.4.474"] [metosin/sieppari "0.0.0-alpha4"] [yada "1.2.13"]