wrap-file through a specific route - clojure

I am setting up static resources for my compojure app. I need to use wrap-file instead of wrap-resource since I need the static files to be served from the File System.
I followed this wiki and configured wrap-file
Now I am able to serve my static assets from http://localhost/static-file.css
What I want to do is to serve my static assets in a specific context http://localhost/context/static-file.css

With Compojure routes, it is important to remember that any route can be wrapped in middleware, not just the top level collection of routes. With that in mind, we can use Compojure's context to mount the file system routes where needed.
(defroutes app-routes
(GET "/" [] "Hello World")
(context "/context" []
(-> (route/not-found "File Not Found")
(wrap-file "path-to/static-files")))
(route/not-found "Not Found"))
(def app
(-> app-routes
(wrap-defaults site-defaults)))

Related

How to use site-defaults middleware for one set of routes and api-defaults for another set of routes?

In my Compojure/Ring web application's handler, I need to serve one set of routes using the site-defaults middleware, and another separate set of routes using the api-defaults middleware. How can I do that?
The code below only serves one set of routes using the site-defaults middleware. What should I add to serve the second set of routes (api-routes) using the api-defaults middleware?
(web-experiment.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults
site-defaults
api-defaults]]
[web-experiment.views :refer :all]))
(defroutes app-routes
(GET "/" [] (index-page))
(GET "/about" [] (about-page))
(route/not-found "Not Found"))
(defroutes api-routes
(GET "/grapefruit" [:as {body :body}] (grapefruit-api body))
(GET "/factory" [:as {body :body}] (factory-api body))
(GET "/umbrella" [:as {body :body}] (umbrella-api body))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
;; TODO: Add api-routes. How to use api-defaults middleware to serve api-routes?
I've read these:
Serving app and api routes with different middleware using Ring and Compojure - Does not solve the problem because the solution presented does not work with the wrap-defaults middleware using the site-defaults configuration.
https://github.com/ring-clojure/ring-anti-forgery/pull/14 - Does not provide a clear solution (i.e. code snippet) to the problem I have.
You can just wrap some of your routes in one wrapper and some others in the other wrapper. You just need to organize your routes a bit differently.
Some time ago I wrote a demo what you can do with ring routes. You can find it at https://github.com/ska2342/ring-routes-demo/
In particular the part starting at line 70 of the demo should be interesting to you.
I'm not sure how this could be solved in Compojure, but you may wish to consider using Pedestal. This page provides a good introduction to the route matching process, which takes place before any interceptors are called (the Pedestal replacement for Ring middleware).
So you could define two different sets of middleware:
(def user-intc-chain [inject-connection auth-required (body-params/body-params)] )
(def api-intc-chain [inject-connection auth-required api-params-intc] )
and then define routes like:
["/echo" :get (conj user-intc-chain echo-intc) ] ; a "user" route
["/alive?" :get (conj api-intc-chain alive-intc) ] ; an "api" route
In addition to the built-in Pedestal features, I have a number of helper & convenience functions documented here: https://cloojure.github.io/doc/tupelo/tupelo.pedestal.html

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

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

Absolute Route URLs with Ring and Compojure

I need to redirect users to an absolute URL following oAuth authentication.
How do I construct an absolute URL for a Compojure route? Non-AJAX HTTP requests seem to omit the Origin header. Is there a Ring or Compojure helper function to build absolute URLs, or should I do this manually with the scheme and Host headers?
Lastly, and probably deserving of a separate question, are there helper functions in Compojure to generate route URLs based on the handler, ala Html.ActionLink(...) in MVC land?
The ring-headers project has a middleware that transforms relative to absolute urls:
(ns ring.middleware.absolute-redirects
"Middleware for correcting relative redirects so they adhere to the HTTP RFC."
(:require [ring.util.request :as req])
(:import [java.net URL MalformedURLException]))
(defn- url? [^String s]
(try (URL. s) true
(catch MalformedURLException _ false)))
(defn absolute-url [location request]
(if (url? location)
location
(let [url (URL. (req/request-url request))]
(str (URL. url location)))))

How to set default route?

Using compojure how to set default route, e.g.
(defroutes app
(GET '/api/user/:id/' [] show-user)
(default-handler render-template)) ; this is what I want
Is there anyway to achieve this? I'm aware with not-found, but it give me 404 http status.
You can simply set a handler to /:
(defroutes app
(GET "/api/user/:id/" [] show-user)
(GET "/" render-template))
Or if you want to default any HTTP verb:
(defroutes app
(GET "/api/user/:id/" [] show-user)
(ANY "/" render-template))
Compojure routes match top to bottom so anything that hasn't been matched will fall back to your / handler.