Skip to content

Commit

Permalink
initial http-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
ikitommi committed Aug 25, 2018
1 parent 8603224 commit 59cbb25
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 6 deletions.
7 changes: 3 additions & 4 deletions modules/reitit-core/src/reitit/interceptor.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@
(defn compile-result
([route opts]
(compile-result route opts nil))
([[path {:keys [interceptors handler] :as data}] opts scope]
(ensure-handler! path data scope)
([[_ {:keys [interceptors handler] :as data}] opts _]
(map->Endpoint
{:interceptors (chain interceptors handler data opts)
:data data})))
Expand All @@ -131,8 +130,8 @@
Options:
| key | description |
| --------------------------------|-------------|
| key | description
| --------------------------------|-------------
| `:reitit.interceptor/transform` | Function of [Interceptor] => [Interceptor] to transform the expanded Interceptors (default: identity).
| `:reitit.interceptor/registry` | Map of `keyword => IntoInterceptor` to replace keyword references into Interceptor
Expand Down
10 changes: 10 additions & 0 deletions modules/reitit-http/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(defproject metosin/reitit-http "0.2.0-SNAPSHOT"
:description "Reitit: HTTP routing with interceptors"
:url "https://github.com/metosin/reitit"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:plugins [[lein-parent "0.3.2"]]
:parent-project {:path "../../project.clj"
:inherit [:deploy-repositories :managed-dependencies]}
:dependencies [[metosin/reitit-core]
[metosin/reitit-ring]])
198 changes: 198 additions & 0 deletions modules/reitit-http/src/reitit/http.cljc
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
(ns reitit.http
(:require [meta-merge.core :refer [meta-merge]]
[reitit.interceptor :as interceptor]
[reitit.ring :as ring]
[reitit.core :as r]
[reitit.impl :as impl]))

(defrecord Endpoint [data handler path method interceptors])

(defn http-handler
"Creates a ring-handler out of a http-router and
an interceptor runner.
Optionally takes a ring-handler which is called
in no route matches."
([router runner]
(http-handler router runner nil))
([router runner default-handler]
(let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))]
(with-meta
(fn [request]
(if-let [match (r/match-by-path router (:uri request))]
(let [method (:request-method request)
path-params (:path-params match)
result (:result match)
interceptors (-> result method :interceptors)
request (-> request
(impl/fast-assoc :path-params path-params)
(impl/fast-assoc ::r/match match)
(impl/fast-assoc ::r/router router))]
(:response (runner interceptors request)))
(default-handler request)))
{::r/router router}))))

(defn get-router [handler]
(-> handler meta ::r/router))

(defn get-match [request]
(::r/match request))

(defn coerce-handler [[path data] {:keys [expand] :as opts}]
[path (reduce
(fn [acc method]
(if (contains? acc method)
(update acc method expand opts)
acc)) data ring/http-methods)])

(defn compile-result [[path data] opts]
(let [[top childs] (ring/group-keys data)
->handler (fn [handler]
(if handler
(fn [ctx]
(->> ctx :request handler (assoc ctx :response)))))
compile (fn [[path data] opts scope]
(let [data (update data :handler ->handler)]
(interceptor/compile-result [path data] opts scope)))
->endpoint (fn [p d m s]
(-> (compile [p d] opts s)
(map->Endpoint)
(assoc :path p)
(assoc :method m)))
->methods (fn [any? data]
(reduce
(fn [acc method]
(cond-> acc
any? (assoc method (->endpoint path data method nil))))
(ring/map->Methods {})
ring/http-methods))]
(if-not (seq childs)
(->methods true top)
(reduce-kv
(fn [acc method data]
(let [data (meta-merge top data)]
(assoc acc method (->endpoint path data method method))))
(->methods (:handler top) data)
childs))))

(defn router
"Creates a [[reitit.core/Router]] from raw route data and optionally an options map with
support for http-methods and Interceptors. See [docs](https://metosin.github.io/reitit/)
for details.
Example:
(router
[\"/api\" {:interceptors [format-i oauth2-i]}
[\"/users\" {:get get-user
:post update-user
:delete {:interceptors [delete-i]
:handler delete-user}}]])
See router options from [[reitit.core/router]] and [[reitit.middleware/router]]."
([data]
(router data nil))
([data opts]
(let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)]
(r/router data opts))))

(ns reitit.interceptor.simple)

(defn enqueue [ctx interceptors]
(update ctx :queue (fnil into clojure.lang.PersistentQueue/EMPTY) interceptors))


(defn leave [ctx stack key]
(let [it (clojure.lang.RT/iter stack)]
(loop [ctx ctx, key key]
(if (.hasNext it)
(if-let [f (-> it .next key)]
(let [[ctx key] (try
[(f ctx)])])
(recur
(try
(leave ctx)))
(recur ctx))
ctx))))

(defn try-f [ctx f]
(try
(f ctx)
(catch Exception e
(assoc ctx :error e))))

(defn enter [ctx]
(let [queue ^clojure.lang.PersistentQueue (:queue ctx)
stack (:stack ctx)
error (:error ctx)
interceptor (peek queue)]
(cond

;; all done
(not interceptor)
(leave ctx stack :leave)

;; error
error
(leave (assoc ctx :queue nil) stack :error)

;; continue
:else
(let [queue (pop queue)
stack (conj stack interceptor)
f (or (:enter interceptor) identity)
ctx (-> ctx (assoc :queue queue) (assoc :stack stack) (try-f f))]
(recur ctx)))))

(defrecord Context [queue stack request response])

(defn context [interceptors request]
(->Context (into clojure.lang.PersistentQueue/EMPTY interceptors) nil request nil))

(defn execute [interceptors request]
(enter (context interceptors request)))

(ns user)

(require '[reitit.http :as http])
(require '[reitit.interceptor.simple :as simple])

(def i (fn [value]
{:enter (fn [ctx]
(update-in ctx [:request :enter] (fnil conj []) value))
:leave (fn [ctx]
(update-in ctx [:response :body :leave] (fnil conj []) value))}))

(def f (fn [key] {key (fn [ctx] (throw (ex-info "fail" {})))}))

(def app
(http/http-handler
(http/router
["/api"
{:interceptors [(i 1) (i 2)]}
["/ipa"
{:interceptors [(i 3) (f :enter) (i 4)]
:handler (fn [{:keys [enter]}]
{:status 200
:body {:enter enter}})}]])
simple/execute))

(app {:request-method :get, :uri "/api/ipa"})
; => {:status 200, :body {:enter [1 2 3 4], :leave [4 3 2 1]}}

(def app2
(http/http-handler
(http/router
["/api"
{:interceptors [(i 1) (i 2)]}
["/ipa"
{:interceptors [(i 3) (i 4) (fn [ctx]
(assoc ctx
:response
{:status 200
:body {:enter (-> ctx
:request
:enter)}}))]}]])
simple/execute))

(app2 {:request-method :get, :uri "/api/ipa"})
; => {:status 200, :body {:enter [1 2 3 4], :leave [4 3 2 1]}}
2 changes: 1 addition & 1 deletion modules/reitit-ring/src/reitit/ring.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
(defrecord Methods [get head post put delete connect options trace patch])
(defrecord Endpoint [data handler path method middleware])

(defn- group-keys [data]
(defn ^:no-wiki group-keys [data]
(reduce-kv
(fn [[top childs] k v]
(if (http-methods k)
Expand Down
2 changes: 1 addition & 1 deletion modules/reitit-swagger/src/reitit/swagger.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
(s/def ::summary string?)
(s/def ::description string?)

(s/def ::swagger (s/keys :req-un [::id]))
(s/def ::swagger (s/keys :opt-un [::id]))
(s/def ::spec (s/keys :opt-un [::swagger ::no-doc ::tags ::summary ::description]))

(def swagger-feature
Expand Down

0 comments on commit 59cbb25

Please sign in to comment.