Compojure route params empty - clojure

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

Related

Compojure triggers "File not found" error in Firefox if middlewares are in the wrong order

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

Getting the http request when using compojure.route/not-found in Clojure

I'm building a server using Cloujre's Compojure. The default route is compojure.route/not-found, is there a way of getting the request that reached this route? I'd like print all requests that end up there.
You can use this kinda approach:
(def handler (-> your-routes
wrap-my-request-middleware ;; it has to be in this order
...))
Let's log in here the uri
(defn wrap-my-request-middleware
[handler]
(fn [request]
(let [response (handler request)]
(when (= 404 (:status response))
;; do whatever you like in here
(log/info (str "Request path: " (:uri request))))
response)));; fn needs to return reponse...

using a custom :store with wrap-multipart-params in ring middle ware

I'm trying to use a custom :store option for wrap-multipart-params and instead I'm clearly getting the default store. My custom function isn't even being called.
(mp/wrap-multipart-params
(POST "/upload-x" request (upload/upload-file request))
{:store upload/logging-store})
My logging store function looks like this (it's just a dummy for now - eventually I want to handle the stream in a custom way) None of that IO happens.
(defn logging-store [{filename :filename
content-type :content-type
stream :stream
:as params}]
(println "in logging store")
(pprint filename)
(pprint params)
filename)
upload-file looks like this:
(defn upload-file [{params :params
session :session :as request}]
(let [user-id (:user-id session)
files (get params "files")]
(pprint request)
(pprint params)
(response/response
{:status :success})))
The printing for the request and the params clearly show the multipart params in there and that they are being handled by the temp file store:
:multipart-params
{"files"
{:size 1674,
:tempfile
#<File /var/folders/rx/9ntjyyvs35qbmcbp6rhfmj200000gn/T/ring-multipart-3853352501927893381.tmp>,
:content-type "application/octet-stream",
:filename "blog-test.clj"}},
EDIT: app definition (as requested)
(defroutes file-list-routes
(GET "/simple-upload" request (upload/simple-upload-file request))
(mp/wrap-multipart-params
(POST "/upload-x" request (upload/upload-file request))
{:store upload/logging-store})
)
(defroutes scratch-app
(context "/files" request file-list-routes)
(route/resources "/")
(route/not-found "Page not found"))
(def my-app
(handler/site
(ring.middleware.json/wrap-json-response
(ring.middleware.json/wrap-json-params
(ring.middleware.stacktrace/wrap-stacktrace
(ring.middleware.session/wrap-session
scratch-app
{:store (ring.middleware.session.memory/memory-store all-the-sessions)
:cookie-attrs {:max-age 3600 ;in s 3600s = 1h
}}))))))
(ring.util.servlet/defservice my-app)
The compojure.handler/site function contains the wrap-multipart-params middleware, so you're unknowingly applying the multipart middleware twice: first with the defaults, then with your custom options. Because the multipart middleware with the default options is applied first, that take priority.

How can I intercept a Compojure request and execute it based on a test?

I have some routes.
(defroutes some-routes
(GET "one" [] one)
(GET "two" [] two))
(defroutes other-routes
(GET "three" [] three)
(GET "four" [] four))
(defroutes more-routes
(GET "five" [] five)
(GET "six" [] six))
(def all-routes
(routes app-routes
(-> some-routes session/wrap-session my-interceptor)
(-> more-routes session/wrap-session my-other-interceptor)
other-routes))
I want to intercept the some-routes but not other-routes and perform a test based on the request (checking that a key exists in the session and some other stuff). I have more than one of these. my-other-interceptor does the same kind of thing but different.
So I start with this:
(defn my-interceptor [handler]
(fn [request]
(prn (-> request :session :thing-key))
(let [thing (-> request :session :thing-key-id)]
(if (nil? thing)
(-> (response "Not authenticated"))
(handler request)))))
This will allow access to the handler if :thing-key is set in the session.
Unfortunately this doesn't play nicely with having more than one set of routes. This check should only apply to some-routes and not other-routes. But until we execute the handler we don't know if the route matches. And at that point the handler has already executed. I could rewrite it to execute handler and then only perform the check if the response is non-nil, but this means I've executed a handler before checking for auth.
I followed this example, which exhibits the problem:
(defn add-app-version-header [handler]
(fn [request]
(let [resp (handler request)
headers (:headers resp)]
(assoc resp :headers
(assoc headers "X-APP-INFO" "MyTerifficApp Version 0.0.1-Alpha")))))
How do I do this? What I want is:
a way of checking a response (and some other logic) before handling a request
that I can apply to a large-ish set of routes' handlers
that isn't applied to all routes in the app
I will have more than one such handler doing a different kind of check on the session
How should I go about doing this?
The way to have separate handlers or middlewares is to decompose your routes using compojure.core/routes and use your handler only where you need it.
In your case, if you put your other-routes first, your problem should be solved.
As in:
(def app-routes
(compojure.core/routes
other-routes
(-> some-routes
session/wrap-session
my-interceptor)))
Remember compojure routes are just ring handlers, you can always write a custom defroutes that calls your handler only if the route matches the request, this is the make route source code
(defn make-route
"Returns a function that will only call the handler if the method and Clout
route match the request."
[method route handler]
(if-method method
(if-route route
(fn [request]
(render (handler request) request)))))
That way, if you have more than one conditioned handler you don't need to rely on putting those routes at the end of the composition.
Notice that approach is in case you want to keep your route handling code clean.
(my-def-routes routes
(GET "/" request (show-all request))
If you don't wanna roll your own defroutes just call your interceptor inside:
(defroutes routes
(GET "/" request (interceptor request show-all))
(defn interceptor
[request handler]

compojure defroutes - route sometimes not recognized

I have a clojure / compojure webapp with the following routes
(defroutes my-routes
(GET "/app/preview" request (my-preview-function request))
(ANY "*" request (str "ANY page <br>" (request :params))))
The preview GET request is made with a couple of parameters. I find this works most of the time but sometimes the /ebook/preview is not found and processing drops to the ANY route, in which case the output is similar to this,
ANY page
{:* "/app/preview", :section "50", :id "48"}
Can anyone suggest what might cause the /ebook/preview request to be skipped? It is definitely a GET request being made; the HTML does not have a POST for the /app/preview URL and to be doubly sure I added a POST route for /app/preview and that was not being hit.
JAR versions:
Clojure 1.2
compojure-0.6.2
ring-core-0.3.7
jetty-6.1.14
ring-jetty-adapter-0.3.1
ring-servlet-0.3.1jar
servlet-api-2.5-6.1.14
Routes are wrapped as follows
(require '[compojure.handler :as handler])
(defn wrap-charset [handler charset]
(fn [request]
(if-let [response (handler request)]
(if-let [content-type (get-in response [:headers "Content-Type"])]
(if (.contains content-type "charset")
response
(assoc-in response
[:headers "Content-Type"]
(str content-type "; charset=" charset)))
response))))
(def app (-> my-routes
handler/site
wrap-stateful-session
(wrap-charset "utf-8")
(wrap-file "public")))
(defn run []
(run-jetty (var app) {:join? false :port 8080}))
If you're trying to figure out what request is causing the problems, stop throwing away the request map with (request :params) and just have a look at request. That will give you a map with all the information Compojure has; you can inspect it, and pass it back into your routes later to observe what happens (after you make some changes, say).
If
(my-preview-function request)
returns nil, then the routing will try the next route. Take a look at (source GET) and see how it matches (or doesn't) your route.