diff --git a/README.md b/README.md index b4b0387c..dd88cf84 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A fast data-driven router for Clojure(Script). * First-class [route data](https://metosin.github.io/reitit/basics/route_data.html) * Bi-directional routing * [Pluggable coercion](https://metosin.github.io/reitit/coercion/coercion.html) ([schema](https://github.com/plumatic/schema) & [clojure.spec](https://clojure.org/about/spec)) -* Helpers for [ring](https://metosin.github.io/reitit/ring/ring.html), [http](https://metosin.github.io/reitit/http/interceptors.html) & [the frontend](https://metosin.github.io/reitit/frontend/basics.html) +* Helpers for [ring](https://metosin.github.io/reitit/ring/ring.html), [http](https://metosin.github.io/reitit/http/interceptors.html), [pedestal](http://pedestal.io) & [frontend](https://metosin.github.io/reitit/frontend/basics.html) * Extendable * Modular * [Fast](https://metosin.github.io/reitit/performance.html) @@ -21,7 +21,7 @@ See the [full documentation](https://metosin.github.io/reitit/) for details. There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians Slack](http://clojurians.net/) for discussion & help. -## Modules +## Main Modules * `reitit` - all bundled * `reitit-core` - the routing core @@ -36,9 +36,13 @@ There is [#reitit](https://clojurians.slack.com/messages/reitit/) in [Clojurians * `reitit-interceptors` - [common interceptors](https://metosin.github.io/reitit/http/default_interceptors.html) * `reitit-sieppari` support for [Sieppari](https://github.com/metosin/sieppari) +## Extra modules + +* `reitit-pedestal` support for [Pedestal](http://pedestal.io) + ## Latest version -All bundled: +All main modules bundled: ```clj [metosin/reitit "0.2.9"] @@ -70,6 +74,11 @@ Optionally, the parts can be required separately: [metosin/reitit-sieppari "0.2.9"] ``` +```clj +;; pedestal +[metosin/reitit-pedestal "0.2.9"] +``` + ## Quick start ```clj diff --git a/doc/http/pedestal.md b/doc/http/pedestal.md index 423740ef..ed61ace2 100644 --- a/doc/http/pedestal.md +++ b/doc/http/pedestal.md @@ -1,25 +1,70 @@ # Pedestal -[Pedestal](http://pedestal.io/) is a well known interceptor-based web framework for Clojure. To use `reitit-http` with Pedestal, we need to change the default routing interceptor into a new one. Examples projects show how to do this. +[Pedestal](http://pedestal.io/) is a well known interceptor-based web framework for Clojure. To use `reitit-http` with Pedestal, we need to change the default routing interceptor. The needed helpers for this are found in a separate package: + +```clj +[metosin/reitit-ring "0.2.9"] +``` + +You should read the [interceptor guide](interceptors.md) to understand the basics on Interceptor-based dispatch. + +## Example + +A minimalistic example on how to to swap the default-router with a reitit router. + +```clj +; [io.pedestal/pedestal.service "0.5.5"] +; [io.pedestal/pedestal.jetty "0.5.5"] +; [metosin/reitit-pedestal "0.2.9"] +; [metosin/reitit "0.2.9"] + +(ns example.server + (:require [io.pedestal.http :as server] + [reitit.pedestal :as pedestal] + [reitit.http :as http] + [reitit.ring :as ring])) + +(def router + (pedestal/routing-interceptor + (http/router + ["/ping" (fn [_] {:status 200, :body "pong"})]) + (ring/create-default-handler))) + +(defn start [] + (-> {::server/type :jetty + ::server/port 3000 + ::server/join? false + ;; no pedestal routes + ::server/routes []} + (server/default-interceptors) + ;; swap the reitit router + (pedestal/replace-last-interceptor router) + (server/dev-interceptors) + (server/create-server) + (server/start)) + (println "server running in port 3000")) + +(start) +``` ## Caveat -`reitit-http` defines Interceptors as `reitit.interceptor/Interceptor`. Compared to Pedestal 2-arity error handlers, reitit uses a simplified (1-arity) handlers. Differences in error handling are described in the [Sieppari README](https://github.com/metosin/sieppari#differences-to-pedestal). +There is no common interceptor spec for Clojure and All default reitit interceptors (coercion, exceptions etc.) use the [Sieppari](https://github.com/metosin/sieppari) interceptor model. For most parts, they are fully compatible with the Pedestal Interceptor model. Only exception being that the `:error` handlers take just 1 arity (`context`) compared to [Pedestal's 2-arity](http://pedestal.io/reference/error-handling) (`context` and `exception`). -* you can use any [pedestal-style interceptor](http://pedestal.io/reference/interceptors) within reitit router (as Pedestal is executing those anyway) -* you can use any reitit-style interceptor that doesn't have `:error`-stage defined -* using a reitit-style interceptor with `:error` defined will cause `ArityException` if invoked +Currently, there is only the `reitit.http.interceptors.exception/exception-interceptor` which has `:error` defined - just don't use it and everything should just work. + +You are most welcome to discuss about a common interceptor spec in [#interceptors](https://clojurians.slack.com/messages/interceptors/) in [Clojurians Slack](http://clojurians.net/). See the [error handling guide](http://pedestal.io/reference/error-handling) on how to handle errors with Pedestal. -## Examples +## More examples ### Simple -* simple example, with both sync & async code: - * https://github.com/metosin/reitit/tree/master/examples/pedestal +Simple example, with both sync & async interceptors: https://github.com/metosin/reitit/tree/master/examples/pedestal -### With batteries +### Swagger -* with [default interceptors](default_interceptors.md), [coercion](../coercion/coercion.md) and [swagger](../ring/swagger.md)-support (note: exception handling is disabled): - * https://github.com/metosin/reitit/tree/master/examples/pedestal-swagger +More complete example with custom interceptors, [default interceptors](default_interceptors.md), [coercion](../coercion/coercion.md) and [swagger](../ring/swagger.md)-support: https://github.com/metosin/reitit/tree/master/examples/pedestal-swagger + +note: exception handling is disabled in this example diff --git a/examples/frontend-controllers/project.clj b/examples/frontend-controllers/project.clj index ea864cdf..5e432242 100644 --- a/examples/frontend-controllers/project.clj +++ b/examples/frontend-controllers/project.clj @@ -4,7 +4,7 @@ :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring-server "0.5.0"] [reagent "0.8.1"] [ring "1.6.3"] diff --git a/examples/frontend/project.clj b/examples/frontend/project.clj index caecc610..74899aa1 100644 --- a/examples/frontend/project.clj +++ b/examples/frontend/project.clj @@ -4,7 +4,7 @@ :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring-server "0.5.0"] [reagent "0.8.1"] [ring "1.6.3"] diff --git a/examples/http-swagger/project.clj b/examples/http-swagger/project.clj index fd9eb4fe..35ead5cb 100644 --- a/examples/http-swagger/project.clj +++ b/examples/http-swagger/project.clj @@ -1,6 +1,6 @@ (defproject ring-example "0.1.0-SNAPSHOT" :description "Reitit Http App with Swagger" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.0"] [aleph "0.4.6"] [metosin/reitit "0.2.9"]] diff --git a/examples/http/project.clj b/examples/http/project.clj index ebd374bc..143ce463 100644 --- a/examples/http/project.clj +++ b/examples/http/project.clj @@ -1,6 +1,6 @@ (defproject ring-example "0.1.0-SNAPSHOT" :description "Reitit Ring App with Swagger" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [org.clojure/core.async "0.4.474"] [funcool/promesa "1.9.0"] [manifold "0.1.8"] diff --git a/examples/just-coercion-with-ring/project.clj b/examples/just-coercion-with-ring/project.clj index 8b3dbcdf..0d6a8971 100644 --- a/examples/just-coercion-with-ring/project.clj +++ b/examples/just-coercion-with-ring/project.clj @@ -1,6 +1,6 @@ (defproject just-coercion-with-ring "0.1.0-SNAPSHOT" :description "Reitit coercion with vanilla ring" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.0"] [metosin/muuntaja "0.4.1"] [metosin/reitit "0.2.9"]]) diff --git a/examples/pedestal-swagger/project.clj b/examples/pedestal-swagger/project.clj index ed6cfae0..53ba7fcb 100644 --- a/examples/pedestal-swagger/project.clj +++ b/examples/pedestal-swagger/project.clj @@ -1,7 +1,8 @@ (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"] + :dependencies [[org.clojure/clojure "1.10.0"] + [io.pedestal/pedestal.service "0.5.5"] + [io.pedestal/pedestal.jetty "0.5.5"] + [metosin/reitit-pedestal "0.2.9"] [metosin/reitit "0.2.9"]] :repl-options {:init-ns example.server}) diff --git a/examples/pedestal-swagger/src/example/server.clj b/examples/pedestal-swagger/src/example/server.clj index 6f80a29c..af99e8d3 100644 --- a/examples/pedestal-swagger/src/example/server.clj +++ b/examples/pedestal-swagger/src/example/server.clj @@ -1,6 +1,6 @@ (ns example.server - (:require [io.pedestal.http] - [reitit.interceptor.pedestal :as pedestal] + (:require [io.pedestal.http :as server] + [reitit.pedestal :as pedestal] [reitit.ring :as ring] [reitit.http :as http] [reitit.swagger :as swagger] @@ -9,12 +9,15 @@ [reitit.coercion.spec :as spec-coercion] [reitit.http.interceptors.parameters :as parameters] [reitit.http.interceptors.muuntaja :as muuntaja] - #_[reitit.http.interceptors.exception :as exception] [reitit.http.interceptors.multipart :as multipart] - [muuntaja.core :as m] - [clojure.java.io :as io])) + [clojure.core.async :as a] + [clojure.java.io :as io] + [muuntaja.core :as m])) -(def routing-interceptor +(defn interceptor [number] + {:enter (fn [ctx] (a/go (update-in ctx [:request :number] (fnil + 0) number)))}) + +(def router (pedestal/routing-interceptor (http/router [["/swagger.json" @@ -23,6 +26,17 @@ :description "with pedestal & reitit-http"}} :handler (swagger/create-swagger-handler)}}] + ["/interceptors" + {:swagger {:tags ["interceptors"]} + :interceptors [(interceptor 1)]} + + ["/number" + {:interceptors [(interceptor 10)] + :get {:interceptors [(interceptor 100)] + :handler (fn [req] + {:status 200 + :body (select-keys req [:number])})}}]] + ["/files" {:swagger {:tags ["files"]}} @@ -69,8 +83,6 @@ (muuntaja/format-negotiate-interceptor) ;; encoding response body (muuntaja/format-response-interceptor) - ;; exception handling - doesn't work - ;;(exception/exception-interceptor) ;; decoding request body (muuntaja/format-request-interceptor) ;; coercing response bodys @@ -85,27 +97,27 @@ (swagger-ui/create-swagger-ui-handler {:path "/" :config {:validatorUrl nil}}) + (ring/create-resource-handler) (ring/create-default-handler)))) -(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))) + (-> {:env :dev + ::server/type :jetty + ::server/port 3000 + ::server/join? false + ;; no pedestal routes + ::server/routes [] + ;; allow serving the swagger-ui styles & scripts from self + ::server/secure-headers {:content-security-policy-settings + {:default-src "'self'" + :style-src "'self' 'unsafe-inline'" + :script-src "'self' 'unsafe-inline'"}}} + (io.pedestal.http/default-interceptors) + ;; use the reitit router + (pedestal/replace-last-interceptor router) + (io.pedestal.http/dev-interceptors) + (io.pedestal.http/create-server) + (io.pedestal.http/start)) (println "server running in port 3000")) (comment diff --git a/examples/pedestal/project.clj b/examples/pedestal/project.clj index ed6cfae0..53ba7fcb 100644 --- a/examples/pedestal/project.clj +++ b/examples/pedestal/project.clj @@ -1,7 +1,8 @@ (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"] + :dependencies [[org.clojure/clojure "1.10.0"] + [io.pedestal/pedestal.service "0.5.5"] + [io.pedestal/pedestal.jetty "0.5.5"] + [metosin/reitit-pedestal "0.2.9"] [metosin/reitit "0.2.9"]] :repl-options {:init-ns example.server}) diff --git a/examples/pedestal/src/example/server.clj b/examples/pedestal/src/example/server.clj index 6bde70f6..afe11590 100644 --- a/examples/pedestal/src/example/server.clj +++ b/examples/pedestal/src/example/server.clj @@ -1,7 +1,7 @@ (ns example.server - (:require [io.pedestal.http] + (:require [io.pedestal.http :as server] [clojure.core.async :as a] - [reitit.interceptor.pedestal :as pedestal] + [reitit.pedestal :as pedestal] [muuntaja.interceptor] [reitit.http :as http] [reitit.ring :as ring])) @@ -18,7 +18,7 @@ {:enter (fn [{:keys [request] :as ctx}] (a/go (assoc ctx :response (handler request))))}) -(def routing-interceptor +(def router (pedestal/routing-interceptor (http/router ["/api" @@ -36,32 +36,27 @@ ;; optional interceptors for all matched routes {:data {:interceptors [(interceptor :router)]}}) - ;; optional default ring handler (if no routes have matched) - (ring/create-default-handler) + ;; optional default ring handlers (if no routes have matched) + (ring/routes + (ring/create-resource-handler) + (ring/create-default-handler)) ;; optional top-level routes for both routes & default route {:interceptors [(muuntaja.interceptor/format-interceptor) (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))) + (-> {::server/type :jetty + ::server/port 3000 + ::server/join? false + ;; no pedestal routes + ::server/routes []} + (server/default-interceptors) + ;; use the reitit router + (pedestal/replace-last-interceptor router) + (server/dev-interceptors) + (server/create-server) + (server/start)) (println "server running in port 3000")) (comment diff --git a/examples/pedestal/src/reitit/interceptor/pedestal.clj b/examples/pedestal/src/reitit/interceptor/pedestal.clj deleted file mode 100644 index 362cbd5d..00000000 --- a/examples/pedestal/src/reitit/interceptor/pedestal.clj +++ /dev/null @@ -1,38 +0,0 @@ -(ns reitit.interceptor.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/examples/ring-example/project.clj b/examples/ring-example/project.clj index d7250ebb..1e5496ab 100644 --- a/examples/ring-example/project.clj +++ b/examples/ring-example/project.clj @@ -1,6 +1,6 @@ (defproject ring-example "0.1.0-SNAPSHOT" :description "Reitit Ring App" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.0"] [metosin/reitit "0.2.9"]] :repl-options {:init-ns example.server}) diff --git a/examples/ring-spec-swagger/project.clj b/examples/ring-spec-swagger/project.clj index e98978a7..dd02a1e7 100644 --- a/examples/ring-spec-swagger/project.clj +++ b/examples/ring-spec-swagger/project.clj @@ -1,6 +1,6 @@ (defproject ring-example "0.1.0-SNAPSHOT" :description "Reitit Ring App with Swagger" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.0"] [metosin/reitit "0.2.9"]] :repl-options {:init-ns example.server}) diff --git a/examples/ring-swagger/project.clj b/examples/ring-swagger/project.clj index e98978a7..dd02a1e7 100644 --- a/examples/ring-swagger/project.clj +++ b/examples/ring-swagger/project.clj @@ -1,6 +1,6 @@ (defproject ring-example "0.1.0-SNAPSHOT" :description "Reitit Ring App with Swagger" - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [ring/ring-jetty-adapter "1.7.0"] [metosin/reitit "0.2.9"]] :repl-options {:init-ns example.server}) diff --git a/modules/reitit-pedestal/project.clj b/modules/reitit-pedestal/project.clj new file mode 100644 index 00000000..8a1ebc89 --- /dev/null +++ b/modules/reitit-pedestal/project.clj @@ -0,0 +1,11 @@ +(defproject metosin/reitit-pedestal "0.2.9" + :description "Reitit + Pedestal Integration" + :url "https://github.com/metosin/reitit" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :scm {:name "git" + :url "https://github.com/metosin/reitit"} + :plugins [[lein-parent "0.3.2"]] + :parent-project {:path "../../project.clj" + :inherit [:deploy-repositories :managed-dependencies]} + :dependencies [[metosin/reitit-http]]) diff --git a/examples/pedestal-swagger/src/reitit/interceptor/pedestal.clj b/modules/reitit-pedestal/src/reitit/pedestal.clj similarity index 72% rename from examples/pedestal-swagger/src/reitit/interceptor/pedestal.clj rename to modules/reitit-pedestal/src/reitit/pedestal.clj index 362cbd5d..ced14531 100644 --- a/examples/pedestal-swagger/src/reitit/interceptor/pedestal.clj +++ b/modules/reitit-pedestal/src/reitit/pedestal.clj @@ -1,4 +1,4 @@ -(ns reitit.interceptor.pedestal +(ns reitit.pedestal (:require [io.pedestal.interceptor.chain :as chain] [io.pedestal.interceptor :as interceptor] [io.pedestal.http :as http] @@ -19,9 +19,9 @@ (defn routing-interceptor ([router] - (routing-interceptor router nil)) + (routing-interceptor router nil)) ([router default-handler] - (routing-interceptor router default-handler nil)) + (routing-interceptor router default-handler nil)) ([router default-handler {:keys [interceptors]}] (interceptor/interceptor (reitit.http/routing-interceptor @@ -30,9 +30,7 @@ {: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))) +(defn replace-last-interceptor [service-map interceptor] + (-> service-map + (update ::http/interceptors pop) + (update ::http/interceptors conj interceptor))) diff --git a/project.clj b/project.clj index ab36ce4d..0982a6d9 100644 --- a/project.clj +++ b/project.clj @@ -22,6 +22,7 @@ [metosin/reitit-swagger-ui "0.2.9"] [metosin/reitit-frontend "0.2.9"] [metosin/reitit-sieppari "0.2.9"] + [metosin/reitit-pedestal "0.2.9"] [meta-merge "1.0.0"] [lambdaisland/deep-diff "0.0-25"] [ring/ring-core "1.7.1"] @@ -53,9 +54,10 @@ "modules/reitit-swagger/src" "modules/reitit-swagger-ui/src" "modules/reitit-frontend/src" - "modules/reitit-sieppari/src"] + "modules/reitit-sieppari/src" + "modules/reitit-pedestal/src"] - :dependencies [[org.clojure/clojure "1.9.0"] + :dependencies [[org.clojure/clojure "1.10.0"] [org.clojure/clojurescript "1.10.439"] ;; modules dependencies @@ -76,12 +78,15 @@ [org.clojure/tools.namespace "0.2.11"] [com.gfredericks/test.chuck "0.2.9"] + [io.pedestal/pedestal.service "0.5.5"] + [org.clojure/core.async "0.4.490"] [manifold "0.1.8"] [funcool/promesa "1.9.0"] ;; https://github.com/bensu/doo/issues/180 [fipp "0.6.14" :exclusions [org.clojure/core.rrb-vector]]]} + :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} :perf {:jvm-opts ^:replace ["-server" "-Xmx4096m" "-Dclojure.compiler.direct-linking=true"] @@ -105,7 +110,7 @@ "-XX:+PrintCompilation" "-XX:+UnlockDiagnosticVMOptions" "-XX:+PrintInlining"]}} - :aliases {"all" ["with-profile" "dev,default"] + :aliases {"all" ["with-profile" "dev,default:dev,default,1.9"] "perf" ["with-profile" "default,dev,perf"] "test-clj" ["all" "do" ["bat-test"] ["check"]] "test-browser" ["doo" "chrome-headless" "test"] diff --git a/scripts/lein-modules b/scripts/lein-modules index 35e466ff..e78d3d08 100755 --- a/scripts/lein-modules +++ b/scripts/lein-modules @@ -15,6 +15,7 @@ for ext in \ reitit-swagger-ui \ reitit-frontend \ reitit-sieppari \ + reitit-pedestal \ reitit; do cd modules/$ext; lein "$@"; cd ../..; done