lein ring auto-refresh overwrites request body? - clojure

I wrote simple client-server app referring to "ClojureScript: Up and Running".
https://github.com/phaendal/clojure-simple-client-server
As shown in below server-code, /text prints request and body to console and returns body from (slurp (:body req)).
But if :auto-refresh? is set to true in project.clj, (slurp (:body req)) will returns empty string instead of sent value.
Why it returns empty? and How to get request-body with auto-refresh?
(ns client-server.server
(:gen-class)
(:require [compojure.route :as route]
[compojure.core :as compojure]
[ring.util.response :as response]))
(defn simple-print [req]
(let [body (slurp (:body req) :encoding "utf-8")]
(println req)
(println (str "slurped: " body))
body))
(compojure/defroutes app
(compojure/POST "/text" request (simple-print request))
(compojure/GET "/" request
(-> "public/index.html"
(response/resource-response)
(response/content-type "text/html")
(response/charset "utf-8")))
(route/resources "/"))

When you set auto-refresh: true, lein-ring also adds ring.middleware.params through wrap-params. see https://github.com/weavejester/ring-refresh/blob/master/src/ring/middleware/refresh.clj#L90-L102.
The ring.middleware.params do its job to parse form-parameter from request body by draining the request body through slurp just like you do in your handler. see https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/params.clj#L29
So, the request body already emptied by the time you try to slurp it in your handler.
Also, when you try to POST, please pay attention to the content-type sent. It's application/www-form-urlencoded by default and it needs parameter name and value. see http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1
Just sending the plain value just like you do in your clojurescript does not play nice with form-parameter parser. In your project example, ring param middleware just skip it because the value sent from your javascript does not conform to the spec. If you put name and value in your POST request, it shows up in :params key in your request.

Related

ring anti-forgery token is unbound in rendered html

I need a CSRF token in order for my websockets client to connect to my server. In order to get the token to my client, I embed the token from ring.middleware.anti-forgery/*anti-forgery-token* in a <meta> tag, like so:
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
However, instead of getting an actual token back (i.e. a 60-character-long random base64 string) in the rendered tag, it renders as
<meta content="Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" name="csrf-token">
I also have ring-default's site-defaults wrapped around the routes as middleware, which includes ring.middleware.anti-forgery.
Here is the relevant file, handler.clj:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery]
[compojure.core :refer :all]))
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
(defn loading-page []
(html5
(head)
[:body {:class "body-container"}
[:div#app]
(include-js "/js/app.js")
[:script "spotify_client.core.init_BANG_()"]]))
(defn index-handler [_]
{:status 200
:headers {"Content-Type" "text/html"}
:body (loading-page)
(defroutes main-routes
(GET "/" [] index-handler)
...
)
(def app (wrap-defaults main-routes site-defaults))
I'm also running all of this using lein run.
Now, according to this link, the anti-forgery token var would become bound in the context of whatever the current request was, and the ring-anti-forgery middleware is what binds it. When my project was brand new and I had just added wrap-defaults (on the initial ten or so calls to start/reload the server), the token var was bound and rendered correctly. However, after no signficant changes, the token var will not bind and it renders as the "Unbound" string. Once the ring-anti-forgery middleware stopped being called(?) and stopped binding *anti-forgery-token* to an actual value, the var was never bound again. I've tried making a new project a couple of times already and the token var stops being bound after 5-10 server reloads. What could cause the ring-anti-forgery middleware within ring-defaults to stop working even though wrap-defaults is in my code?
According to the docs, the *anti-forgery-token* is a dynamic variable that is only defined within the context of a wrap-anti-forgery middleware call.
Add [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] to your require form, and then add wrap-anti-forgery to your app declaration:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[compojure.core :refer :all]))
...
(def app
(-> main-routes
(wrap-anti-forgery)
(wrap-defaults site-defaults))

How do I make middleware to put a response header only when `:swagger {:deprecated true}`?

We use the compojure-api to get us some nice swagger integration in our ring apps. The :swagger {:deprecated true} meta works like a champ to get the swagger page correct, but I have a requirement that I put a specific header on the response when the route is :swagger {:deprecated true}. I am struggling to figure out how to do this with the middleware pattern that I've been using to do similar response header manipulations.
(ns bob.routes
(:require [clojure.tools.logging :as log]
[compojure.api.sweet :refer :all]
[ring.util.http-response :as status]
[schema.core :as s]
[ring.swagger.schema :as rs]))
(s/defschema BobResponse {:message (rs/describe String "Message")})
(defn wrap-bob-response-header [handler]
(fn [request]
(let [response (handler request)]
;; can I reach into the request or the response to see what
;; route served this and if it has the :swagger {:deprecated true}
;; meta on it and NOT emit the x-bob header if it does?
(assoc-in response [:headers "x-bob"] "Robert"))))
(defroutes bob-routes
(context "" []
:middleware [wrap-bob-response-header]
:tags ["bob"]
:description ["Tease out how to do swagger driven response header"]
(GET "/notdeprectated" [:as request]
:swagger {:deprecated false}
:new-relic-name "GET_notdeprecated"
:return BobResponse
(status/ok {:message "All is well"}))
(GET "/isdeprecated" [:as request]
:swagger {:deprecated true}
:new-relic-name "GET_isdeprecated"
:return BobResponse
(status/ok {:message "You came to the wrong neighborhood."}))))
How do I modify wrap-bob-response-header to only emit x-bob on routes with :swagger {:deprecated true}?
With Compojure-API, the middleware are invoked in-place, at the path context they are defined at. In your example, the wrap-bob-response-header doesn't yet know where the request is going to go (or will it even match anything). If it knew, you could use the injected route information from the request (see https://github.com/metosin/compojure-api/blob/master/src/compojure/api/api.clj#L71-L73) to determine if the endpoints would have the swagger information set.
What you could do, is mount the header-setting middleware only to the routes that need it.
There is a library called reitit (also by Metosin) which solves this by applying a route-first architecture: the full path lookup is done first and the middleware chain is applied after that. Because of this, all the middleware know the endpoint they are mounted to. Middleware can just query the endpoint data (either at request-time or at compile-time) and act accordingly. They can even decide not to mount to that spesific route.
Reitit is feature-par with compojure-api, just with different syntax, e.g. fully data-driven.
Good examples in the blog: https://www.metosin.fi/blog/reitit-ring/
PS. I'm co-author of both of the libs.
EDIT.
Solution to inject data to the response after a match:
1) create middleware that adds data (or meta-data) to the response
2) add or modify a restructuring handler to mount the middleware from 1 into the endpoint, with the given data (available in the handler)
3) read the data in the response pipeline and act accordingly
(defn wrap-add-response-data [handler data]
(let [with-data #(assoc % ::data data)]
(fn
([request]
(with-data (handler request)))
([request respond raise]
(handler #(respond (with-data %)) raise)))))
(defmethod compojure.api.meta/restructure-param :swagger [_ swagger acc]
(-> acc
(assoc-in [:info :public :swagger] swagger)
(update-in [:middleware] into `[[wrap-add-response-data ~swagger]])))
(def app
(api
(context "/api" []
(GET "/:kikka" []
:swagger {:deprecated? true}
(ok "jeah")))))
(app {:request-method :get, :uri "/api/kukka"})
; {:status 200, :headers {}, :body "jeah", ::data {:deprecated? true}}

how to you access :headers inside compojure function

org.clojure/clojure-contrib "1.2.0"
ring "1.1.8"
compojure "1.1.5"
clout "1.1.0"
(defroutes rest-routes
(GET "/" [] "<p> Hello </p>")
(POST "/api/v1/:stor/sync" [stor] (start-sync stor))
(POST ["/api/v1/:stor/:txn/data/:file" :file #".*"] [stor txn file] (txn-add stor txn file))
(ANY "*" [] "<p>Page not found. </p>"))
In the second POST, I also want to pass all http-headers to "txn-add" handler. I did lot of google and look through the code, but couldn't find anything useful.
I know, I can use the following to pass headers (but then it doesn't parse url request),
(POST "/api/v1"
{headers :headers} (txn-add "dummy stor" "dummy txn" headers))
Also, how do I pass the content (i.e. :body) of POST request to "txn-add" ?
If the second argument to GET, POST etc is not a vector, it's a destructuring binding form for request. That means you can do things like:
(GET "/my/path"
{:keys [headers params body] :as request}
(my-fn headers body request))
To pick out the parts of request you want. See the Ring SPEC and Clojure's docs on binding & destructuring
The whole request map can be specified in the bindings using :as keyword in bindings and then used to read headers or body :
(POST ["/api/v1/:stor/:txn/data/:file" :file #".*"]
[stor txn file :as req]
(my-handler stor txn file req))

Why does my Ring middleware not see the :params map in the request?

I'm writing a Ring middleware, and also using Compojure. I want my middleware to look in the :params map to see if a specific key was provided by the user. In my middleware function, the request map does not contain a :params map, though. In the final request handler, there is a :params map. I'm thinking the :params map it not being set before my custom middleware, but I can't figure out how to get it to actually be set.
Any ideas?
(ns localshop.handler
(:use [ring.middleware.format-response :only [wrap-restful-response]]
[compojure.core])
(:require [localshop.routes.api.items :as routes-api-items]
[localshop.middleware.authorization :as authorization]
[compojure.handler :as handler]))
;; map the route handlers
(defroutes app-routes
(context "/api/item" [] routes-api-items/routes))
;; define the ring application
(def app
(-> (handler/api app-routes)
(authorization/require-access-token)
(wrap-restful-response)))
Above is my handler.clj file, and below is the middleware itself.
(ns localshop.middleware.authorization)
(defn require-access-token [handler]
(fn [request]
(if (get-in request [:params :token])
(handler request)
{:status 403 :body "No access token provided"})))
I actually figured this out. This works if you adjust the (def app ... ) portion of the code so that it matches the following:
(def app
(-> app-routes
(wrap-restful-response)
(authorization/require-access-token)
(handler/api)))

Accessing posted json with ring format-params middleware

I'm trying to make a very simple API using ring in clojure. I'm using the rack.middleware.format-params middleware to convert the output to json, and the input from json to clojure data structures.
I've got the output working nicely, but I can't for the life of me access the parameters sent through json. Here's some code that works for get requests, but I can't get the POST request to return the json it recieves
(ns testing.core
(:use [compojure.core]
[ring.middleware.format-params :only [wrap-json-params]]
[ring.middleware.format-response :only [wrap-json-response]]
[ring.adapter.jetty])
(:require [compojure.handler :as handler]))
(defroutes app-routes
(GET "/"
[]
{:body {:hello "world"}})
(POST "/"
{params :params}
{:body params}))
(def app
(-> (handler/api app-routes)
(wrap-json-params)
(wrap-json-response)))
It just returns this: {}
What am I doing wrong?
I'm an idiot and realised that I wasn't sending the json Content-Type header. Hopefully no-one else makes the same silly mistake :P