error while composing routes in compojure - clojure

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.

Related

Unable to access form paramaters when using nested defroutes in Compojure

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

Registering multiple handlers while running server

Is there any way to register multiple handlers while running an http-kit server:
(defroutes rest-main-app
(GET "/" "Welcome"))
(defroutes rest-events-app
(GET "/events" "Event API"))
(defn -main []
(run-server rest-main-app {:port 5000}))
How can I pass both routes to the run-server e.g both rest-main-app and rest-events-app ?
You can use compojure's routes function. You can also pass several handlers to defroutes, an example is provided below:
(defroutes get-routes
(GET "/events" [] "Event API")
(GET "/" [] "Welcome"))
(defroutes post-routes
(POST "/events" [] "Post Event API"))
(def all-routes
(routes
get-routes
post-routes))
(defn -main []
(run-server all-routes {:port 5000}))

How do I add webjars resources to lib-noir's app-handler?

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"))

Compojure: Nested route issue

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"))

Compojure: how to map query parameters

I'm trying to make any of the following mappings work to map http://mysite.org/add?http://sitetoadd.com or http://mysite.org/add?u=http://sitetoadd.com
(GET "/add?:url" [url] url)
(GET "/add?u=:url" [url] url)
(GET "/add" {params :params} (params :u))
(GET "/add" {params :params} (params "u"))
(GET "/add" [u] u)
But it just fails and I don't know why. On the other hand, this works:
(GET "/add/:url" [url] url)
but I can't use it because I have to pass a url and http://mysite.org/add/http://sitetoadd.com is invalid while http://mysite.org/add?http://sitetoadd.com is ok.
EDIT: dumping request i've seen that params is empty. I thought that it would contain POST and GET parameters, but the only place where I can find the params I pass is in :query-string ("u=asd"). It seems that a middleware is needed to parse query strings. My question stands still, by the way.
See https://github.com/weavejester/compojure under Breaking Changes. The params map is no longer bound by default. If you have your example routes inside a "(defroutes main-routes ... )", make sure you activate it through "(handler/site main-routes)" as explained on https://github.com/weavejester/compojure/wiki/Getting-Started as it is the site or api method that makes sure the params map gets bound by default.
Here's an example that works:
(ns hello-world
(:use compojure.core, ring.adapter.jetty)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes main-routes
(GET "/" {params :params} (str "<h1>Hello World</h1>" (pr-str params)))
(route/not-found "<h1>Page not found</h1>"))
(defn -main [& m]
(run-jetty (handler/site main-routes) {:port 8080}))