Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to set and get cookie with compojure?

Created project using lein new compojure project-name and have the server referencing (wrap-defaults project-name site-defaults).

Inside my handler I am calling controllers and passing the params to those controllers. Project structure:

handler.clj ;; ring accesses def app
controllers/app.clj ;; where route params are sent
example: (GET "/signup" {params :params} (controller-app/signup params))

So the issue I am having is I cannot figure out how to get or set cookies from the controllers file. Things I have tried:

  • passing cookie as a param: {cookies :cookies}. I was able to view the default cookie but could not set any data.
  • Use cookie-response and cookie-request. Same problem of not being able to add to the cookie.
  • Using the :cookie in every route possible and getting nothing back.

Any help would be much appreciated. There is not much documentation on this so unfortunately the problem has taken a fair amount of time.

like image 211
user275667 Avatar asked Jan 29 '23 10:01

user275667


2 Answers

Finally was able to solve it by brute force. It's no joke when people say the documentation for Clojure is pretty sparse. A couple of notes on ring:

  • Sessions are not what you think of when you hear sessions. It's just a signed cookie and a poorly documented one at that. Just ignore it. The few tutorials I did find constantly misspoke and used "sessions" when they meant "signed cookie". The documentation even uses cookies and sessions interchangeably. Why? I have no idea as they are completely separate methods of storing data. Sessions are a server side in memory store and cookies are a client side browser store.

  • :headers are needed otherwise the cookie will just download into an empty text file. Took forever to find out why this was the case.

  • :path has to go into the cookie body otherwise the cookie will only persist for the page where the cookie was set. You would think :path would go after :cookies and above :body. It makes no sense to me why the :path would be included in the hashmap along with the value. Again, no documentation for how or why so this took forever as well.

Now onto how to do this:

Here you pass the cookie to the controller from inside your handler. Cookies are available by default if you used "lein new compojure app-name" to create your app. I had to read the source code to figure this out.

default namespace (app-name/handler.clj) -

(ns some-namespace.handler
  [compojure.core :refer :all]
  [compojure.route :as route]
  [ring.middleware.defaults :refer [wrap-defaults site-defaults]
  [app-name.controllers.home :as home-controller))

Your app routes (app-name/handler.clj) -

(defroutes app-routes
  (GET "/" {cookies :cookie} (home-controller/home cookies)))

Here is how you set the cookie itself (app-name/controllers/home.clj).

(ns app-name.controllers.home)

(defn home [cookies]
  {:headers {"Content-Type" "Set-Cookie"},
   :cookies {"cookie-name" {:value "cookie-value", :path "/"}},
   :body "setting a cookie"})

Bottom of handler.clj using default wraps for specified routes (app-name/handler.clj)

(def app
  (wrap-defaults app-routes site-defaults ))

This was a very simple problem that turned out to be far more complex. It took me 3 days to figure all of the above out. I am new to Clojure/Ring/Compojure but this has been the worst experience I have had yet programming.

It's really a problem of just enough abstraction to become dangerous (so basically nothing is obvious). Libraries like Ring REALLY need to be better documented and over explained if wider adoption is wanted.

Anyways, I hope this helps someone.

like image 101
user275667 Avatar answered Feb 04 '23 19:02

user275667


I personally spent quite some time to making cookie reading work due to my poor understanding of destructuring in compojure.

After wrapping yout application with the wrap-cookies handler, you need to destructure cookies variable "the clojure way" and not "the compojure way" since the cookies map is not in the request params map.

Example that doesn't work :

  (GET "/read-cookie" [cookies]
   (let [name (get-in cookies ["name" :value] "")]
     (if (empty? name)
       (layout/common [:h1 "I don't read a cookie"])
       (layout/common [:h1 (str "I read a cookie : " name)]))))

Example that works :

  (GET "/read-cookie" {:keys [cookies]} ;; or {cookies :cookies}
   (let [name (get-in cookies ["name" :value] "")]
     (if (empty? name)
       (layout/common [:h1 "I don't read a cookie"])
       (layout/common [:h1 (str "I read a cookie : " name)]))))

Hope this helps,

like image 42
Michel A. Avatar answered Feb 04 '23 19:02

Michel A.