updated atom value not used in compojure route - clojure

I have a (routes (route/not-found)) definition with value derived from an (atom). Though I've updated the atom, the routing retains the initial value. This is similiar to Dynamic handler update in Clojure Ring/Compojure REPL but I'm having a hard time understanding what needs to be de/referenced where.
(ns mveroute
(:require
[org.httpkit.server :as srv]
[compojure.core :as cmpj]
[compojure.route :as route]
[clj-http.client :as client])
(:gen-class))
(def my-atom (atom "foobar"))
(def app
(cmpj/routes
(route/not-found {:status 400 :body #my-atom})))
(defn -main [& args]
(reset! my-atom "hello world")
(srv/run-server #'app {:port 8005})
;; "hello world" as expected
(println #my-atom)
;; still "foobar" but wanted "hello world"
(-> "http://localhost:8005"
(client/get {:throw-exceptions? false})
:body
println))
I thought warp-routes might have come to the rescue. But not how I've used it.
(defn atom-body [] {:status 200 :body #my-atom})
(cmpj/defroutes wrap-found
(route/not-found (atom-body)))
(def app
(cmpj/wrap-routes #'wrap-found {}))
The ultimate goal is a simple cli application that can set the resource/html root directory with command line arguments.

Try something like the following:
(defn not-found-fn
[req]
{:status 400 :body "not found again!"})
(def app
(cmpj/routes
(route/not-found not-found-fn)))
So inside of not-found-fn, you can construct the :body string any way you like. You could also have the string stored in an atom which is dereferenced by not-found-fn.
Side note:
Please see the following to clarify when you should use a Var object instead of just a symbol in your code:
When to use a Var instead of a function?

Related

Clojure and Compojure: Response Map is nil

I'm using compojure for a basic web app, I have this code in core.clj:
(defroutes routes
(GET "/" [] (layout/application "Home" (contents/index)))
(route/resources "/"))
(def application (handler/site routes))
(defn -main []
(let [port (Integer/parseInt (or (System/getenv "PORT") "8090"))]
(jetty/run-jetty application {:port port :join? false})))
When I access the 0.0.0.0:8090 everything is loading ok, but I keep seeing this error:
java.lang.NullPointerException: Response map is nil
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:100)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:95)
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91)
at ring.adapter.jetty$proxy_handler$fn__337.invoke(jetty.clj:27)
at ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:503)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.base/java.lang.Thread.run(Thread.java:844)
Any idea what's going on?
#Svante is almost certainly correct. An easy way to verify is to use the spyx function from the Tupelo library:
(ns demo.core
(:use tupelo.core))
(defroutes routes
(GET "/" [] (spyx (layout/application "Home" (contents/index))))
(route/resources "/"))
which will print something like:
(layout/application "Home" (contents/index))) => nil
when run. spyx ("spy explicit") prints the expression you give it, an arrow, and the expression value. spy, spyx, spy-pretty, etc also return the value printed (unlike println which always returns nil) so you can insert a spy printout anywhere without disrupting the processing chain. Thus, you don't need to write something like:
(defroutes routes
(GET "/" [] (let [tmp-1 (layout/application "Home" (contents/index))]
(println "layout/application result => " tmp-1)
tmp-1)))
(route/resources "/"))
in order to get a debug message printed. In order to spy & friends, add this to the :dependencies in your project.clj:
[tupelo "0.9.138"]
Update
Hmmmm.... Not sure what could be the problem. I made a simple demo app from lein new compojure demo-compojure with the following:
(ns demo-compojure.handler
(:use tupelo.core)
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defn index []
(spy :index--result "Hello World"))
(defroutes app-routes
(GET "/" [] (spyx (index)))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
and results:
~/expr/demo-compojure > lein ring server
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Started server on port 3000
:index--result => "Hello World"
(index) => "Hello World"
So that is working. Also, int? is a basic Clojure function, so that is puzzling. Perhaps make a clean demo project like the above and take it from there?

How to apply ring-anti-forgery on specific reitit routes?

I keep getting "Invalid anti-forgery token" when wrapping specific routes created with metosin/reitit reitit.ring/ring-router. I've also tried reitit's middleware registry, but it didn't work too. Although I could just wrap the entire handler with wrap-session and wrap-anti-forgery, that defeats the reitit's advantage on allowing route-specific middleware.
(ns t.core
(:require [immutant.web :as web]
[reitit.ring :as ring]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.session :refer [wrap-session]]
[ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.util.response :as res]))
(defn render-index [_req]
(res/response (str "<form action='/sign-in' method='post'>"
(anti-forgery-field)
"<button>Sign In</button></form>")))
(defn sign-in [{:keys [params session]}]
(println "params: " params
"session:" session)
(res/redirect "/index.html"))
(defn wrap-af [handler]
(-> handler
wrap-anti-forgery
wrap-session
wrap-keyword-params
wrap-params))
(def app
(ring/ring-handler
(ring/router [["/index.html" {:get render-index
:middleware [[wrap-content-type]
[wrap-af]]}]
["/sign-in" {:post sign-in
:middleware [wrap-af]}]])))
(defn -main [& args]
(web/run app {:host "localhost" :port 7777}))
It turns out that metosin/reitit creates one session store for each route (refer to issue 205 for more information); in other words, ring-anti-forgery is not working because reitit does not use the same session store for each route.
As of the time of this answer, the maintainer suggests the following (copied from the issue for ease of reference within Stack Overflow):
mount the wrap-session outside of the router so there is only one instance of the mw for the whole app. There is :middleware option in ring-handler for this:
(require '[reitit.ring :as ring])
(require '[ring.middleware.session :as session])
(defn handler [{session :session}]
(let [counter (inc (:counter session 0))]
{:status 200
:body {:counter counter}
:session {:counter counter}}))
(def app
(ring/ring-handler
(ring/router
["/api"
["/ping" handler]
["/pong" handler]])
(ring/create-default-handler)
;; the middleware on ring-handler runs before routing
{:middleware [session/wrap-session]}))
create a single session store and use it within the routing table (all instances of the session middleware will share the single store).
(require '[ring.middleware.session.memory :as memory])
;; single instance
(def store (memory/memory-store))
;; inside, with shared store
(def app
(ring/ring-handler
(ring/router
["/api"
{:middleware [[session/wrap-session {:store store}]]}
["/ping" handler]
["/pong" handler]])))
Not shown in this answer is the third option that the maintainer calls for PR.

Clojure require namespace: "Don't know how to create ISeq from: clojure.lang.Keyword"

I'm trying to split code in 2 files, each with it's own namespace. Following this tutorial.
But I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Keyword
I think it's because the namespace being included is not recognised properly.
Main file:
(ns mytest2.handler
(:use compojure.core)
(:require [compojure.handler :as handler]
[compojure.route :as route]
[mytest2.views :as foo] ;<-- line causing error
[hiccup.core :refer (html)])
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(defroutes app-routes
(GET "/" [] (main-page))
(route/resources "/")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
; (println (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))
Second file:
(ns mytest2.views
:require [hiccup.core :refer (html)]
)
(defn layout [title & content]
(html
[:head [:title title]]
[:body content]))
(defn main-page []
(layout "My Blog"
[:h1 "My Blog"]
[:p "Welcome to my page"]))
(note I copied the functions from mytest2.views in mytest2.handler for testing. They're not supposed to be in mytest2.handler).
Paths of files:
/mytest2/src/mytest2/handler.clj
/mytest2/src/mytest2/views.clj
(where first mytest2 is the name of the project, and the second is part of the path- automatically created by lein).
As you see in the first file I printed the class path to verify that /mytest2/src/mytest2/ is included, and yes it is.
Received the same error from trying to use :refer :all in Clojurescript, which apparently is against the rules.
You missed some brackets in your original code
;; wrong
(ns mytest2.views
:require [hiccup.core :refer [html]])
There is just one pair of brackets missing. Do it as in your Main file:
;; Done right!
(ns mytest2.views
(:require [hiccup.core :refer [html]]))
I am not familar with Compojure so I do not know what you have to require. But you need to add the bracket around :require.

How to set Content-Type header on Ring-Compojure application

I'm trying to get started with Clojure and Clojurescript by implementing a simple web app. Things are going pretty good so far and reading from different tutorials I've come up with the code below:
core.clj:
(ns myapp.core
(:require [compojure.core :as compojure]
[compojure.handler :as handler]
[compojure.route :as route]
[myapp.controller :as controller]))
(compojure/defroutes app-routes
(compojure/GET "/" [] controller/index)
(route/resources "/public")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
controller.clj:
(ns myapp.controller
(:use ring.util.response)
(:require [myapp.models :as model]
[myapp.templates :as template]))
(defn index
"Index page handler"
[req]
(->> (template/home-page (model/get-things)) response))
templates.clj:
(ns myapp.templates
(:use net.cgrand.enlive-html)
(:require [myapp.models :as model]))
(deftemplate home-page "index.html" [things]
[:li] (clone-for [thing things] (do->
(set-attr 'data-id (:id thing))
(content (:name thing)))))
The problem is I can't display non-ascii characters on the page and I don't know how to set HTTP headers on a page.
I see solutions like this but I simply can't figure out where place them in my code:
(defn app [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
P.S: Any suggestions about style and/or code organization are welcome.
Use ring.util.response:
(require '[ring.util.response :as r])
Then on your index function:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")))
You can chain other actions on the response such as set-cookie and whatnot:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")
(r/set-cookie "your-cookie-name"
"" {:max-age 1
:path "/"})))

Compojure app not playing well with with-redefs

I'm writing a Compojure application and am using clj-webdriver to graphically test it. I'm trying to use with-redefs to mock out the function that pulls out data from persistence to just return canned values, but it's ignoring my function overwrite. I know with-redefs works in terms of vars, but it's still not working:
project.clj relevant pieces:
(defproject run-hub "0.1.0-SNAPSHOT"
:main run-hub.handler/start-server)
handler.clj:
(ns run-hub.handler
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[ring.adapter.jetty :refer :all]
[run-hub.controllers.log-controller :as log-controller]))
(defroutes app-routes
(GET "/MikeDrogalis/log" [] (log-controller/mikes-log))
(route/resources "/")
(route/not-found "Not Found"))
(def app (handler/site #'app-routes))
log-controller.clj:
(ns run-hub.controllers.log-controller
(:require [run-hub.views.log :as views]
[run-hub.persistence :as persistence]))
(defn mikes-log []
(views/mikes-log (persistence/mikes-log)))
persistence.clj
(ns run-hub.persistence
(require [clj-time.core :as time]
[run-hub.models.log :as log]))
(defn mikes-log [] [])
And finally, my graphical test - which tries to override mikes-log and fails:
(fact
"It has the first date of training as August 19, 2012"
(with-redefs [persistence/mikes-log (fn [] (one-week-snippet))]
(to (local "/MikeDrogalis/log"))
(.contains (text "#training-log") "August 19, 2012"))
=> true)
Where one-week-snippet is a function that returns some sample data.
(defn start-server []
(run-jetty (var app) {:port 3000 :join? false}))
I am able to use with-redefs in a clj-webdriver test doing the following:
(defn with-server
[f]
(let [server (run-jetty #'APP {:port 0 :join? false})
port (-> server .getConnectors first .getLocalPort)]
(binding [test-port port]
(try
(println "Started jetty on port " test-port)
(f)
(finally
(.stop server))))))
(use-fixtures :once with-server)
Then the whole bunch of tests gets its own jetty and this seems to run in
such a manner that with-redefs works.