Clojure / Ring: Response map is nil when adding session key - clojure

I want to update a session key on /callback upon logging in. Here's the handler:
(defn callback [{session :session :as request}]
(let [id (:id (get-current-users-profile))
updated-session (assoc session :identity id)]
{:status 200
:headers {"Content-Type" "text/html"}
:body (loading-page)
:session updated-session}))
As long as the session key-value pair is present in the response map, I get a 500 "Response map is nil" response.
Is there something I'm doing wrongly here?
By the way, I can still update the session map (with no errors) on my /logout route by doing the following:
(defn logout [{session :session}]
(-> (resp/redirect "/")
(assoc :session {})))
And here's what calls both callback and logout:
(defroutes main-routes
(GET "/" [] index)
(GET "/login" [] login)
(GET "/callback" [] callback)
(GET "/logout" [] logout))

Related

Compojure-api not respecting body from ring.mock.requests

I'm trying to use the ring.mock.requests library to test an http service. I have this bit of code
(auth-routes
(->
(mock/request :post "/auth/user")
(mock/body {:username "user" :password "pass"})
(mock/content-type "application/json")))
The inner part with the thread-first macro seems to work correctly, but it fails when I try to make the mock request to auth-routes
And this is my route:
(def auth-routes
(context "/auth" []
:tags ["Authentication"]
(POST "/user" []
:return s/Uuid
:body [request m/User]
:summary "Create new user"
(ok (auth/create-user request)))) ...)
This is my schema:
(def User {:username s/Str
:password s/Str})
The exception I'm seeing is
clojure.lang.ExceptionInfo
Request validation failed
{:error (not (map? nil)), :type :compojure.api.exception/request-validation}
It looks like my route is getting nil as the body, and I expect {:username "user" :password "pass"} to be there.
What am I doing wrong and how do I pass a body to this route?
I think you should serialize your data as a json string in your test. Using cheshire:
(cheshire.core/generate-string {:username "user" :password "pass"})
Take a look here or here.

Request map passed to my anonymous function inside a Compojure handler

I'm two days into learning Clojure by writing a simple REST server using ring-clojure and Compojure with ring-json's wrap-json-body middleware.
So far, I have:
A vector users containing the users (with a couple of default users):
(def users [{:id 0 :username "aname"}
{:id 1 :username "anothername"}])
A function (form?) save-user that accepts a map (user) and looks for existing users with the same username. If the username is available, I overwrite the users vector to include the new user before returning HTTP 201. If the username is taken, I simply return HTTP 400:
(defn save-user [user]
(prn users)
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(def users (conj users user))
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
A route for POST /users which calls save-user with the received map:
(defroutes app-routes
(POST "/users" request (save-user (:body request))))
I don't think it matters, but the middleware is applied like this:
(def app
(-> app-routes
(wrap-cors :access-control-allow-origin "*" :access-control-allow-methods "*")
(wrap-json-response)
(wrap-keyword-params)
(wrap-json-body {:keywords? true :bigdecimals? true})
(wrap-defaults (assoc site-defaults :security false))))
My problem:
For whatever reason, the entire request map is passed to the function i pass as then inside the if. Printing it:
(if
(not-any? #(= (:username %) (:username user)) users)
(fn [request]
(prn request))
...
... gives me this:
{:ssl-client-cert nil, :cookies {}, :remote-addr "0:0:0:0:0:0:0:1", :params {}, :flash nil, :route-params {}, :headers {"host" "localhost:3000", "accept" "*/*", "content-length" "42", "content-type" "application/json", "user-agent" "curl/7.43.0"}, :server-port 3000, :content-length 42, :form-params {}, :compojure/route [:post "/users"], :session/key nil, :query-params {}, :content-type "application/json", :character-encoding nil, :uri "/users", :server-name "localhost", :query-string nil, :body {:username "testusername", :password "testpassword"}, :multipart-params {}, :scheme :http, :request-method :post, :session {}}
The same happens if I pass an anonymous function as the if's else. However, nothing wrong happens when I only pass (status ...), like in the code above.
From what I understand, the request map shouldn't be available inside save-user at all, since it's not passed as an argument. Why is it passed to my anonymous function, and is there any way to simply ignore it?
In save-user you are returning a function that takes a request and returns a response (a handler).
I suppose you should instead just return the response directly. Just replace (fn [request] with (do to wrap multiple expressions in a single form.
(def users (atom [{:id 0 :username "aname"}
{:id 1 :username "anothername"}]))
(defn save-user [user]
(if (not-any? #(= (:username %) (:username user)) #users)
(do
(swap! users conj user)
(status
(response (str "Saved user with username: " (:username user)))
201))
(status
(response (str "User with username '" (:username user) "' already exists"))
400)))
I’ve also changed the global users var to be an atom. Redefining global vars from inside a function is a big no-no in Clojure.

how to load another page in cljs after post request using cljs-ajax

I have created a login page login.cljs and handler.clj.here is my code
login.cljs:-
(defn handler [response]
(let[status (aget (clj->js response) "status")
msg (str (aget (clj->js response) "greeting"))]
(if (= status 200)
(home/home-root)
(js/alert "Invalid username or password"))
))
(defn handle-submit [e app owner]
(let [[user user-node] (value-from-node owner "username")
[pass pass-node] (value-from-node owner "password")]
(POST "/hello"
{:params {:username user :password pass}
:handler handler
:error-handler error-handler
:format :json
:response-format :json})
))
(defn login-view [app owner]
(reify
om/IRender(render [this]
(dom/div nil
(dom/h1 nil "Login")
(dom/input #js {:type "text" :placeholder "Your Name" :ref "username"})
(dom/input #js {:type "password" :placeholder "Your Password" :ref "password"})
(dom/button
#js { :onClick (fn [event] (handle-submit event app owner))}
"submit")
))))
(defn say-hello []
(om/root
{} login-view (.getElementById js/document "content")))
home.cljs:-
(defn home-view [app owner]
(reify
om/IRender
(render [this]
(dom/div nil "Welcome"
))))
(defn home-root []
(om/root
{}
home-view
(.getElementById js/document "content"))
handler.clj:-
(defn login-check! [request]
(let [user (get-in request [:params :username])
pass (get-in request [:params :password])]
(cond
(and(= user "admin")(= pass "admin")) (response/response {:greeting user
:status 200})
:else (response/response {:greeting "Invalid username or password"
:status 403}))
))
(defroutes app-routes
(GET "/" [] (response/resource-response "index.html" {:root "public"}))
(route/resources "/")
(POST "/hello" request (login-check! request))
(GET "/home" request (response/response {:body "home"}))
(route/not-found "Not Found"))
I need to load home page view when its a valid login.
here i have called another cljs method 'home/home-root' but its not working.
Here i got one solution..I don't know how much its correct but its working good
I have created a new defn page using reagent as follows:-
(defn page [page-component]
(reagent/render-component [page-component]
(.getElementById js/document "content")))
and called this function from login.cljs handler method:
(defn handler [response]
(page home-root))
now the page method will be called and home-root cljs will be passed to it.

where does ring get the wrap-params information for forms?

I'm trying to delve deeper into ring and have the following code to look at what is going on in the request map:
(require '[ring.adapter.jetty :as serv])
(require '[ring.middleware.params :as wp])
(defn base-handler [params]
{:code 200
:type "txt/html"
:body (str params)})
(defn wp-handler [params]
((wp/wrap-params base-handler) params))
(serv/run-jetty #'wp-handler {:port 8099})
When I want to look at the get query data on the request map, it is there. Say i typed in http://localhost:8099/api/tasks/remove?hello=3 in the browser the response looks something like this:
{:ssl-client-cert nil,
:remote-addr "127.0.0.1",
:scheme :http,
:request-method :get,
:uri "/api/tasks/remove"
:query-string "hello=3"
.... more ....}
and you can see that "hello=3" is in the query string.
But for post, the response has :content-type "application/x-www-form-urlencoded", the entire response looks like this example below and I do not see any form parameters:
{:ssl-client-cert nil, :remote-addr "127.0.0.1", :scheme :http, :request-method :post, :query-string nil, :content-type "application/x-www-form-urlencoded", :uri "/api/tasks/remove", :server-name "localhost", :headers {"user-agent" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11", "origin" "http://localhost:8099", "accept-charset" "ISO-8859-1,utf-8;q=0.7,*;q=0.3", "accept" "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "host" "localhost:8099", "referer" "http://localhost:8099/form.html", "content-type" "application/x-www-form-urlencoded", "cache-control" "max-age=0", "accept-encoding" "gzip,deflate,sdch", "content-length" "23", "accept-language" "en-US,en;q=0.8", "connection" "keep-alive"}, :content-length 23, :server-port 8099, :character-encoding nil, :body #}
My question is in two parts:
What exactly is ring or jetty doing when a form is submitted?
If i'm trying to write my own handler for handling form data, what is a simplified code skeleton of how I am able to access the form query parameters?
I've isolated the 'magic' to this function in params.clj:
(defn- assoc-form-params
"Parse and assoc parameters from the request body with the request."
[request encoding]
(merge-with merge request
(if-let [body (and (urlencoded-form? request) (:body request))]
(let [params (parse-params (slurp body :encoding encoding) encoding)]
{:form-params params, :params params})
{:form-params {}, :params {}})))
in particular this line:
(parse-params (slurp body :encoding encoding) encoding)
but am not sure what it is doing.
Judging by source code of urlencode-form? parse-params is never called as your request is application/x-www-form-urlencoded. Please see description of request types here.
How do you send post request? As far as I understand what happens is parameters are sent in multipart/form-data mode, but request itself is marked as application/x-www-form-urlencoded.

Compojure Routes losing params info

My code:
(defn json-response [data & [status]]
{:status (or status 200)
:headers {"Content-Type" "application/json"}
:body (json/generate-string data)})
(defroutes checkin-app-handler
(GET "/:code" [code & more] (json-response {"code" code "params" more})))
When I load the file to the repl and run this command, the params seems to be blank:
$ (checkin-app-handler {:server-port 8080 :server-name "127.0.0.1" :remote-addr "127.0.0.1" :uri "/123" :query-string "foo=1&bar=2" :scheme :http :headers {} :request-method :get})
> {:status 200, :headers {"Content-Type" "application/json"}, :body "{\"code\":\"123\",\"params\":{}}"}
What am I doing wrong? I need to get at the query-string, but the params map is always empty..
In order to get the query string parsed into the params map, you need to use the params middleware:
(ns n
(:require [ring.middleware.params :as rmp]))
(defroutes checkin-app-routes
(GET "" [] ...))
(def checkin-app-handler
(-> #'checkin-app-routes
rmp/wrap-params
; .. other middlewares
))
Note, that the usage of the var (#'checkin-app-routes) is not strictly necessary, but it makes the routes closure, wrapped inside the middlewares, pick up the changes, when you redefine the routes.
IOW you can also write
(def checkin-app-handler
(-> checkin-app-routes
rmp/wrap-params
; .. other middlewares
))
but then, you need to redefine the handler too, when interactively redefining the routes.