I am writing a web API using Compojure with basic-authentication middleware. The basic authentication part looks something like:
(defn authenticated? [id pass]
(and (= id "blah")
(= pass "blah"))
id and pass are passed in using the id:pass#website technique. My problem is, I would like to access this id and pass further on down where the routes are processed with the various GET, PUT, POST, etc. headings. But I can't figure out how to do this, and haven't had any luck googling around.
; i'd like to access id and pass here
(defroutes routes
(GET "/" [] {:status 200 :headers {} :body "hi!"})
...)
I presume the solution is for the above to somehow "add" id and pass to some set of variables that can be accessed where the routes are processed, but I have no idea how to add nor to access them.
Hopefully someone can point me in the right direction - thanks.
Assuming you are talking about https://github.com/remvee/ring-basic-authentication, the authenticated? fn can return a truthly value that will be added to the request in the :basic-authentication. Something like (untested):
(defn authenticated? [id pass]
(and (= id "gal")
(= pass "foo")
{:user id :passwd pass}))
(defroutes routes
(GET "/" {the-user :basic-authentication} {:status 200 :headers {} :body (str "hi! Mr. " (:user the-user) " your password is " (:passwd the-user))})
...)
The return of the authenticated? method is associated in request map referenced by the key :basic-authentication. Here goes an example with a route that returns the user. You could, however, return a map or any other object and access it through :basic-authentication key.
(defn authenticated? [user password] user)
(defroutes require-auth-routes
(GET "/return-user" request (.toString (:basic-authentication request)))
(def my-app
(routes
(-> require-auth-routes
(wrap-basic-authentication authenticated?)))
Related
I have a server with an endpoint .../end2 which I send parameters to, such as:
.../end2?a=2&b=1
How do I get a map {:a 2 :b 1}? I thought (:params request) is the way to go but I get an empty map..
Assuming you're using compojure, the params are not automatically bound to the request, and ring middleware must be applied to do this:
(defroutes app-routes
(GET "/end2" request (str (:params request))))
(def app
(-> app-routes
ring.middleware.params/wrap-params))
(run-server #'app {:port 8888})
You need to add ring middle ware to parse params. You could check ring default
You don't have to worry about nested params or others.
I have POSTed data to a Pedestal endpoint "/my-post. I have routed that end point as such:
[[["/" {:get landing} ^:interceptors [(body-params/body-params) ...]
["/my-post {:post mypost-handler}
....
So to my mind this means that the body-params interceptor will fire for /my-post too.
In mypost-handler I have:
(defn mypost-handler
[request]
****HOW TO ACCESS THEN FORM DATA HERE ****
)
How do I now access the form data here? I can see from printing the request that I have a #object[org.eclipse.jetty.sever.HttpInputOverHTTP..] which will clearly need further processing before it is useful to me.
(I must say, the documentation for Pedestal is pretty sketchy at best...)
Something like this should work. Note the body-params interceptor on the mypost-handler route
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; json-params is the posted json, so
;; (:name json-params) will be the value (i.e. John) of name property of the posted json {"name": "John"}
;; handle request
{:status 200
:body "ok"})
(defroutes routes
[[["/mypost-handler" {:post mypost-handler}
^:interceptors [(body-params/body-params)]
]
]])
The mypost-handler is acting as a Ring handler, i. e. it should accept a Ring request map and return a Ring response map. Thus, you can expect a typical Ring request structure:
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; handle request
{:status 200
:body "ok"})
Here's more relevant info on defining such handlers in your route tables.
I'm trying to access basic session data in a ring middleware to, in my case, copy user data into the request for easier access.
(def app
(->
(handler/site app-routes)
(wrap-user-auth) ; <--- my middleware
(session/wrap-session {:store (cookie-store {:key "something super secret"})})))
Middleware:
(defn wrap-user-auth [handler]
(fn [request]
(println request )
(let [user (get (request :session) :user nil)]
(let [response
(handler
(if-not (nil? user)
(assoc request :user user :is_authenticated? true)
(assoc request :user nil :is_authenticated? false)))]
response
))))
Session data is always empty when printing request, even though it has been set and is accessible through the views. I tried messing around with middleware order but can't figure out why the atom is empty.
Some random route where session data is available
(ANY "/foo" request (println (request :session)))
Found a solution.
Turns out this problem is 'somewhat' related to Compojure/Ring: Why doesn't a session with cookie-store survive a server restart?, which explains that 2 session middleware are being used: One by compojure, one by wrap-session. After changing both middleware to the same storage engine, the atom is filled with my session data - although I don't quite understand why.
I am putting this as an answer rather than a comment since it solves the actual problem. Happy to hear why this is happening though!
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]
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.