How to serve static resources in Yada - clojure

With Compojure I can serve static resources like so:
(defroutes routes
(route/resources "/"))
Following the Yada docs I have this working:
(def server
(listener
["/"
[["hello" (as-resource "Hello World!")]
["test" (resource {:produces "text/plain"
:response "This is a test!"})]
[true (as-resource nil)]]]
{:port 3000}))
But how do I make Yada serve resources from the file system?

I ended up finding the answer here: Wrapping resource handlers with bidi
(ns yada-test
(:require [yada.yada :refer [listener as-resource]]
[bidi.ring :refer [resources]]))
(def server
(listener
["/"
[["" (resources {:prefix "public/"})]]]
{:port 3001}))

Try the following:
(ns yada-test
(:require [clojure.java.io :as io]
[yada.yada :refer [listener as-resource]]]))
(def server
(listener
["/"
[["talks" (as-resource (io/file (io/resource "docs")))]
[true (as-resource nil)]]]
{:port 3001}))
You need to add a resource directory to your classpath and create the docs folder in there.

Related

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, Compojure: How Can I add Ring middleware CORS to ring adapter jetty based project?

I am using ring adapter jetty server in my compojure api project. Now I want to add ring middleware CORS to my Project. How should I add and where should I add ring middleware CORS in my project?
These are my project code snippets
API
(ns clojure-dauble-business-api.core
(:require [compojure.api.sweet :refer :all]
[ring.util.http-response :refer :all]
[clojure-dauble-business-api.logic :as logic]
[clojure.tools.logging :as log]
[clojure-dauble-business-api.domain.artwork]
[cheshire.core :as json])
(:import [clojure_dauble_business_api.domain.artwork Artwork]))
(defapi app
(GET ["/hello/:id", :id #"[0-9]+"] [id :as request]
(log/info "Function begins from here" request)
(def jsonString (json/generate-string (get-in request [:headers])))
(log/info "Create - header value is " (get-in (json/parse-string jsonString true) [:accesstoken]))
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201}))
(ok data)))
Method to Run the api on Jetty
(ns clojure-dauble-business-api.routes
(:require [compojure.core :refer :all]
[ring.adapter.jetty :as jetty]
[ring.middleware.cors :refer [wrap-cors]]
(clojure-dauble-business-api [core :as core]
[test :as t])))
(def app
(routes core/app t/test))
(jetty/run-jetty app {:port 3000})
Here core/app is the above API function path and t/test another API function(I have not provided the code here).

Carmine connection error during tests

I've a luminus project with some simple compojure-api routes.
I've added carmine to communicate with a redis server, using the wcar* macro (defined in services.clj) to make calls to it, and everything works fine.
Now I'm trying to add some tests, but seems that the redis connection doesn't works properly during them, because I'm receiving this error with lein test:
ERROR Carmine connection error
clojure.lang.ExceptionInfo: Carmine connection error {}
Since it's working in dev e prod environments, I think that is something related to a missing env load in the test environment, but I didn't find a way to solve it.
These are relevant parts of the code in use:
test.clj
(ns app.test.handler
(:require [clojure.test :refer :all]
[ring.mock.request :refer :all]
[app.handler :refer :all]))
(deftest test-app
(testing "redis ping"
(let [response ((app) (request :get "/api/redis-ping"))]
(is (= 200 (:status response))))))
services.clj
(ns app.routes.services
(:require [ring.util.http-response :refer :all]
[compojure.api.sweet :refer :all]
[schema.core :as s]
[app.config :refer [env]]
[clojure.tools.logging :as log]
[mount.core :refer [defstate]]
[taoensso.carmine :as car :refer (wcar)]))
(defmacro wcar* [& body] `(car/wcar
{:spec {:host (:redis-host env) :port (:redis-port env)}}
~#body))
(defapi service-routes
(context "/api" []
:tags ["myapi"]
(GET "/redis-ping" []
:return String
:summary "A redis client test."
(ok (wcar* (car/ping "hello"))))))
handler.clj
(ns app.handler
(:require [compojure.core :refer [routes wrap-routes]]
[app.routes.services :refer [service-routes]]
[compojure.route :as route]
[app.env :refer [defaults]]
[mount.core :as mount]
[app.middleware :as middleware]))
(mount/defstate init-app
:start ((or (:init defaults) identity))
:stop ((or (:stop defaults) identity)))
(def app-routes
(routes
#'service-routes
(route/not-found
"page not found")))
(defn app [] (middleware/wrap-base #'app-routes))
Profiles.clj
{:profiles/dev {:env {:redis-host "127.0.0.1" :redis-port 6381}}
:profiles/test {:env {:redis-host "127.0.0.1" :redis-port 6381}}}
Config.clj
(ns app.config
(:require [cprop.core :refer [load-config]]
[cprop.source :as source]
[mount.core :refer [args defstate]]))
(defstate env :start (load-config
:merge
[(args)
(source/from-system-props)
(source/from-env)]))
SOLUTION
Add a text fixture with the mount/start command that's executed before tests.
Add to test.clj:
(defn my-test-fixture [f]
(mount/start)
(f))
(use-fixtures :once my-test-fixture)
You are using mount to manage your application state lifecycle. I think you are not calling (mount/start) in your tests thus your app.config/env state is not initialized properly. On the other hand when you start your application (mount/start) is probably called and thus it's working correctly.

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.

Weird lein uberwar behavior

I'm executing lein uberwar for my test webapp and I'm getting the following weird error:
Exception in thread "main" java.lang.ClassCastException: java.lang.Boolean cannot be cast to clojure.lang.Symbol (servlet.clj:1)
The servlet.clj contains the following:
(ns test.servlet
(:use ring.util.servlet)
(:require test.routes :as routes)
(:gen-class :extends javax.servlet.http.HttpServlet))
(defservice routes/app-routes)
The test.routes file contains:
(ns test.routes
(:use compojure.core)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes app-routes
(GET "/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body "<h1>Hello World</h1>"})
(route/files "/" {:root "static"})
(ANY "/:s" [s] (str "page-not-found" s)
;; For lein ring-server
;(def test-handler
; (handler/site app-routes))
The strange thing is that if I substitute the line
(:require test.routes :as routes)
With:
(:require test.routes)
And call
(defservice test.routes/app-routes)
In servlet.clj, the WAR compiles fine and it is working flawlessly in tomcat. Is there some bug in my code that I fail to see? Why do I need to call test.routes/... instead of just routes/...?
You need to put it inside a vector:
(:require [test.routes :as routes])