I'm trying to handle exceptions with Dire library. Like this:
(defn test-fn []
(client/head "https://google.com/404")
)
(with-handler! #'test-fn
java.lang.Exception
(fn [e] "error!"))
But always got an error:
ExceptionInfo clj-http: status 404 clj-http.client/wrap-exceptions/fn--3052 (client.clj:196)
I've tried to change java.lang.Exception to clojure.lang.ExceptionInfo with same effect. Did I miss something?
clj-http uses Slingshot apparently, cf. clj-http documentation on exceptions. The throw+ operation of Slingshot can throw any kind of object, not just exceptions (Throwables). The clj-http documentation has an example:
; Response map is thrown as exception obj.
; We filter out by status codes
(try+
(client/get "http://some-site.com/broken")
(catch [:status 403] {:keys [request-time headers body]}
(log/warn "403" request-time headers))
(catch [:status 404] {:keys [request-time headers body]}
(log/warn "NOT Found 404" request-time headers body))
(catch Object _
(log/error (:throwable &throw-context) "unexpected error")
(throw+)))
Furthermore, the Dire documentation has an example of how to integrate with Slingshot: basically you should be able to drop [:status 404] instead of the java.lang.Exception.
Related
I'm working on my shopping app and I'm trying to make a custom exception handler, but there's something else that's catching them before I get to them!
Here is my handler
(defn wrap-fallback-exception
[handler]
(fn [request]
(try+
(handler request)
(catch [:type :test] {:keys [msg]}
(println "## Test Exception:" msg))
(catch Exception e (println "## Exception"))
(catch Throwable e
(println "## Throwable:" (.getMessage e) e)))))
Here is my stack
(-> all-routes
(wrap-anti-forgery)
(friend/authenticate {
:unauthorized-handler unauth-handler
:credential-fn (partial cred get-user)
:workflows [(workflows/interactive-form)]})
(wrap-session {:store (->ShopStore )})
(wrap-keyword-params)
(wrap-params)
(wrap-cookies)
(wrap-fallback-exception)
(run-server {:port 3000}))
I trow a test in one of my ring functions
(throw+ (ex-info "TEST" {:type :test}))
And the resulting output looks like this
Sun Apr 01 15:57:29 CEST 2018 [worker-4] ERROR - GET /user/home
clojure.lang.ExceptionInfo: TEST {:type :test}
at clojure.core$ex_info.invokeStatic(core.clj:4739)
at clojure.core$ex_info.invoke(core.clj:4739)
at shop2.views.home$mk_proj_row.invokeStatic(home.clj:90)
at shop2.views.home$mk_proj_row.invoke(home.clj:88)
at clojure.lang.AFn.applyToHelper(AFn.java:154)
at clojure.lang.AFn.applyTo(AFn.java:144)
at orchestra.spec.test$spec_checking_fn$fn__1372.doInvoke(test.clj:123)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$map$fn__5587.invoke(core.clj:2747)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:528)
at clojure.core$seq__5124.invokeStatic(core.clj:137)
at clojure.core$print_sequential.invokeStatic(core_print.clj:53)
at clojure.core$fn__7019.invokeStatic(core_print.clj:174)
at clojure.core$fn__7019.invoke(core_print.clj:174)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7039.invokeStatic(core_print.clj:225)
at clojure.core$fn__7039.invoke(core_print.clj:225)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7039.invokeStatic(core_print.clj:225)
at clojure.core$fn__7039.invoke(core_print.clj:225)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7039.invokeStatic(core_print.clj:225)
at clojure.core$fn__7039.invoke(core_print.clj:225)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7019.invokeStatic(core_print.clj:174)
at clojure.core$fn__7019.invoke(core_print.clj:174)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7019.invokeStatic(core_print.clj:174)
at clojure.core$fn__7019.invoke(core_print.clj:174)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.core$print_sequential.invokeStatic(core_print.clj:66)
at clojure.core$fn__7039.invokeStatic(core_print.clj:225)
at clojure.core$fn__7039.invoke(core_print.clj:225)
at clojure.lang.MultiFn.invoke(MultiFn.java:233)
at clojure.core$pr_on.invokeStatic(core.clj:3666)
at clojure.core$pr_on.invoke(core.clj:3660)
at clojure.lang.Var.invoke(Var.java:385)
at clojure.lang.RT.print(RT.java:1877)
at clojure.lang.RT.printString(RT.java:1857)
at clojure.lang.APersistentVector.toString(APersistentVector.java:26)
at org.httpkit.HttpUtils.bodyBuffer(HttpUtils.java:112)
at org.httpkit.HttpUtils.HttpEncode(HttpUtils.java:442)
at org.httpkit.server.HttpHandler.run(RingHandler.java:101)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
The exception is never caught by my handler!
I've tried every version I can think of with no luck :-(
Who is catching it?
And how do I make it stop?
Regards
Soren
Your handler returns successfully, without throwing an exception. The value that it returns is, apparently, a many-levels-deep vector containing, buried deep within itself, a lazy sequence. When Ring tries to print that lazy sequence, forcing that sequence calls mk-proj-row, which throws an exception. Importantly, this happens after your handler has completely finished running, meaning the exception handler is no longer in effect.
I doubt you really want to be stringifying a giant vector as your output: my guess is you forgot to use Hiccup to convert that to HTML, or something else to convert it to JSON. You should probably have some middleware installed which does that conversion before returning to Ring; if you put that middleware before your wrap-fallback-exception middleware, then the laziness will no longer be a problem, because the lazy sequence will be realized inside of the Hiccup (or whatever) renderer, rather than by Ring after all your handlers have finished.
I have a route in Phoenix that needs to check the JSON parameters with REGEX.
In my routine, I am creating a list of errors to report in the body in case one or more regexes fail.
But whenever I run the code, I get the "FunctionClauseError" error on "Regex.match?". I have tried String.match, but they evaluate to the same function.
Here is my code:
def postServidor(conn, parameters) do
reasons = []
error = False
if not Regex.match?(~r/^(19[0-9]{2}|2[0-9]{3})-(0[1-9]|1[012])-([123]0|[012][1-9]|31)$/, Map.get(parameters, "data_nascimento")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[data_nascimento] missing or failed to match API requirements. It should look like this: 1969-02-12"}]}
end
if not Regex.match?(~r/^([A-Z][a-z]+([ ]?[a-z]?['-]?[A-Z][a-z]+)*)$/, Map.get(parameters, "nome")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[name] missing or failed to match API requirements. It should look like this: Firstname Middlename(optional) Lastname"}]}
end
if not Regex.match?(~r/^([A-Z][a-z]+([ ]?[a-z]?['-]?[A-Z][a-z]+)*)$/, Map.get(parameters, "nome_identificacao")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[nome_identificacao] missing or failed to match API requirements. It should look like this: Firstname Middlename(optional) Lastname"}]}
end
if not Regex.match?(~r/\b[MF]{1}\b/, Map.get(parameters, "sexo")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[sexo] missing or failed to match API requirements. It should look like this: M for male, F for female"}]}
end
if not Regex.match?( ~r/\b[0-9]+\b/, Map.get(parameters, "id_pessoa")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[id_pessoa] missing or failed to match API requirements. It should be numeric. "}]}
end
if not Regex.match?(~r/\b[0-9]+\b/, Map.get(parameters, "matricula_interna")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[matricula_interna] missing or failed to match API requirements. It should be numeric. "}]}
end
if not Regex.match?(~r/\b[0-9]+\b/, Map.get(parameters, "siape")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[siape] missing or failed to match API requirements. It should be numeric. "}]}
end
if error = True do
json put_status(conn, 400),reasons
else
IO.puts("ok")
end
end
Regex.match?/2 requires a string as its second argument. You're using Map.get/3 to potentially return that string, but if the key isn't found in the map, it defaults to nil, and that's my guess as to what's happening. If the key is not found in your map, it's passing nil to Regex.match?/2, for which there will be no function clause that matches. You can either fix your map to correctly have the key, or you can provide your own default string to use as a third parameter to Map.get/3. For example, Map.get(parameters, "data_nascimento", "some default").
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))))
Does anybody understand the following behavior inside a ClojureScript browser repl? (provided by the boot tool chain)
my.core> (require 'cljs.pprint)
nil
my.core> (cljs.pprint/pprint "one")
#object[TypeError TypeError: cljs.pprint is undefined]
No stacktrace available.
my.core> (cljs.pprint/pprint "one" "two" "three")
WARNING: Wrong number of args (3) passed to cljs.pprint/pprint at line 1 <cljs repl>
#object[TypeError TypeError: cljs.pprint is undefined]
No stacktrace available.
It looks, that there is no reference to cljs.pprint/pprint, however the repl knows about the arity of this function.
this might help as well:
my.core> (cljs.repl/dir cljs.pprint)
t_cljs$pprint28171
t_cljs$pprint28177
with-pprint-dispatch
with-pretty-writer
write
write-out
nil
cider-repl-toggle-pretty-printing did the trick.
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)))