Do I have to re-eval the router definition every time I change my handler?
(def app
(ring/ring-handler
(ring/router
[["/" main-page]
["/config" {:post config/save}]]) ; config/save cannot be used as var here and thus you need to re-eval `app` every time
...
@jumar it doesnβt work with Var? It could, and there is a multimethod to handle how things are expanded, Var could be added as default mapping to it.
you could also re-create the top-level ring handler for all requests in dev. quite slow, but always up-to-date, e.g. (defn app [request] ((ring/β¦) request))
. Personally using integrant-repl and have keyboard mapping of shift + Cmd + ΓΆ
to run reset. takes few millis so, just that.
With var I get this error:
No implementation of method: :expand of protocol: #'reitit.core/Expand found for class: clojure.lang.Var
I try to avoid using tools.namespace refresh (if that's the thing integrant uses) - at least when changes are small.
Reloading the app (I do that now) is easy when it's in the same namespace
but since config/save
is another ns it's more annoying π.
(extend-protocol reitit.core/Expand
clojure.lang.Var
(expand [this _] {:handler this}))
does that help?
Yes! You are super-helpful, thanks! Is it worth filing an issue or should I just keep it in my code?
I think it could do that out-of-the-box, so PR welcome
I know it might not be the best place to ask this, but I am trying to run the example from Reitit+Malli, and I can create a post request with httpie, but I failed to make the same request with http-kit
Did anyone go through the same trouble?
(ns test.server
(:require
[org.httpkit.client :as client]
[cheshire.core :as j]))
;; you can use babashka for this example
(->>
(client/post
"<http://localhost:3000/math/plus>"
{:accept :json
:body (j/encode {:x 1 :y 20}) :as :text})
deref
:body
println)
;; => {"schema":"[:map [:x any?] [:y any?]]","errors":[{"in":[],"value":null,"message":"invalid type","schema":"[:map [:x any?] [:y any?]]","path":[],"type":"malli.core/invalid-type"}],"value":null,"type":"reitit.coercion/request-coercion","coercion":"malli","in":["request","body-params"],"humanized":["invalid type"]}
The funny thing is this works fine
http POST :3000/math/plus x:=1 y:=20
The content type isn't being set on the http-kit client, whereas in httpie
, it's set automatically as application/json
might that have anything to do with it?
Let me double check
nope, it it is not that π
It drives me crazy haha
I am fairly certain it has to play with the coercion, because when I just make an echo of the request, I do see a json body.
can you share your schema please?
and your router setup please
(ns ch.my.server
(:require
;; Uncomment to use
;; [reitit.http.interceptors.dev :as dev]
;; [spec-tools.spell :as spell]
[reitit.swagger :as swagger]
[clojure.core.async :as a]
[<http://clojure.java.io|clojure.java.io> :as io]
[clojure.pprint :as pprint]
[integrant.core :as ig]
[io.pedestal.http :as server]
[muuntaja.core :as m]
[muuntaja.interceptor]
[malli.util :as mu]
[reitit.coercion.malli]
[reitit.dev.pretty :as pretty]
[reitit.http :as http]
[reitit.http.coercion :as coercion]
[reitit.http.interceptors.exception :as exception]
[reitit.http.interceptors.multipart :as multipart]
[reitit.http.interceptors.muuntaja :as muuntaja]
[reitit.http.interceptors.parameters :as parameters]
[reitit.pedestal :as pedestal]
[reitit.ring :as ring]
[reitit.ring.malli]
[reitit.swagger-ui :as swagger-ui]))
(defn interceptor [x]
{:enter (fn [ctx] (update-in ctx [:request :via] (fnil conj []) {:enter x}))
:leave (fn [ctx] (update-in ctx [:response :body] conj {:leave x}))})
(def routes
[["/echo"
{:get {:handler
(fn [context] (pprint/pprint context)
{:status 200
:body (slurp (:body context))})}
:post {:handler
(fn [context] (pprint/pprint context)
{:status 200
:body (slurp (:body context))})}}]
["/swagger.json"
{:get {:no-doc true
:swagger
{:info {:title "my-api"
:description "with [malli](<https://github.com/metosin/malli>) and reitit-ring"}
:tags [{:name "files", :description "file api"}
{:name "math", :description "math api"}]}
:handler (swagger/create-swagger-handler)}}]
["/math"
{:swagger {:tags ["math"]}
#_#_:interceptors [(interceptor :api)]}
["/plus"
{:get {:summary "plus with malli query parameters"
:parameters {:query [:map [:x int?] [:y int?]]}
:responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x y]} :query} :parameters}]
{:status 200
:body {:total (+ x y)}})}
:post {:summary "plus with malli body parameters"
:parameters {:body [:map [:x int?] [:y int?]]}
;; :responses {200 {:body [:map [:total int?]]}}
:handler (fn [{{{:keys [x y]} :body} :parameters :as context}]
(pprint/pprint context)
{:status 200
:body {:total (+ x y)}})}}]]])
(def router-args
{;:reitit.interceptor/transform dev/print-context-diffs ;; pretty context diffs
;;:validate spec/validate ;; enable spec validation for route data
;;:reitit.spec/wrap spell/closed ;; strict top-level validation
:exception pretty/exception
:data
{:coercion (reitit.coercion.malli/create {:compile mu/open-schema})
:muuntaja m/instance
:interceptors
[;; swagger feature
;; swagger/swagger-feature
;; ;; format
(muuntaja/format-interceptor)
;; query-params & form-params
(parameters/parameters-interceptor)
;; content-negotiation
(muuntaja/format-negotiate-interceptor)
;; decoding request body
(muuntaja/format-request-interceptor)
;; encoding response body
(muuntaja/format-response-interceptor)
;; exception handling
(exception/exception-interceptor)
;; coercing response bodys
(coercion/coerce-response-interceptor)
;; coercing request parameters
(coercion/coerce-request-interceptor)
;; multipart
#_(multipart/multipart-interceptor)]}})
(defn router []
(pedestal/routing-interceptor
(http/router routes router-args)
;; optional default ring handler (if no routes have matched)
(ring/routes
(swagger-ui/create-swagger-ui-handler
{:path "/"
:config {:validatorUrl nil
:operationsSorter "alpha"}})
(ring/create-resource-handler)
(ring/create-default-handler))))
(defn start [{:keys [port router]}]
(let [service-map (-> {:env :dev
::server/type :jetty
::server/port (or 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'"}}}
(server/default-interceptors)
;; use the reitit router
(pedestal/replace-last-interceptor router)
(server/dev-interceptors)
(server/create-server))]
(server/start service-map)
service-map))
(def system-config
{::jetty {:port 3000
:join? false
:router (ig/ref ::router)}
::router {}})
(defmethod ig/init-key ::jetty [_ {:keys [port join? router]}]
(println "server running in port" port)
(start {:port port :join? join? :router router}))
(defmethod ig/halt-key! ::jetty [_ service-map]
(println "Stopping" service-map)
(server/stop service-map))
(defmethod ig/init-key ::router [_ _]
(router))
(defn -main []
(ig/init system-config))
This works
(client/post
"<http://localhost:8080/math/plus>"
{:headers {"Content-Type" "application/json"}
:body (j/encode {:x 1 :y 20})})
For some reason, it doesn't support setting the :content-type
directly
β― cat foo.clj
(ns foo
(:require
[org.httpkit.client :as client]
[cheshire.core :as j]))
;; you can use babashka for this example
(->>
(client/post
"<http://localhost:8080/math/plus>"
{:headers {"Content-Type" "application/json"}
:body (j/encode {:x 1 :y 20})})
deref
:body
println)
ο ο ~
β― bb foo.clj
{"total":21}
Thanks!
I am sorry for the trouble...
not a problemo
@gupta.harsh96 Small world. π If you're evaluating reitit, fwiw, I fully endorse.
Thanks! reitit, is super cool π