ring.middleware.json/wrap-json-params parses numbers into strings? - json

I'm sure I must be doing something wrong...here are the relevant lines of clojure:
(ns command.command-server
(:use [org.httpkit.server :only [run-server]])
(:use [storage.core-storage])
(:use compojure.core)
(:use [command.event-loop :only [enqueue]])
(:require [compojure.handler :as handler]
[compojure.route :as route]
[ring.middleware.json :as middleware]))
(def app
(-> (handler/api app-routes)
(middleware/wrap-json-body)
(middleware/wrap-json-response)
(middleware/wrap-json-params)))
;in app-routes, the rest left out for brevity
(POST "/request" {json :params}
(do
(queue-request json)
(response {:status 200})
))
(defn queue-request [evt]
(let [new-evt (assoc evt :type (keyword (:type evt)))]
(println (str (type (:val1 evt))))
(enqueue new-evt)))
The "println" near the end is showing the type of :val1 as java.lang.String when I send the following from jquery:
$.ajax({
type: "POST",
url: 'http://localhost:9090/request',
data: {type: 'add-request', val1: 12, val2: 50},
success: function(data){
console.log(data);
}
});
So what am I doing wrong?

This could be down to the jQuery request, rather than the ring middleware.
To be honest, I don't know much about jQuery, but I just came across this answer which looks like it could explain what's happening. In short, the data from your query as it stands will be encoded as strings in the URL. These will be parsed by ring into strings, not integers, as the URL encoding does not specify the type. If you want JSON encoding, you'll need to specify it explicitly. Like so:
$.ajax({
type: "POST",
url: 'http://localhost:9090/request',
data: JSON.stringify({type: 'add-request', val1: 12, val2: 50}),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(data){
console.log(data);
});

Related

ClojureScript AJAX POST to send a json data

I am trying to implement AJAX POST in ClojureScript.
The following is the code i am using
(defn handler [response]
(.log js/console (str response)))
(defn test-post [name email]
(let [data {:name name :email email}]
(POST "http://localhost:5000/Add/"
{
:format {"Accept" :json}
:body (.stringify js/JSON (clj->js data))
:handler handler
:error-handler (fn [r] (prn r))
:response-format {"Content-Type" "application/json;charset=utf-8"}
}
)))
When do i call the Post method. On form Submit? Also the post request is hitting the url but the json data is not present.
I assume you're using cljs-ajax for sending data.
What you really need in something like this:
(let [data {:name name :email email}]
(POST "/Add"
{:format :json
:params data
:handler handler
:error-handler (fn [r] (prn r))})))
You can just pass a plain Clojure object as params, just set :json as data format (default is :transit).
The second question is rather open and depends on your setup. I think the simplest to use is reagent, here is a nice example with sending form data.

Why do I have to flatten nested JSON when using the ring json middleware

I have been writing a clojure application with ring and compojure. I am using the ring.middleware.json middleware for handling JSON so I don't have to serialise and deserialise it myself in my code.
This middleware only seems to correctly parse nested JSON when given in a flattened format, for example if I want to POST nested data to an API route I have to encode it as:
{"task" : 1,
"success" : false,
"files[0][type]" : "log",
"files[0][sha256]" : "adef5c",
"files[0][url]" : "s3://url"}
Instead of, what seems to me, more standard JSON:
{"task" : 1,
"success" : false,
"files" : [{"type" : "log", "sha256" : "adef5c", "url": "s3://url"}]}
Is this indented or a standard way of posting nested JSON? Here is my middleware stack:
(defn middleware [handler]
(-> handler
(wrap-json-response)
(wrap-with-logger)
(api)))
Here is a full Clojure example of post and get routes that handle JSON or return JSON:
(ns ring-app.core
(:require [ring.adapter.jetty :as jetty]
[compojure.core :as compojure]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.json :refer [wrap-json-response wrap-json-body]]
[ring.util.http-response :as response]
[clojure.pprint :refer [pprint]]
[ring.middleware.reload :refer [wrap-reload]]))
(defn json-get [request]
(response/ok
{"task" 1
"success" false
"files" [{"type" "log" "sha256" "adef5c" "url" "s3://url"}]}))
(defn json-post [request]
(let [bpdy (:body request)]
(prn bpdy)
(response/ok bpdy)))
(compojure/defroutes handler_
(compojure/GET "/get" request json-get)
(compojure/POST "/post" request json-post))
(defn wrap-nocache [handler]
(fn [request]
(-> request
handler
(assoc-in [:headers "Pragma"] "no-cache"))))
(def handler
(-> #'handler_ wrap-nocache wrap-reload wrap-json-response wrap-json-body ) )
The get endpoint returns the nested structure:
curl http://localhost:3000/get
; {"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}
And the POST endpoint parses the json and create a Clojure data structure:
curl -X post -d '{"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}' -H "Accept: application/json" -H "Content-type: application/json" http://localhost:3000/post
; {"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}
This is roundtripping on JSON->CLJ->JSON
The json ring middleware expects the Content-type to be properly set so maybe that was why the structure was not parsed properly in your case ?

clojure spa webapp trouble logging in with friend-json-auth & compojure

I am trying to use the friend-json-auth library with my app in tandem with compojure instead immutant as they have listed in their example app.
https://github.com/marianoguerra/immutant-recipes/tree/master/friend-json-auth
I am using angular as my client and I make this post request:
this.login = function ( username, password ) { return $http({
method: 'POST',
url: 'http://localhost:3000/api/session',
headers: {
'Content-Type': 'application/json',
'Data-Type': 'json'
},
data: { username : "jane", password : "user_password" }
}) };
but the reponse I get back is
POST http://localhost:3000/api/session 401 (Unauthorized)
Here is my POST endpoint for /api/session:
controller.clj
(ns recursiftion.controller
(:use [ring.adapter.jetty :only [run-jetty]]
[recursiftion.websocket :only [wamp-handler]]
[recursiftion.config :only [conf]]
[ring.middleware params keyword-params nested-params]
[ring.util.response :only (redirect)]
[cemerick.friend.util :only (gets)]
[clojure.data.json :only (read-json json-str)]
)
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[clojure.java.io :as io]
[ring.util.io :refer [string-input-stream]]
[ring.util.response :as resp]
[ring.util.response :refer [response]]
[ring.middleware.json :as middleware]
[ring.middleware.cors :refer [wrap-cors]]
[environ.core :refer [env]]
[cheshire.core :refer :all]
[recursiftion.model :as model]
[clojure.pprint :refer [pprint]]
[cemerick.friend :as friend]
(cemerick.friend [workflows :as workflows]
[credentials :as creds])
[ring.middleware.session :as ring-session]
[marianoguerra.friend-json-workflow :as json-auth]
[ring.middleware.file :as ring-file]
[ring.middleware.file-info :as ring-file-info]))
(defroutes app-routes
(POST "/api/session" request
(let [node_object (or (get-in request [:params :data])
(get-in request [:body :data])
"ROUTER_ERROR")]
(json-auth/handle-session request)))
)
(def app
(-> (handler/api app-routes)
(middleware/wrap-json-body {:keywords? true})
(middleware/wrap-json-response)
(wrap-cors routes #"^http://localhost:9000$")))
(def users {"root" {:username "root"
:password (creds/hash-bcrypt "admin_password")
:roles #{::admin}}
"jane" {:username "jane"
:password (creds/hash-bcrypt "user_password")
:roles #{::user}}})
(def secure-app
(-> app
(friend/authenticate
{:login-uri "/friend-json-auth/api/session"
:unauthorized-handler json-auth/unauthorized-handler
:workflows [(json-auth/json-login
:login-uri "/friend-json-auth/api/session"
:login-failure-handler json-auth/login-failed
:credential-fn (partial creds/bcrypt-credential-fn users))]})
(ring-session/wrap-session)))
(defn -main []
(run-jetty app {:port (if (nil? (System/getenv "PORT"))
8000 ; localhost or heroku?
(Integer/parseInt (System/getenv "PORT")))}) )
project.clj
(defproject helloworld "1.0.0-SNAPSHOT"
:description "csc358 final project"
:url "http://localhost:9000/"
:license {:name "FIXME: choose"
:url "http://example.com/FIXME"}
:dependencies [[org.clojure/clojure "1.7.0"]
[compojure "1.1.8"]
[ring/ring-json "0.3.1"]
[ring/ring-core "1.4.0"]
[ring-router "0.2-SNAPSHOT"]
[ring/ring-jetty-adapter "1.3.2"]
[jumblerg/ring.middleware.cors "1.0.1"]
[c3p0/c3p0 "0.9.1.2"]
[org.clojure/java.jdbc "0.2.3"]
[cheshire "5.4.0"]
[environ "0.2.1"]
[hiccup "1.0.0"]
[clojurewerkz/neocons "3.1.0-rc1"]
[org.clojure/data.json "0.2.5"]
[clj-wamp "1.0.2"]
[clj-http "2.0.0"]
[com.cemerick/friend "0.2.1"]
[org.marianoguerra/friend-json-workflow "0.2.1"]]
:min-lein-version "2.0.0"
:plugins [[environ/environ.lein "0.2.1"]
[lein-ring "0.7.3"]]
:hooks [environ.leiningen.hooks]
:profiles {:dev {:dependencies [[ring-mock "0.1.3"]]}}
:ring {:handler recursiftion.controller/app})
I am very grateful for help you might be able to offer.
On his example client he uses JSON.stringify to pass a JSON string as a map to the AJAX request. In your request you're passing a js object instead of a JSON string as the value of data:
https://github.com/marianoguerra/immutant-recipes/blob/master/friend-json-auth/resources/s/js/app.js
function login(username, password) {
var data = JSON.stringify({"username": username, "password": password});
return $.ajax({
type: "POST",
url: "api/session",
dataType: "json",
contentType: "application/json",
data: data
});
}

clojure ring handler returning json - done of xhr of backbone collection not called

I have a clojure running server whose one endpoint returns simple json.
I make a callback from backbone.js collection to this endpoint. I have put a done handler on the return xhr of the fetch() of collection. However, this done callback is never called. I can see that request is 200 with right data coming to the client.
code is here:
(defn getJson []
{:status 200
:headers {"Content-Type" "application/json"}
:body "[{id: 123, name: \"a2k jailed\"}, {id: 234, name: \"klmno won\"}]"
}
)
(defroutes main-routes*
(GET "/headlines" [] (getJson))
(route/resources "/")
(route/not-found "<h1>404 - Page not found</h1>"))
(def app (compojure.handler/site main-routes*))
(defn -main [port]
(def server (jetty/run-jetty #'app
{:port (Integer. port) :join? false})))
// backbone client side:
this.jsonCollection = new someClassExtendedFrombackboneCollection();
this.jsonCollection.fetch().done(function(jsons) {
console.log("jsons: " + jsons);
});
What is not right in here ?

Accessing POST json in clojure

EDIT - The source code is on github if you're interested. Thanks
I am a bit confused as to how to access json data that has been posted to a url in clojure; I just can't seem to get it to work.
This is my route:
(cc/POST "/add"
request
(str ": " request))
I am not entirely sure what I have to put in place of request - I just saw some blog online and tried to follow it, but couldn't get it to work.
Here's how I'm trying to post: (from fiddler)
note: request header port is different in the image; it's a mistake, I was trying to mess with that data to see what it says so ignore that part in the above image please
In curl, I'm just doing this:
curl -X POST -H "Content-Type: application/json" -d '{"foo":"bar","baz":5}'
http://localhost:3005/add
It looks like clojure is not receiving the json data that I posted at all.
Here's what the request var contains:
: {:scheme :http, :query-params {}, :form-params {}, :request-method :post,
:query-string nil, :route-params {}, :content-type "\"application/json\"",
:uri "/event", :server-name "localhost", :params {},
:headers {"user-agent" "Fiddler", "content-type" "\"application/json\"",
"content-length" "23", "host" "localhost:3005"},
:content-length 23, :server-port 3005, :character-encoding nil, :body #}
As you can see, all params are empty...
I am using compojure and cheshire - and I can convert data into json and return them just fine for GET routes.. I need to figure out how to pass json and convert it into clojure data..
thanks
That's because :params is filled by a ring middleware which deals with "form-encoded" body.
You can use ring-json to wrap your application into this other middleware. It will parse the JSON body and fill :params accordingly. (https://github.com/ring-clojure/ring-json)
Here is an example that does what you want. The code was taken from this project. In the README you will see some of the access patterns that this supports. The code is a little messy, but it should illustrate how this can be done.
(ns poky.protocol.http.jdbc.text
(:require [poky.kv.core :as kv]
(compojure [core :refer :all]
[route :as route]
[handler :as handler])
[ring.util.response :refer [response not-found]]
(ring.middleware [format-response :as format-response ]
[format-params :as format-params])
[cheshire.core :as json]
[ring.middleware.stacktrace :as trace]))
;
; curl -d"some data" -H'Content-Type: application/text' -v -X PUT http://localhost:8080/xxx
; curl -d'{"bla":"bar"}' -H'Content-Type: application/json' -v -X PUT http://localhost:8080/bla
(def valid-key-regex #"[\d\w-_.,]+")
; FIXME: this should be split- one fn for get, one for mget
(defn- wrap-get
[kvstore ks params headers body]
(response
(let [eks (clojure.string/split ks #",")
nks (count eks)
multi (> nks 1)
ret (if multi (kv/mget* kvstore eks) (kv/get* kvstore ks))]
(condp = (get headers "accept")
"application/json" ret
"text/plain" (if multi (throw (Exception. "Multi get unsupported with Accept: text/plain")) (get ret ks))
ret))))
(defn- wrap-put
[kvstore ks params headers body]
(if (and
(= (get headers "content-type") "application/json")
(get params (keyword ks) nil))
(kv/set* kvstore ks (get params (keyword ks)))
(kv/set* kvstore ks body))
(response ""))
(defn api
[kvstore]
(let [api-routes
(routes
(GET ["/:ks" :ks valid-key-regex] {{:keys [ks] :as params} :params body :body headers :headers}
(wrap-get kvstore ks params headers body))
(PUT ["/:ks" :ks valid-key-regex] {{:keys [ks] :as params} :params
body :body body-params :body-params headers :headers}
(let [body (slurp body)
body (if (empty? body) body-params body)]
(wrap-put kvstore ks params headers body))))]
(-> (handler/api api-routes)
(format-params/wrap-format-params
:predicate format-params/json-request?
:decoder #(json/parse-string % true)
:charset format-params/get-or-guess-charset)
(format-response/wrap-format-response
:predicate format-response/serializable?
:encoders [(format-response/make-encoder json/encode "application/json")
(format-response/make-encoder identity "text/plain")]
:charset "utf-8")
trace/wrap-stacktrace)))
Hope this helps.
Just an update to a previous answers, now is enough to add formats key with list of formats which will be processed by Ring to your handler.
So something like
(def app (noir.util.middleware/app-handler
[your-routes]
:formats [:json-kw]))