How do I add webjars resources to lib-noir's app-handler?
I used to do this only using Ring like this:
(def app
(-> handler
(wrap-resource "public")
(wrap-resource "/META-INF/resources")
;;resources from webjars
))
Now I'm trying to figure out how to do this with lib-noir.
I tried this:
(def app (noir-middleware/app-handler [home-routes app-routes]
:ring-defaults {:static
{:resources
"/META-INF/resources"}}))
and it works, but I get a problem when posting forms after configuring this. The params are empty in the ring request now.
This seems to do it:
(defroutes app-routes
(route/resources "/")
(route/resources "/" {:root "META-INF/resources/"})
(route/not-found "Not Found"))
Related
after doing web development for ages and discovering Clojure a year ago, I want to combine these two things.
After starting with Compojure, I try to implement authentication by using a middleware which responds with a 403 code, telling the user to authenticate.
This is my code:
(defn authenticated? [req]
(not (nil? (get-in req [:session :usr]))))
(defn helloworld [req]
(html5
[:head
[:title "Hello"]]
[:body
[:p "lorem ipsum"]]))
(defn backend [req]
(html5
[:head
[:title "Backend"]]
[:body
[:p "authenticated"]]))
(defroutes all-routes
(GET "/" [] helloworld)
(context "/backend" []
(GET "/" [] backend)))
(defn wrap-auth [handler]
(fn [req]
(if (authenticated? req)
(handler req)
(-> (response "Nope")
(status 403)))))
(def app
(-> (handler/site all-routes)
(wrap-auth)
(wrap-defaults site-defaults)))
Here comes the funny part: If I run the code as shown above, Firefox breaks with the error message "File not found". Opening the debug toolbar, I see a 403 response and the content "Tm9wZQ==" which is base 64 decoded the "Nope" from my auth middleware function. When I put wrap-auth after wrap-defaults everything works fine.
I want to understand what's going on there. Can you help me?
It's really difficult to say what's going on under the hood. The wrap-defaults middleware brings lots of stuff, maybe 10 or more wrappers at once. You'd better to examine its source code and choose exactly what you need.
I may guess that, for some reason, the Ring server considers your response being a file, so that's why it encodes it into base64. Try to return a plain map with proper headers as follows:
{:status 403
:body "<h1>No access</h1>"
:headers {"Content-Type" "text/html"}}
I'm unable to access form parameters from a POST request. I've tried every combination of middleware and config options I've seen in the docs, on SO, etc. (including the deprecated compojure/handler options) and I'm still unable to see the parameters. I'm sure I'm missing something very obvious, so any suggestions (no matter how slight) would be greatly appreciated.
Here's my latest attempt, wherein I try to use the site-defaults middleware and disable the anti-forgery/CSRF protection provided by default. (I know this is a bad idea.) However, when I try to view the page in question in a web browser, the browser tries to download the page, as if it were a file it wasn't capable of rendering. (Interestingly, the page is rendered as expected when using Curl.)
Here's the latest attempt:
(defroutes config-routes*
(POST "/config" request post-config-handler))
(def config-routes
(-> #'config-routes*
(basic-authentication/wrap-basic-authentication authenticated?)
(middleware-defaults/wrap-defaults (assoc middleware-defaults/site-defaults :security {:anti-forgery false}))))
Previous attempt:
(def config-routes
(-> #'config-routes*
(basic-authentication/wrap-basic-authentication authenticated?)
middleware-params/wrap-params))
UPDATE:
The parameters appear to be swallowed by the outer defroutes:
(defroutes app-routes
(ANY "*" [] api-routes)
(ANY "*" [] config-routes)
(route/not-found "Not Found"))
So, my question now becomes: How can I thread the parameters through to the nested defroutes?
My temporary solve is based on this solution, but Steffen Frank's is much simpler. I will try that and follow-up.
UPDATE 2:
In trying to implement the suggestions provided by both of the current answers, I'm running into a new issue: route matches are overeager. e.g. given the following, POSTs to /something fail with a 401 response because of the wrap-basic-authentication middleware in config-routes.
(defroutes api-routes*
(POST "/something" request post-somethings-handler))
(def api-routes
(-> #'api-routes*
(middleware-defaults/wrap-defaults middleware-defaults/api-defaults)
middleware-json/wrap-json-params
middleware-json/wrap-json-response))
(defroutes config-routes*
(GET "/config" request get-config-handler)
(POST "/config" request post-config-handler))
(def config-routes
(-> #'config-routes*
(basic-authentication/wrap-basic-authentication authenticated?)
middleware-params/wrap-params))
(defroutes app-routes
config-routes
api-routes
(route/not-found "Not Found"))
(def app app-routes)
The issue is that when you define your routes in this way:
(defroutes app-routes
(ANY "*" [] api-routes)
(ANY "*" [] config-routes)
(route/not-found "Not Found"))
then any request will be matched by api-routes as long as it returns non-nil response. Thus api-routes does not swallow your request params but rather stealing the whole request.
Instead you should define your app-routes as (preferred solution):
(defroutes app-routes
api-routes
config-routes
(route/not-found "Not Found"))
or make sure that your api-routes returns nil for unmatched URL path (e.g. it shouldn't have not-found route defined).
Just a guess, but have you tried this:
(defroutes app-routes
api-routes
config-routes
(route/not-found "Not Found"))
You may find the following post useful. It talks about mixing api and app routes such that they don't interfere with each other and you avoid adding middleware from one to the toher etc. Serving app and api routes with different middleware using Ring and Compojure
I'm trying to set up routes in my application such that:
/:locale/ -> Home, with locale binding
/:locale/search -> Search,
with locale binding
Thus far, my routing code is:
(defn controller-routes [locale]
(home/c-routes locale)
(search/c-routes locale)))
(defroutes app-routes
(route/resources "/")
(context "/:locale" [locale]
(controller-routes locale))
no-locale-route
(route/not-found "Not Found"))
search/c-routes:
(defn c-routes [locale]
(GET "/search" [] (index locale)))
home/c-routes:
(defn c-routes [locale]
(GET "/" [] (index locale)))
I can't understand why this doesn't work properly, but currently "/uk/search/" matches correctly, but "/uk/" gives the 404 page.
Any help would be appreciated. Thanks.
controller-routes is a normal function which as of now returns the last route i.e search and hence only search works. What you need is make controller-routes a route using defroutes and changing the c-routes as well:
search/c-routes:
(def c-routes (GET "/search" [locale] (index locale)))
home/c-routes:
(def c-routes (GET "/" [locale] (index locale)))
Where you use above routes:
(defroutes controller-routes
home/c-routes
search/c-routes)
(defroutes app-routes
(route/resources "/")
(context "/:locale" [locale]
controller-routes)
no-locale-route
(route/not-found "Not Found"))
The following compojure routes work.
(defroutes app-routes
(GET "/" [] (index))
(GET "/twauth" [] (tw/authorize))
(ANY "/twcallback" [] (do
(tw/callback)
(index)))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site app-routes))
However I get error with the following. It throws a java.nullpointer.exception. What am I doing wrong here ?
(defroutes app-routes
(GET "/" [] (index))
(GET "/twauth" [] (tw/authorize))
(ANY "/twcallback" [] (do
(tw/callback)
(index))))
(defroutes base-routes
(route/resources "/")
(route/not-found "Not Found"))
(def app
(-> app-routes
base-routes
handler/site))
Your base-routes matches all requests. I think the following illustrates it better:
(defroutes base-routes
(route/not-found "Not found"))
(def app
(-> app-routes
base-routes
handler/site))
No matter what you do in app-routes above, base-routes is checked after and will always return not-found. Note that each request is threaded through both routes, not first match wins.
So you need to either move the base-routes into your app-routes as fallbacks -- like you already did in your working example -- or compose them in app:
(def app
(-> (routes app-routes base-routes)
handler/site))
Here the composed routes ensures that the first match wins.
My Compojure web app ([compojure "1.0.1"]) always receives an empty parameter map, despite adding wrap-params etc. Code sample below:
(defroutes public-routes
(PUT "/something" {params :params}
(println (str "Params: " params))
(do-put-something params)))
(def myapp
(-> public-routes
ring-params/wrap-params))
(defn start-server []
(future (jetty/run-jetty (var myapp) {:port 8080})))
I've tried adding the wrap-params, wrap-keyword-params and wrap-multipart-params but when I PUT to the endpoint using httpie (or my client), I find that params is always empty. Can anyone help?
Thanks!
The only problem with your example code was that it lacks a ring response hash-map in the route body. The solution is evaluate to a ring response instead of using println. When you call println in your route it prints to standard out where the server process is running, which has nothing to do with the response to the API call.
(defroutes public-routes
(PUT "/something" {params :params}
{:status 200
:body (str "Params: " params)}))
This produces a 200 response with Params: {"foo" "bar"} as the response body.
I am using this to test your PUT route:
curl -X PUT -d "foo=bar" http://127.0.0.1:8080/something