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
Related
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}}
I am using ring with compojure and friend to realize basic password authentication in a toy app. Now I was trying to implement an example and my ring server causes a redirect loop in every browser.
This is my code:
(ns kassenclo.handlers
(:use [ring.util.response]
[ring.middleware.resource]
[ring.middleware.params])
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.session :refer [wrap-session]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[kassenclo.views :as views]
[cemerick.friend :as friend]
[cemerick.friend.workflows :refer [make-auth]]))
(defroutes app
(GET "/" [] views/tally-view)
(GET "/inventory" [] (friend/authorize #{::user} views/inventory-view))
(route/not-found (html [:h1 "This page could not be found, please go back."])))
(defn auth-workflow [request]
(let [speak (get-in request [:params :speak])
credential-fn (get-in request [::friend/auth-config :credential-fn])]
(make-auth (credential-fn speak))))
(defn auth-credential [password]
(if (= password "pass")
{:identity password :roles #{::user}}))
(def handler
(-> app
(friend/authenticate {:workflows [auth-workflow]
:credential-fn auth-credential})
(wrap-keyword-params)
(wrap-params)
(wrap-session)
(wrap-resource "public")))
Simple debugging shows me that the server is alternating between auth-workflow and auth-credential a few times before it is stopped. Can anybody point out to me what I am missing?
// Edit:
The curious thing is that this redirect-loop happens on every route, even on / where friend is not used in the defroutes command.
I found out that the make-auth function, which wraps the authentication-map so it has the correct form has to be applied on the return value of the auth-credential before returning it. If it happens afterwards like in my original post friend rejects it and we get a authentication loop.
I have POSTed data to a Pedestal endpoint "/my-post. I have routed that end point as such:
[[["/" {:get landing} ^:interceptors [(body-params/body-params) ...]
["/my-post {:post mypost-handler}
....
So to my mind this means that the body-params interceptor will fire for /my-post too.
In mypost-handler I have:
(defn mypost-handler
[request]
****HOW TO ACCESS THEN FORM DATA HERE ****
)
How do I now access the form data here? I can see from printing the request that I have a #object[org.eclipse.jetty.sever.HttpInputOverHTTP..] which will clearly need further processing before it is useful to me.
(I must say, the documentation for Pedestal is pretty sketchy at best...)
Something like this should work. Note the body-params interceptor on the mypost-handler route
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; json-params is the posted json, so
;; (:name json-params) will be the value (i.e. John) of name property of the posted json {"name": "John"}
;; handle request
{:status 200
:body "ok"})
(defroutes routes
[[["/mypost-handler" {:post mypost-handler}
^:interceptors [(body-params/body-params)]
]
]])
The mypost-handler is acting as a Ring handler, i. e. it should accept a Ring request map and return a Ring response map. Thus, you can expect a typical Ring request structure:
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; handle request
{:status 200
:body "ok"})
Here's more relevant info on defining such handlers in your route tables.
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.
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)))