When defining a compojure handler e.g. by using the defroutes macro, I can do something like this:
(defroutes home-routes
(GET "/myhome/:id" [ id ] (home-page)))
(defn home-page [ id ]
( ... do something ... ))
So I know how to pass a piece of the path parameter. But imagine I want to return a HAL+JSON object with a selflink. How would I get defroutes to pass the whole URI to the home-page function?
The Ring request map contains all the necessary information to construct a "selflink". Specifically, :scheme, :server-name, :server-port, and :uri values can be assembled into full request URL. When I faced this problem I created Ring middleware that adds the assembled request URL to the Ring request map. I could then use the request URL in my handlers as long as I pass the request map (or some subset of it) into the handler. The following snippet shows one way of implementing this:
(defroutes app-routes
(GET "/myhome/:id" [id :as {:keys [self-link]}] (home-page id self-link))
(route/resources "/")
(route/not-found "Not Found"))
(defn wrap-request-add-self-link [handler]
(fn add-self-link [{:keys [scheme server-name server-port uri] :as r}]
(let [link (str (name scheme) "://" server-name ":" server-port uri)]
(handler (assoc r :self-link link)))))
(def app
(-> app-routes
handler/site
wrap-request-add-self-link))
Related
I have my dependencies
(ns test.core
(:require [reagent.core :as reagent :refer [atom]]
[ajax.core :refer [GET]]))
I then have my handler that handles the responses to my ajax call
(defn handle-response [response]
(println (type response))
(println film))
THe data from my call is JSON, in the browser it looks like this:
{"id":3,"name":"Sicario","star":"Emily Blunt","rating":5}
When I run the above Clojure, I see this:
cljs.core/PersistentArrayMap core.cljs:192:23
{id 3, name Sicario, star Emily Blunt, rating 5}
Is their a way for me to go from the PersistArrayMap, and destructure it into id, name, star and rating?
I tried
(let [film (js->clj (.parse js/JSON response) :keywordize-keys true)]
...)
Hoping I would get a map named film, but no avail!
I think maybe I could use (get-in), but can't get the syntax right for this either.
Thanks,
Got it,
I was missing :response-format :json and :keywords? true from my GET call
Now, it looks like this:
(GET (str "https://localhost:5001/api/films/" film-name)
{:response-format :json
:keywords? true
And it all works.
I've got a website that I'm the process of developing, the backend route which isn't working is /login with the compojure route as:
(POST login "/" [] check-admin-before-logging)
This is what the check-admin-before-logging function does:
(defn redirect-admin-to-page
[]
(-> (response/redirect "/admin/home")
(assoc :body "logging you in...")
(assoc :cookies {:token (token/sign-token)})))
;; function that checks whether credentials are in fact valid.
;; check-admin is a db func which compares hashed pswd with input.
(defn check-admin-before-logging-in
[request]
(let [username (:username (:params request))
password (:password (:params request))]
(if (check-admin username password)
(redirect-admin-to-page username)
(str "username : " username " and password " password " is not valid.\n" request))))
For the moment it just returns a string, as for testing purposes.
and for the app I have wrapped it in the following middleware:
(def app
(-> all-routes
(wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false))
(wrap-json-body)
;; (wrap-json-params)
(wrap-json-response)
))
Now when I make an AJAX request with the following on the frontend:
(defn submit
[]
(let [username *grabbed from input field...*
password *grabbed from input field...*]
(POST "/login" {:params {:username username :password password}})))
But the problem arises when I actually click the button I get this. The console shows the result of the post request itself, with :params being empty and some mangled JSON in the :body key. If I was to try and include
(wrap-json-params)
within app, I would get nil in the :body and :params key. Moreover it seems there is a problem with the output, as cheshire has problems with parsing it. If I leave the ring-json library to the side and just have wrap-defaults over app, I just get this for the body key
:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x6ef9a9e1 HttpInputOverHTTP#6ef9a9e1]
My question is how do I change the json so that it is able to be understood by wrap-json-params or cheshire? Any advice is much appreciated.
I'm new to using compojure, but have been enjoying using it so far. I'm
currently encountering a problem in one of my API endpoints that is generating
a large CSV file from the database and then passing this as the response body.
The problem I seem to be encountering is that the whole CSV file is being kept
in memory which is then causing an out of memory error in the API. What is the
best way to handle and generate this, ideally as a gzipped file? Is it possible
to stream the response so that a few thousand rows are returned at a time? When
I return a JSON response body for the same data, there is no problem returning
this.
Here is the current code I'm using to return this:
(defn complete
"Returns metrics for each completed benchmark instance"
[db-client response-format]
(let [benchmarks (completed-benchmark-metrics {} db-client)]
(case response-format
:json (json-grouped-output field-mappings benchmarks)
:csv (csv-output benchmarks))))
(defn csv-output [data-seq]
(let [header (map name (keys (first data-seq)))
out (java.io.StringWriter.)
write #(csv/write-csv out (list %))]
(write header)
(dorun (map (comp write vals) data-seq))
(.toString out)))
The data-seq is the results returned from the database, which I think is a
lazy sequence. I'm using yesql to perform the database call.
Here is my compojure resource for this API endpoint:
(defresource results-complete [db]
:available-media-types ["application/json" "text/csv"]
:allowed-methods [:get]
:handle-ok (fn [request]
(let [response-format (keyword (get-in request [:request :params :format] :json))
disposition (str "attachment; filename=\"nucleotides_benchmark_metrics." (name response-format) "\"")
response {:headers {"Content-Type" (content-types response-format)
"Content-Disposition" disposition}
:body (results/complete db response-format)}]
(ring-response response))))
Thanks to all the suggestion that were provided in this thread, I was able to create a solution using piped-input-stream:
(defn csv-output [data-seq]
(let [headers (map name (keys (first data-seq)))
rows (map vals data-seq)
stream-csv (fn [out] (csv/write-csv out (cons headers rows))
(.flush out))]
(piped-input-stream #(stream-csv (io/make-writer % {})))))
This differs from my solution because it does not realise the sequence using dorun and does not create a large String object either. This instead writes to a PipedInputStream connection asynchronously as described by the documentation:
Create an input stream from a function that takes an output stream as its
argument. The function will be executed in a separate thread. The stream
will be automatically closed after the function finishes.
Your csv-output function completely realises the dataset and turns it into a string. To lazily stream the data, you'll need to return something other than a concrete data type like a String. This suggests ring supports returning a stream, that can be lazily realised by Jetty. The answer to this question might prove useful.
I was also struggling with the streaming of large csv file. My solution was to use httpkit-channel to stream every single line of the data-seq to the client and then close the channel. My solution looks like that:
[org.httpkit.server :refer :all]
(fn handler [req]
(with-channel req channel (let [header "your$header"
data-seq ["your$seq-data"]]
(doseq [line (cons header data-seq)]
(send! channel
{:status 200
:headers {"Content-Type" "text/csv"}
:body (str line "\n")}
false))
(close channel))))
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.
I have a rather simple problem I thought. I have a luminus web project with clojurescript and cljs-ajax. What I do is an ajax request to my server and in my error-handler I want to parse a json string to display a specific error message. I just cannot get it working.
This is my code:
Clojurescript:
(defn error-handler [response]
(js/alert (aget response "message")))
; (js/alert (:message response ))) neither works here
(defn test-connection []
(POST "/url"
{:params (get-form-data)
:response-format :json
:handler succ-handler
:error-handler error-handler}))
And my serverside code:
(defn connect-jira []
{:body {:message "No connection to JIRA. Please check your details."}}
)
(defroutes jira-routes
(POST "url" []
(connect-jira)))
What is the idiomatic way to return a custom error message and parse that with clojurescript?
Update
I tracked my problem down some more. There is a difference between the success and the error handler of the POST request. Lets say my server side looks like this:
(defn connect-jira []
{:status 200 :body {:message "No connection to JIRA. Please check your details."}})
And my success handler like this:
(defn succ-handler [{:keys [message]}]
(.log js/console message))
Then its all fine, the correct messages is logged to the console.
But if my server side looks like this (different statu):
(defn connect-jira []
{:status 500 :body {:message "No connection to JIRA. Please check your details."}})
I call the error handler on cljs side
(defn err-handler [{:keys [message]}]
(.log js/console message))
But the message is "null"
I also tried to overwrite the status-text on server side, but did not succeed. So, how do I pass a message to the error handler from server side?
Are you using this library for your AJAX calls?
https://github.com/yogthos/cljs-ajax
If so, your error handler would need to look something more like this:
(defn err-handler [{:keys [response]}]
(.log js/console (:message response)))