Unable to route properly with bidi - clojure

I am sending an index-handler on "/this-route":
(defn index-handler [req]
(assoc (resource-response "index.html" {:root "public"})
:headers {"Content-Type" "text/html; charset=UTF-8"}))
(def routes ["" {"/this-route" {:get index-handler}}]) ;; works
Which works fine.
But when I append anything to this-route, I'm unable to send index-handler even though I can still send a basic res/response:
(def routes ["" {"/this-route" {"" {:get index-handler} ;; doesn't work
"/something" {:get index-handler} ;; doesn't work
"/something-else" (res/response "some response") ;; works
}}])
I get the errors in the client side console showing an error in the index.html line, where I start my app:
<script type="text/javascript">myapp.system.go();</script>
and the error is "myapp is not defined."
Why might this be, and what am I doing wrong?
--- EDIT ---
This is the complete error in console:
Uncaught ReferenceError: myapp is not defined
at something-else:15
DevTools failed to load SourceMap: Could not load content for chrome-extension://gighmmpiobklfepjocnamgkkbiglidom/include.preload.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME

Your routes work fine:
(let [routes ["" {"/this-route" {:get :some-handler}}]]
(is= (bidi/match-route routes "/this-route" :request-method :get)
{:handler :some-handler, :request-method :get}))
(let [routes ["" {"/this-route" {"" {:get :handler-1}
"/something" {:get :handler-2}
"/something-else" {:get :handler-3}}}]]
(is= (bidi/match-route routes "/this-route" :request-method :get)
{:handler :handler-1, :request-method :get})
(is= (bidi/match-route routes "/this-route/something" :request-method :get)
{:handler :handler-2, :request-method :get})
(is= (bidi/match-route routes "/this-route/something-else" :request-method :get)
{:handler :handler-3, :request-method :get}))
As cfrick said, you have somehow created a problem compiling and/or loading your code. That is where you need to look.

Related

clojure: PUT to server always returns 404

I have a server hosting my API. My API relies on data requested from a third-party API (Spotify). Here are the relevant parts of my API handler:
(ns myapp.api.handler
(:require
[compojure.api.sweet :refer :all]
[ring.util.http-response :refer [ok forbidden no-content not-found bad-request]]
[clj-spotify.core :as spotify]))
(defroutes api-routes
(api
{:middleware [wrap-api]
:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "My API"
:description "A description for My API"}
:consumes ["application/json"]
:produces ["application/json"]}}}
(context "/api" []
(context "/me" []
(PUT "/player" []
:query-params [device_id :- String]
(handle-player-put device_id))))))
As you'll be able to tell from my route handler, I'd essentially like to forward the response of the third-party API to my API. Here is the handler function, handle-player-put:
(defn handle-player-put [device-id]
(let [available-devices (-> (spotify/get-current-users-available-devices
{}
(lm/oauth-token :spotify))
:devices)]
(doseq [device available-devices]
(when (= (:id device) device-id)
(if (not (:is_restricted device))
(let [response (spotify/transfer-current-users-playback
{:device_ids [device-id]
:play false}
(lm/oauth-token :spotify))]
(case (-> response :error :status)
nil (no-content)
404 (do
(println "Playback response: 404")
(not-found "Spotify could not find the requested resource."))
{:status (-> response :error :status)
:headers {}
:body (-> response :error :message)})))))))
After a successful (spotify/transfer-current-users-playback) request, response binds to {}. An example of a response after an error looks like {:error {:status 502, :message "Bad gateway."}}
No matter whether transfer-current-users-playback is successful or not, I always get a 404 error (with body text Not Found [404]). What am I doing wrong?
doseq always returns nil so your handler returns nil - which is interpreted by compojure as “this handler won’t handle the request; skip to the next handler” and if no other handler handles the request you get a 404 not found.
You should not use (doseq … (when … expr))) if you need to return expr

How do I simplify Compojure routes?

I have the following code to define my routes in Compojure:
(ns my-project.my-test
(:gen-class)
(:require
[my-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defroutes app
(wrap-request
(GET "/" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(wrap-request
(GET "/page1" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(wrap-request
(GET "/page2" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(wrap-request
(GET "/page3" request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))}))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
That works but it seems like I should be able to simplify it like this:
(ns my-project.my-test
(:gen-class)
(:require
[my-test.template-views :refer :all]
[compojure.core :refer [defroutes GET POST context]]
[compojure.route :as route]
[org.httpkit.server :refer [run-server]]))
(defn wrap-request
[handler]
(fn [request]
(let [{remote-addr :remote-addr uri :uri scheme :scheme request-method :request-method} request]
(println (str "REQUEST: " request)))
(handler request)))
(defn wrap-template
[route]
(wrap-request
(GET route request
{:status 200
:headers {"Content-Type" "text/html"}
:body (template-body (:uri request))})))
(defroutes app
(map wrap-template ["/" "/page1" "/page2" "/page3"])
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
However, when I do, I get this error backtrace:
Sat Apr 24 22:38:33 MDT 2021 [worker-2] ERROR - GET /page2
java.lang.ClassCastException: class clojure.lang.LazySeq cannot be cast to class clojure.lang.IFn (clojure.lang.LazySeq and clojure.lang.IFn are in unnamed module of loader 'app')
at compojure.core$routing$fn__368163.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2705)
at clojure.core$some.invoke(core.clj:2696)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:669)
at clojure.core$apply.invoke(core.clj:662)
at compojure.core$routes$fn__368167.invoke(core.clj:192)
at org.httpkit.server.HttpHandler.run(RingHandler.java:117)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Is there something about using (map) that is wrong here?
routes (and thus defroutes) expects each argument to be a request handler function. A list of handlers is not a handler function; hence the error. Happily, there is a function to convert a list of handlers to a single handler: routes! Since it wants N separate arguments, rather than a single list, you will need apply as well. So:
(defroutes app
(apply routes (map wrap-template ["/" "/page1" "/page2" "/page3"]))
(route/resources "/")
(route/not-found {:status 404
:headers {"Content-Type" "text/html"}
:body "<h1>Not Found</h1>"}))
As an aside, I generally suggest not using defroutes, simply because it does not compose as easily as separate def + routes, and for beginners it leads to forgetting that anything but defroutes exists, when in fact most interesting servers will want to apply a function to some of their routes.

post-redirect? not working in liberator or Preflight response is not successful

I have the following code:
defresource handle-sign-in [redirect-uri]
:available-media-types ["application/json"]
:allowed-methods [:post]
:post! (prn "welcome to post")
:post-redirect? (fn [_] ;;(ring-response
{:location redirect-uri}
;;)
)
)
When I send the request, I get the errors Preflight response is not successful and XMLHttpRequest cannot load [authorize-uri] due to access control checks.
When I wrap the redirect location map around ring-response, however, I don't get the errors but neither do I get the redirect in the browser. What am I doing wrong?
-- EDIT --
This is my system.components config.
(defn config []
{:http-port (Integer. (or (env :port) 5000))
:middleware [[wrap-defaults api-defaults]
wrap-with-logger
wrap-gzip
ignore-trailing-slash
[wrap-reload {:dir "../../src"}]
[wrap-trace :header :ui]
wrap-params
wrap-keyword-params
wrap-cookies
[wrap-cors #".*"]
]})
and this is what my new resource looks like:
(defresource handle-sign-in [authorize-uri]
:available-media-types ["application/json"]
:allowed-methods [:post]
:post-redirect? true
:as-response (fn [d ctx]
(-> (as-response d ctx) ;; default implementation
(assoc-in [:headers "Access-Control-Allow-Origin"] "*")
(assoc-in [:headers "Access-Control-Allow-Headers"] "Content-Type")
)
)
:location authorize-uri
)
But I still get the "No 'Access-Control-Allow-Origin' header is present on the requested resource." error.
Please read about CORS. You'd need to either implement the OPTIONS method in the resource or wrap your handler with appropriate middleware, e.g. ring-cors.

How do you set up middleware in Clojure using Reitit to enable coercion of body params?

I am trying to set a a Reitit router that performs coercion. I can get the response section working but I can't seem to get the body coercion to work properly. The following is the code I am using:
(ns example
(:require
[ring.middleware.json :refer [wrap-json-body wrap-json-response]]
[ring.middleware.reload :refer [wrap-reload]]
[ring.util.response :refer [response]]
[reitit.ring.coercion :as rrc]
[reitit.coercion.malli]
[reitit.ring :as ring]))
(def router
(ring/ring-handler
(ring/router
[["/healthz" {:get (fn [_] {:status 200 :body "healthy"})}]
["/api" {:coercion reitit.coercion.malli/coercion
:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}
["/messages" {:post {:summary "Add a new message"
:parameters {:body [:map [:name string?]]}
:responses {200 {:body [:map
[:message string?]]}}
:handler (fn [req]
{:status 200 :body {:message (:name (:body req))}})}}]]]
{:data {:middleware []}})
(ring/create-default-handler)))
(def app (-> #'router
(wrap-reload)
(wrap-json-response)
(wrap-json-body {:keywords? true :bigdecimals? true})))
The coercion is failing even when I send the correct body parameters with the following error:
{
"schema": "[:map {:closed true} [:name string?]]",
"errors": [
{
"path": [],
"in": [],
"schema": "[:map {:closed true} [:name string?]]",
"value": null,
"type": "malli.core/invalid-type",
"message": "invalid type"
}
],
"value": null,
"type": "reitit.coercion/request-coercion",
"coercion": "malli",
"in": [
"request",
"body-params"
],
"humanized": [
"invalid type"
]
}
This seems to be indicating that the coercion can't find any body parameters. I assume this has to do with the way I am setting up my middleware but I can't seem to fix this. How should I set the middleware up so the Reitit coercion works properly in this case?
I was able to get coercion to work by removing the wrap-json-response and wrap-json-body middleware and replacing it with the muuntaja middleware as follows:
(def router
(ring/ring-handler
(ring/router
[["/healthz" {:get (fn [_] {:status 200 :body "healthy"})}]
["/api" {:coercion reitit.coercion.malli/coercion
:middleware []}
["/messages" {:name ::message
:post {:summary "Add a new message"
:parameters {:body [:map [:name string?]]}
:responses {200 {:body [:map
[:message string?]]}}
:handler (fn [{:keys [parameters]}]
{:status 200 :body {:message (:name (:body parameters))}})}}]]]
{:data {:muuntaja m/instance
:middleware [params/wrap-params
muuntaja/format-middleware
rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})
(ring/create-default-handler)))

Compojure Routes Issues

I have a small compojure site, with the routes defined as such:
(defroutes example
(GET "/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (home)})
(GET "/*" (or (serve-file (params :*)) :next))
(GET "/execute/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (execute-changes)})
(GET "/status/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (status)})
(route/not-found "Page not found"))
When I try to load the project, I get this error:
java.lang.Exception: Unsupported binding form: (or (serve-file (params :*)) :next)
What am I doing wrong? I took most of this from scattered examples on the internet.
After adding the empty vector, I get this error:
java.lang.Exception: Unable to resolve symbol: serve-file in this context
I think you're missing a binding form:
(GET "/*" {params :params} (or (serve-file (params :*)) :next))
; ^- note the binding form